app/assets/javascripts/pghero/Chart.bundle.js in pghero-1.7.0 vs app/assets/javascripts/pghero/Chart.bundle.js in pghero-2.0.0

- old
+ new

@@ -1,11 +1,11 @@ /*! * Chart.js * http://chartjs.org/ - * Version: 2.2.2 + * Version: 2.5.0 * - * Copyright 2016 Nick Downie + * Copyright 2017 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){ /* MIT license */ @@ -1659,4235 +1659,4345 @@ "yellow": [255, 255, 0], "yellowgreen": [154, 205, 50] }; },{}],6:[function(require,module,exports){ //! moment.js -//! version : 2.14.1 +//! version : 2.17.1 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com ;(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.moment = factory() -}(this, function () { 'use strict'; +}(this, (function () { 'use strict'; - var hookCallback; +var hookCallback; - function utils_hooks__hooks () { - return hookCallback.apply(null, arguments); - } +function hooks () { + return hookCallback.apply(null, arguments); +} - // This is done to register the method called with moment() - // without creating circular dependencies. - function setHookCallback (callback) { - hookCallback = callback; - } +// This is done to register the method called with moment() +// without creating circular dependencies. +function setHookCallback (callback) { + hookCallback = callback; +} - function isArray(input) { - return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; - } +function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; +} - function isObject(input) { - return Object.prototype.toString.call(input) === '[object Object]'; - } +function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return input != null && Object.prototype.toString.call(input) === '[object Object]'; +} - function isObjectEmpty(obj) { - var k; - for (k in obj) { - // even if its not own property I'd still call it non-empty - return false; - } - return true; +function isObjectEmpty(obj) { + var k; + for (k in obj) { + // even if its not own property I'd still call it non-empty + return false; } + return true; +} - function isDate(input) { - return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; - } +function isNumber(input) { + return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; +} - function map(arr, fn) { - var res = [], i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); - } - return res; - } +function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; +} - function hasOwnProp(a, b) { - return Object.prototype.hasOwnProperty.call(a, b); +function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); } + return res; +} - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; - } - } +function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); +} - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; +function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; } - - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; - } - - return a; } - function create_utc__createUTC (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, true).utc(); + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; } - function defaultParsingFlags() { - // We need to deep clone this object. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso : false, - parsedDateParts : [], - meridiem : null - }; + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; } - function getParsingFlags(m) { - if (m._pf == null) { - m._pf = defaultParsingFlags(); - } - return m._pf; - } + return a; +} - var some; - if (Array.prototype.some) { - some = Array.prototype.some; - } else { - some = function (fun) { - var t = Object(this); - var len = t.length >>> 0; +function createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); +} - for (var i = 0; i < len; i++) { - if (i in t && fun.call(this, t[i], i, t)) { - return true; - } - } +function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null + }; +} - return false; - }; +function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); } + return m._pf; +} - function valid__isValid(m) { - if (m._isValid == null) { - var flags = getParsingFlags(m); - var parsedParts = some.call(flags.parsedDateParts, function (i) { - return i != null; - }); - m._isValid = !isNaN(m._d.getTime()) && - flags.overflow < 0 && - !flags.empty && - !flags.invalidMonth && - !flags.invalidWeekday && - !flags.nullInput && - !flags.invalidFormat && - !flags.userInvalidated && - (!flags.meridiem || (flags.meridiem && parsedParts)); +var some; +if (Array.prototype.some) { + some = Array.prototype.some; +} else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; - if (m._strict) { - m._isValid = m._isValid && - flags.charsLeftOver === 0 && - flags.unusedTokens.length === 0 && - flags.bigHour === undefined; + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; } } - return m._isValid; - } - function valid__createInvalid (flags) { - var m = create_utc__createUTC(NaN); - if (flags != null) { - extend(getParsingFlags(m), flags); + return false; + }; +} + +var some$1 = some; + +function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some$1.call(flags.parsedDateParts, function (i) { + return i != null; + }); + var isNowValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } else { - getParsingFlags(m).userInvalidated = true; + return isNowValid; } - - return m; } + return m._isValid; +} - function isUndefined(input) { - return input === void 0; +function createInvalid (flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); } + else { + getParsingFlags(m).userInvalidated = true; + } - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - var momentProperties = utils_hooks__hooks.momentProperties = []; + return m; +} - function copyConfig(to, from) { - var i, prop, val; +function isUndefined(input) { + return input === void 0; +} - if (!isUndefined(from._isAMomentObject)) { - to._isAMomentObject = from._isAMomentObject; - } - if (!isUndefined(from._i)) { - to._i = from._i; - } - if (!isUndefined(from._f)) { - to._f = from._f; - } - if (!isUndefined(from._l)) { - to._l = from._l; - } - if (!isUndefined(from._strict)) { - to._strict = from._strict; - } - if (!isUndefined(from._tzm)) { - to._tzm = from._tzm; - } - if (!isUndefined(from._isUTC)) { - to._isUTC = from._isUTC; - } - if (!isUndefined(from._offset)) { - to._offset = from._offset; - } - if (!isUndefined(from._pf)) { - to._pf = getParsingFlags(from); - } - if (!isUndefined(from._locale)) { - to._locale = from._locale; - } +// Plugins that add properties should also add the key here (null value), +// so we can properly clone ourselves. +var momentProperties = hooks.momentProperties = []; - if (momentProperties.length > 0) { - for (i in momentProperties) { - prop = momentProperties[i]; - val = from[prop]; - if (!isUndefined(val)) { - to[prop] = val; - } - } - } +function copyConfig(to, from) { + var i, prop, val; - return to; + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; } - - var updateInProgress = false; - - // Moment prototype object - function Moment(config) { - copyConfig(this, config); - this._d = new Date(config._d != null ? config._d.getTime() : NaN); - // Prevent infinite loop in case updateOffset creates new moment - // objects. - if (updateInProgress === false) { - updateInProgress = true; - utils_hooks__hooks.updateOffset(this); - updateInProgress = false; - } + if (!isUndefined(from._i)) { + to._i = from._i; } - - function isMoment (obj) { - return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + if (!isUndefined(from._f)) { + to._f = from._f; } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } - function absFloor (number) { - if (number < 0) { - // -0 -> 0 - return Math.ceil(number) || 0; - } else { - return Math.floor(number); + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } } } - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; + return to; +} - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - value = absFloor(coercedNumber); - } +var updateInProgress = false; - return value; +// Moment prototype object +function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } +} - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; +function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); +} + +function absFloor (number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); } +} - function warn(msg) { - if (utils_hooks__hooks.suppressDeprecationWarnings === false && - (typeof console !== 'undefined') && console.warn) { - console.warn('Deprecation warning: ' + msg); - } +function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); } - function deprecate(msg, fn) { - var firstTime = true; + return value; +} - return extend(function () { - if (utils_hooks__hooks.deprecationHandler != null) { - utils_hooks__hooks.deprecationHandler(null, msg); - } - if (firstTime) { - warn(msg + '\nArguments: ' + Array.prototype.slice.call(arguments).join(', ') + '\n' + (new Error()).stack); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); +// compare two arrays, return the number of differences +function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } } + return diffs + lengthDiff; +} - var deprecations = {}; +function warn(msg) { + if (hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); + } +} - function deprecateSimple(name, msg) { - if (utils_hooks__hooks.deprecationHandler != null) { - utils_hooks__hooks.deprecationHandler(name, msg); +function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); } - if (!deprecations[name]) { - warn(msg); - deprecations[name] = true; + if (firstTime) { + var args = []; + var arg; + for (var i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (var key in arguments[0]) { + arg += key + ': ' + arguments[0][key] + ', '; + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); + firstTime = false; } + return fn.apply(this, arguments); + }, fn); +} + +var deprecations = {}; + +function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } +} - utils_hooks__hooks.suppressDeprecationWarnings = false; - utils_hooks__hooks.deprecationHandler = null; +hooks.suppressDeprecationWarnings = false; +hooks.deprecationHandler = null; - function isFunction(input) { - return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; +function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; +} + +function set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); +} - function locale_set__set (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (isFunction(prop)) { - this[i] = prop; +function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; } else { - this['_' + i] = prop; + delete res[prop]; } } - this._config = config; - // Lenient ordinal parsing accepts just a number in addition to - // number + (possibly) stuff coming from _ordinalParseLenient. - this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); } - - function mergeConfigs(parentConfig, childConfig) { - var res = extend({}, parentConfig), prop; - for (prop in childConfig) { - if (hasOwnProp(childConfig, prop)) { - if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { - res[prop] = {}; - extend(res[prop], parentConfig[prop]); - extend(res[prop], childConfig[prop]); - } else if (childConfig[prop] != null) { - res[prop] = childConfig[prop]; - } else { - delete res[prop]; - } - } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop])) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); } - for (prop in parentConfig) { - if (hasOwnProp(parentConfig, prop) && - !hasOwnProp(childConfig, prop) && - isObject(parentConfig[prop])) { - // make sure changes to properties don't modify parent config - res[prop] = extend({}, res[prop]); - } - } - return res; } + return res; +} - function Locale(config) { - if (config != null) { - this.set(config); - } +function Locale(config) { + if (config != null) { + this.set(config); } +} - var keys; +var keys; - if (Object.keys) { - keys = Object.keys; - } else { - keys = function (obj) { - var i, res = []; - for (i in obj) { - if (hasOwnProp(obj, i)) { - res.push(i); - } +if (Object.keys) { + keys = Object.keys; +} else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); } - return res; - }; - } - - var defaultCalendar = { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' + } + return res; }; +} - function locale_calendar__calendar (key, mom, now) { - var output = this._calendar[key] || this._calendar['sameElse']; - return isFunction(output) ? output.call(mom, now) : output; - } +var keys$1 = keys; - var defaultLongDateFormat = { - LTS : 'h:mm:ss A', - LT : 'h:mm A', - L : 'MM/DD/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY h:mm A', - LLLL : 'dddd, MMMM D, YYYY h:mm A' - }; +var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' +}; - function longDateFormat (key) { - var format = this._longDateFormat[key], - formatUpper = this._longDateFormat[key.toUpperCase()]; +function calendar (key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; +} - if (format || !formatUpper) { - return format; - } +var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' +}; - this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); +function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; - return this._longDateFormat[key]; + if (format || !formatUpper) { + return format; } - var defaultInvalidDate = 'Invalid date'; + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); - function invalidDate () { - return this._invalidDate; - } + return this._longDateFormat[key]; +} - var defaultOrdinal = '%d'; - var defaultOrdinalParse = /\d{1,2}/; +var defaultInvalidDate = 'Invalid date'; - function ordinal (number) { - return this._ordinal.replace('%d', number); - } +function invalidDate () { + return this._invalidDate; +} - var defaultRelativeTime = { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }; +var defaultOrdinal = '%d'; +var defaultOrdinalParse = /\d{1,2}/; - function relative__relativeTime (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (isFunction(output)) ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - } +function ordinal (number) { + return this._ordinal.replace('%d', number); +} - function pastFuture (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return isFunction(format) ? format(output) : format.replace(/%s/i, output); - } +var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' +}; - var aliases = {}; +function relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); +} - function addUnitAlias (unit, shorthand) { - var lowerCase = unit.toLowerCase(); - aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; - } +function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); +} - function normalizeUnits(units) { - return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; - } +var aliases = {}; - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; +function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; +} - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } +function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; +} + +function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; } } - - return normalizedInput; } - var priorities = {}; + return normalizedInput; +} - function addUnitPriority(unit, priority) { - priorities[unit] = priority; +var priorities = {}; + +function addUnitPriority(unit, priority) { + priorities[unit] = priority; +} + +function getPrioritizedUnits(unitsObj) { + var units = []; + for (var u in unitsObj) { + units.push({unit: u, priority: priorities[u]}); } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; +} - function getPrioritizedUnits(unitsObj) { - var units = []; - for (var u in unitsObj) { - units.push({unit: u, priority: priorities[u]}); +function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); } - units.sort(function (a, b) { - return a.priority - b.priority; - }); - return units; - } + }; +} - function makeGetSet (unit, keepTime) { - return function (value) { - if (value != null) { - get_set__set(this, unit, value); - utils_hooks__hooks.updateOffset(this, keepTime); - return this; - } else { - return get_set__get(this, unit); - } - }; - } +function get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; +} - function get_set__get (mom, unit) { - return mom.isValid() ? - mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; +function set$1 (mom, unit, value) { + if (mom.isValid()) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); } +} - function get_set__set (mom, unit, value) { - if (mom.isValid()) { - mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } +// MOMENTS + +function stringGet (units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); } + return this; +} - // MOMENTS - function stringGet (units) { +function stringSet (units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units); + for (var i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { units = normalizeUnits(units); if (isFunction(this[units])) { - return this[units](); + return this[units](value); } - return this; } + return this; +} +function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; +} - function stringSet (units, value) { - if (typeof units === 'object') { - units = normalizeObjectUnits(units); - var prioritized = getPrioritizedUnits(units); - for (var i = 0; i < prioritized.length; i++) { - this[prioritized[i].unit](units[prioritized[i].unit]); - } - } else { - units = normalizeUnits(units); - if (isFunction(this[units])) { - return this[units](value); - } - } - return this; - } +var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; - function zeroFill(number, targetLength, forceSign) { - var absNumber = '' + Math.abs(number), - zerosToFill = targetLength - absNumber.length, - sign = number >= 0; - return (sign ? (forceSign ? '+' : '') : '-') + - Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; - } +var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; - var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; +var formatFunctions = {}; - var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; +var formatTokenFunctions = {}; - var formatFunctions = {}; - - var formatTokenFunctions = {}; - - // token: 'M' - // padded: ['MM', 2] - // ordinal: 'Mo' - // callback: function () { this.month() + 1 } - function addFormatToken (token, padded, ordinal, callback) { - var func = callback; - if (typeof callback === 'string') { - func = function () { - return this[callback](); - }; - } - if (token) { - formatTokenFunctions[token] = func; - } - if (padded) { - formatTokenFunctions[padded[0]] = function () { - return zeroFill(func.apply(this, arguments), padded[1], padded[2]); - }; - } - if (ordinal) { - formatTokenFunctions[ordinal] = function () { - return this.localeData().ordinal(func.apply(this, arguments), token); - }; - } +// token: 'M' +// padded: ['MM', 2] +// ordinal: 'Mo' +// callback: function () { this.month() + 1 } +function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } +} - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); +function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); } + return input.replace(/\\/g, ''); +} - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; +function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); } - - return function (mom) { - var output = '', i; - for (i = 0; i < length; i++) { - output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; - } - return output; - }; } - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); + return function (mom) { + var output = '', i; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; } + return output; + }; +} - format = expandFormat(format, m.localeData()); - formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); - - return formatFunctions[format](m); +// format date using native date object +function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); } - function expandFormat(format, locale) { - var i = 5; + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } + return formatFunctions[format](m); +} - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } +function expandFormat(format, locale) { + var i = 5; - return format; + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; } - var match1 = /\d/; // 0 - 9 - var match2 = /\d\d/; // 00 - 99 - var match3 = /\d{3}/; // 000 - 999 - var match4 = /\d{4}/; // 0000 - 9999 - var match6 = /[+-]?\d{6}/; // -999999 - 999999 - var match1to2 = /\d\d?/; // 0 - 99 - var match3to4 = /\d\d\d\d?/; // 999 - 9999 - var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 - var match1to3 = /\d{1,3}/; // 0 - 999 - var match1to4 = /\d{1,4}/; // 0 - 9999 - var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } - var matchUnsigned = /\d+/; // 0 - inf - var matchSigned = /[+-]?\d+/; // -inf - inf + return format; +} - var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z - var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z +var match1 = /\d/; // 0 - 9 +var match2 = /\d\d/; // 00 - 99 +var match3 = /\d{3}/; // 000 - 999 +var match4 = /\d{4}/; // 0000 - 9999 +var match6 = /[+-]?\d{6}/; // -999999 - 999999 +var match1to2 = /\d\d?/; // 0 - 99 +var match3to4 = /\d\d\d\d?/; // 999 - 9999 +var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 +var match1to3 = /\d{1,3}/; // 0 - 999 +var match1to4 = /\d{1,4}/; // 0 - 9999 +var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 - var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 +var matchUnsigned = /\d+/; // 0 - inf +var matchSigned = /[+-]?\d+/; // -inf - inf - // any word (or two) characters or numbers including two/three word month in arabic. - // includes scottish gaelic two word and hyphenated months - var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; +var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z +var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z +var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 - var regexes = {}; +// any word (or two) characters or numbers including two/three word month in arabic. +// includes scottish gaelic two word and hyphenated months +var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; - function addRegexToken (token, regex, strictRegex) { - regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { - return (isStrict && strictRegex) ? strictRegex : regex; - }; - } - function getParseRegexForToken (token, config) { - if (!hasOwnProp(regexes, token)) { - return new RegExp(unescapeFormat(token)); - } +var regexes = {}; - return regexes[token](config._strict, config._locale); - } +function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; +} - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function unescapeFormat(s) { - return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - })); +function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); } - function regexEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } + return regexes[token](config._strict, config._locale); +} - var tokens = {}; +// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript +function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); +} - function addParseToken (token, callback) { - var i, func = callback; - if (typeof token === 'string') { - token = [token]; - } - if (typeof callback === 'number') { - func = function (input, array) { - array[callback] = toInt(input); - }; - } - for (i = 0; i < token.length; i++) { - tokens[token[i]] = func; - } - } +function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +} - function addWeekParseToken (token, callback) { - addParseToken(token, function (input, array, config, token) { - config._w = config._w || {}; - callback(input, config._w, config, token); - }); +var tokens = {}; + +function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } +} - function addTimeToArrayFromToken(token, input, config) { - if (input != null && hasOwnProp(tokens, token)) { - tokens[token](input, config._a, config, token); - } +function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); +} + +function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); } +} - var YEAR = 0; - var MONTH = 1; - var DATE = 2; - var HOUR = 3; - var MINUTE = 4; - var SECOND = 5; - var MILLISECOND = 6; - var WEEK = 7; - var WEEKDAY = 8; +var YEAR = 0; +var MONTH = 1; +var DATE = 2; +var HOUR = 3; +var MINUTE = 4; +var SECOND = 5; +var MILLISECOND = 6; +var WEEK = 7; +var WEEKDAY = 8; - var indexOf; +var indexOf; - if (Array.prototype.indexOf) { - indexOf = Array.prototype.indexOf; - } else { - indexOf = function (o) { - // I know - var i; - for (i = 0; i < this.length; ++i) { - if (this[i] === o) { - return i; - } +if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; +} else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; } - return -1; - }; - } + } + return -1; + }; +} - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } +var indexOf$1 = indexOf; - // FORMATTING +function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); +} - addFormatToken('M', ['MM', 2], 'Mo', function () { - return this.month() + 1; - }); +// FORMATTING - addFormatToken('MMM', 0, 0, function (format) { - return this.localeData().monthsShort(this, format); - }); +addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; +}); - addFormatToken('MMMM', 0, 0, function (format) { - return this.localeData().months(this, format); - }); +addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); +}); - // ALIASES +addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); +}); - addUnitAlias('month', 'M'); +// ALIASES - // PRIORITY +addUnitAlias('month', 'M'); - addUnitPriority('month', 8); +// PRIORITY - // PARSING +addUnitPriority('month', 8); - addRegexToken('M', match1to2); - addRegexToken('MM', match1to2, match2); - addRegexToken('MMM', function (isStrict, locale) { - return locale.monthsShortRegex(isStrict); - }); - addRegexToken('MMMM', function (isStrict, locale) { - return locale.monthsRegex(isStrict); - }); +// PARSING - addParseToken(['M', 'MM'], function (input, array) { - array[MONTH] = toInt(input) - 1; - }); +addRegexToken('M', match1to2); +addRegexToken('MM', match1to2, match2); +addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); +}); +addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); +}); - addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { - var month = config._locale.monthsParse(input, token, config._strict); - // if we didn't find a month name, mark the date as invalid. - if (month != null) { - array[MONTH] = month; - } else { - getParsingFlags(config).invalidMonth = input; - } - }); +addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; +}); - // LOCALES +addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } +}); - var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/; - var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); - function localeMonths (m, format) { - return isArray(this._months) ? this._months[m.month()] : - this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; +// LOCALES + +var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; +var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); +function localeMonths (m, format) { + if (!m) { + return this._months; } + return isArray(this._months) ? this._months[m.month()] : + this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; +} - var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); - function localeMonthsShort (m, format) { - return isArray(this._monthsShort) ? this._monthsShort[m.month()] : - this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; +var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); +function localeMonthsShort (m, format) { + if (!m) { + return this._monthsShort; } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; +} - function units_month__handleStrictParse(monthName, format, strict) { - var i, ii, mom, llc = monthName.toLocaleLowerCase(); - if (!this._monthsParse) { - // this is not used - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - for (i = 0; i < 12; ++i) { - mom = create_utc__createUTC([2000, i]); - this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); - this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); - } +function handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); } + } - if (strict) { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; + if (strict) { + if (format === 'MMM') { + ii = indexOf$1.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf$1.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; } + ii = indexOf$1.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; } else { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; + ii = indexOf$1.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; } + ii = indexOf$1.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; } } +} - function localeMonthsParse (monthName, format, strict) { - var i, mom, regex; +function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; - if (this._monthsParseExact) { - return units_month__handleStrictParse.call(this, monthName, format, strict); - } + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } - if (!this._monthsParse) { - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - } + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } - // TODO: add sorting - // Sorting makes sure if one month (or abbr) is a prefix of another - // see sorting in computeMonthsParse - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = create_utc__createUTC([2000, i]); - if (strict && !this._longMonthsParse[i]) { - this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); - this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); - } - if (!strict && !this._monthsParse[i]) { - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { - return i; - } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { - return i; - } else if (!strict && this._monthsParse[i].test(monthName)) { - return i; - } + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } } +} - // MOMENTS +// MOMENTS - function setMonth (mom, value) { - var dayOfMonth; +function setMonth (mom, value) { + var dayOfMonth; - if (!mom.isValid()) { - // No op - return mom; - } - - if (typeof value === 'string') { - if (/^\d+$/.test(value)) { - value = toInt(value); - } else { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (typeof value !== 'number') { - return mom; - } - } - } - - dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + if (!mom.isValid()) { + // No op return mom; } - function getSetMonth (value) { - if (value != null) { - setMonth(this, value); - utils_hooks__hooks.updateOffset(this, true); - return this; + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); } else { - return get_set__get(this, 'Month'); + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } } } - function getDaysInMonth () { - return daysInMonth(this.year(), this.month()); - } + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; +} - var defaultMonthsShortRegex = matchWord; - function monthsShortRegex (isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsShortStrictRegex; - } else { - return this._monthsShortRegex; - } - } else { - if (!hasOwnProp(this, '_monthsShortRegex')) { - this._monthsShortRegex = defaultMonthsShortRegex; - } - return this._monthsShortStrictRegex && isStrict ? - this._monthsShortStrictRegex : this._monthsShortRegex; - } +function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); } +} - var defaultMonthsRegex = matchWord; - function monthsRegex (isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsStrictRegex; - } else { - return this._monthsRegex; - } +function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); +} + +var defaultMonthsShortRegex = matchWord; +function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; } else { - if (!hasOwnProp(this, '_monthsRegex')) { - this._monthsRegex = defaultMonthsRegex; - } - return this._monthsStrictRegex && isStrict ? - this._monthsStrictRegex : this._monthsRegex; + return this._monthsShortRegex; } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; } +} - function computeMonthsParse () { - function cmpLenRev(a, b) { - return b.length - a.length; +var defaultMonthsRegex = matchWord; +function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); } - - var shortPieces = [], longPieces = [], mixedPieces = [], - i, mom; - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = create_utc__createUTC([2000, i]); - shortPieces.push(this.monthsShort(mom, '')); - longPieces.push(this.months(mom, '')); - mixedPieces.push(this.months(mom, '')); - mixedPieces.push(this.monthsShort(mom, '')); + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; } - // Sorting makes sure if one month (or abbr) is a prefix of another it - // will match the longer piece. - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - for (i = 0; i < 12; i++) { - shortPieces[i] = regexEscape(shortPieces[i]); - longPieces[i] = regexEscape(longPieces[i]); + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; } - for (i = 0; i < 24; i++) { - mixedPieces[i] = regexEscape(mixedPieces[i]); - } + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; + } +} - this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._monthsShortRegex = this._monthsRegex; - this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); - this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); +function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; } - // FORMATTING + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } - addFormatToken('Y', 0, 0, function () { - var y = this.year(); - return y <= 9999 ? '' + y : '+' + y; - }); + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); +} - addFormatToken(0, ['YY', 2], 0, function () { - return this.year() % 100; - }); +// FORMATTING - addFormatToken(0, ['YYYY', 4], 0, 'year'); - addFormatToken(0, ['YYYYY', 5], 0, 'year'); - addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); +addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; +}); - // ALIASES +addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; +}); - addUnitAlias('year', 'y'); +addFormatToken(0, ['YYYY', 4], 0, 'year'); +addFormatToken(0, ['YYYYY', 5], 0, 'year'); +addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); - // PRIORITIES +// ALIASES - addUnitPriority('year', 1); +addUnitAlias('year', 'y'); - // PARSING +// PRIORITIES - addRegexToken('Y', matchSigned); - addRegexToken('YY', match1to2, match2); - addRegexToken('YYYY', match1to4, match4); - addRegexToken('YYYYY', match1to6, match6); - addRegexToken('YYYYYY', match1to6, match6); +addUnitPriority('year', 1); - addParseToken(['YYYYY', 'YYYYYY'], YEAR); - addParseToken('YYYY', function (input, array) { - array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input); - }); - addParseToken('YY', function (input, array) { - array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); - }); - addParseToken('Y', function (input, array) { - array[YEAR] = parseInt(input, 10); - }); +// PARSING - // HELPERS +addRegexToken('Y', matchSigned); +addRegexToken('YY', match1to2, match2); +addRegexToken('YYYY', match1to4, match4); +addRegexToken('YYYYY', match1to6, match6); +addRegexToken('YYYYYY', match1to6, match6); - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } +addParseToken(['YYYYY', 'YYYYYY'], YEAR); +addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); +}); +addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); +}); +addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); +}); - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } +// HELPERS - // HOOKS +function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; +} - utils_hooks__hooks.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; +function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; +} - // MOMENTS +// HOOKS - var getSetYear = makeGetSet('FullYear', true); +hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); +}; - function getIsLeapYear () { - return isLeapYear(this.year()); - } +// MOMENTS - function createDate (y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); +var getSetYear = makeGetSet('FullYear', true); - //the date constructor remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { - date.setFullYear(y); - } - return date; - } +function getIsLeapYear () { + return isLeapYear(this.year()); +} - function createUTCDate (y) { - var date = new Date(Date.UTC.apply(null, arguments)); +function createDate (y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); - //the Date.UTC function remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { - date.setUTCFullYear(y); - } - return date; + //the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { + date.setFullYear(y); } + return date; +} - // start-of-first-week - start-of-year - function firstWeekOffset(year, dow, doy) { - var // first-week day -- which january is always in the first week (4 for iso, 1 for other) - fwd = 7 + dow - doy, - // first-week day local weekday -- which local weekday is fwd - fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; +function createUTCDate (y) { + var date = new Date(Date.UTC.apply(null, arguments)); - return -fwdlw + fwd - 1; + //the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); } + return date; +} - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, dow, doy) { - var localWeekday = (7 + weekday - dow) % 7, - weekOffset = firstWeekOffset(year, dow, doy), - dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, - resYear, resDayOfYear; +// start-of-first-week - start-of-year +function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; - if (dayOfYear <= 0) { - resYear = year - 1; - resDayOfYear = daysInYear(resYear) + dayOfYear; - } else if (dayOfYear > daysInYear(year)) { - resYear = year + 1; - resDayOfYear = dayOfYear - daysInYear(year); - } else { - resYear = year; - resDayOfYear = dayOfYear; - } + return -fwdlw + fwd - 1; +} - return { - year: resYear, - dayOfYear: resDayOfYear - }; +//http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday +function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; } - function weekOfYear(mom, dow, doy) { - var weekOffset = firstWeekOffset(mom.year(), dow, doy), - week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, - resWeek, resYear; + return { + year: resYear, + dayOfYear: resDayOfYear + }; +} - if (week < 1) { - resYear = mom.year() - 1; - resWeek = week + weeksInYear(resYear, dow, doy); - } else if (week > weeksInYear(mom.year(), dow, doy)) { - resWeek = week - weeksInYear(mom.year(), dow, doy); - resYear = mom.year() + 1; - } else { - resYear = mom.year(); - resWeek = week; - } +function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; - return { - week: resWeek, - year: resYear - }; + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; } - function weeksInYear(year, dow, doy) { - var weekOffset = firstWeekOffset(year, dow, doy), - weekOffsetNext = firstWeekOffset(year + 1, dow, doy); - return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; - } + return { + week: resWeek, + year: resYear + }; +} - // FORMATTING +function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; +} - addFormatToken('w', ['ww', 2], 'wo', 'week'); - addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); +// FORMATTING - // ALIASES +addFormatToken('w', ['ww', 2], 'wo', 'week'); +addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); - addUnitAlias('week', 'w'); - addUnitAlias('isoWeek', 'W'); +// ALIASES - // PRIORITIES +addUnitAlias('week', 'w'); +addUnitAlias('isoWeek', 'W'); - addUnitPriority('week', 5); - addUnitPriority('isoWeek', 5); +// PRIORITIES - // PARSING +addUnitPriority('week', 5); +addUnitPriority('isoWeek', 5); - addRegexToken('w', match1to2); - addRegexToken('ww', match1to2, match2); - addRegexToken('W', match1to2); - addRegexToken('WW', match1to2, match2); +// PARSING - addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { - week[token.substr(0, 1)] = toInt(input); - }); +addRegexToken('w', match1to2); +addRegexToken('ww', match1to2, match2); +addRegexToken('W', match1to2); +addRegexToken('WW', match1to2, match2); - // HELPERS +addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); +}); - // LOCALES +// HELPERS - function localeWeek (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - } +// LOCALES - var defaultLocaleWeek = { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }; +function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; +} - function localeFirstDayOfWeek () { - return this._week.dow; - } +var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. +}; - function localeFirstDayOfYear () { - return this._week.doy; - } +function localeFirstDayOfWeek () { + return this._week.dow; +} - // MOMENTS +function localeFirstDayOfYear () { + return this._week.doy; +} - function getSetWeek (input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - } +// MOMENTS - function getSetISOWeek (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - } +function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); +} - // FORMATTING +function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); +} - addFormatToken('d', 0, 'do', 'day'); +// FORMATTING - addFormatToken('dd', 0, 0, function (format) { - return this.localeData().weekdaysMin(this, format); - }); +addFormatToken('d', 0, 'do', 'day'); - addFormatToken('ddd', 0, 0, function (format) { - return this.localeData().weekdaysShort(this, format); - }); +addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); +}); - addFormatToken('dddd', 0, 0, function (format) { - return this.localeData().weekdays(this, format); - }); +addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); +}); - addFormatToken('e', 0, 0, 'weekday'); - addFormatToken('E', 0, 0, 'isoWeekday'); +addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); +}); - // ALIASES +addFormatToken('e', 0, 0, 'weekday'); +addFormatToken('E', 0, 0, 'isoWeekday'); - addUnitAlias('day', 'd'); - addUnitAlias('weekday', 'e'); - addUnitAlias('isoWeekday', 'E'); +// ALIASES - // PRIORITY - addUnitPriority('day', 11); - addUnitPriority('weekday', 11); - addUnitPriority('isoWeekday', 11); +addUnitAlias('day', 'd'); +addUnitAlias('weekday', 'e'); +addUnitAlias('isoWeekday', 'E'); - // PARSING +// PRIORITY +addUnitPriority('day', 11); +addUnitPriority('weekday', 11); +addUnitPriority('isoWeekday', 11); - addRegexToken('d', match1to2); - addRegexToken('e', match1to2); - addRegexToken('E', match1to2); - addRegexToken('dd', function (isStrict, locale) { - return locale.weekdaysMinRegex(isStrict); - }); - addRegexToken('ddd', function (isStrict, locale) { - return locale.weekdaysShortRegex(isStrict); - }); - addRegexToken('dddd', function (isStrict, locale) { - return locale.weekdaysRegex(isStrict); - }); +// PARSING - addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { - var weekday = config._locale.weekdaysParse(input, token, config._strict); - // if we didn't get a weekday name, mark the date as invalid - if (weekday != null) { - week.d = weekday; - } else { - getParsingFlags(config).invalidWeekday = input; - } - }); +addRegexToken('d', match1to2); +addRegexToken('e', match1to2); +addRegexToken('E', match1to2); +addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); +}); +addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); +}); +addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); +}); - addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { - week[token] = toInt(input); - }); +addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } +}); - // HELPERS +addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); +}); - function parseWeekday(input, locale) { - if (typeof input !== 'string') { - return input; - } +// HELPERS - if (!isNaN(input)) { - return parseInt(input, 10); - } +function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } - input = locale.weekdaysParse(input); - if (typeof input === 'number') { - return input; - } - - return null; + if (!isNaN(input)) { + return parseInt(input, 10); } - function parseIsoWeekday(input, locale) { - if (typeof input === 'string') { - return locale.weekdaysParse(input) % 7 || 7; - } - return isNaN(input) ? null : input; + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; } - // LOCALES + return null; +} - var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); - function localeWeekdays (m, format) { - return isArray(this._weekdays) ? this._weekdays[m.day()] : - this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; +function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; } + return isNaN(input) ? null : input; +} - var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); - function localeWeekdaysShort (m) { - return this._weekdaysShort[m.day()]; - } +// LOCALES - var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); - function localeWeekdaysMin (m) { - return this._weekdaysMin[m.day()]; +var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); +function localeWeekdays (m, format) { + if (!m) { + return this._weekdays; } + return isArray(this._weekdays) ? this._weekdays[m.day()] : + this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; +} - function day_of_week__handleStrictParse(weekdayName, format, strict) { - var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._shortWeekdaysParse = []; - this._minWeekdaysParse = []; +var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); +function localeWeekdaysShort (m) { + return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; +} - for (i = 0; i < 7; ++i) { - mom = create_utc__createUTC([2000, 1]).day(i); - this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); - this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); - this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); - } +var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); +function localeWeekdaysMin (m) { + return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; +} + +function handleStrictParse$1(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); } + } - if (strict) { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; + if (strict) { + if (format === 'dddd') { + ii = indexOf$1.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; } + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; } else { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; + ii = indexOf$1.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; } + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; } } +} - function localeWeekdaysParse (weekdayName, format, strict) { - var i, mom, regex; +function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; - if (this._weekdaysParseExact) { - return day_of_week__handleStrictParse.call(this, weekdayName, format, strict); - } + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._minWeekdaysParse = []; - this._shortWeekdaysParse = []; - this._fullWeekdaysParse = []; - } + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already - mom = create_utc__createUTC([2000, 1]).day(i); - if (strict && !this._fullWeekdaysParse[i]) { - this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); - this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); - this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); - } - if (!this._weekdaysParse[i]) { - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { - return i; - } + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } } +} - // MOMENTS +// MOMENTS - function getSetDayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } +function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } +} - function getSetLocaleDayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); +function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); +} - function getSetISODayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } +function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. - if (input != null) { - var weekday = parseIsoWeekday(input, this.localeData()); - return this.day(this.day() % 7 ? weekday : weekday - 7); - } else { - return this.day() || 7; - } + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; } +} - var defaultWeekdaysRegex = matchWord; - function weekdaysRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysStrictRegex; - } else { - return this._weekdaysRegex; - } +var defaultWeekdaysRegex = matchWord; +function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; } else { - if (!hasOwnProp(this, '_weekdaysRegex')) { - this._weekdaysRegex = defaultWeekdaysRegex; - } - return this._weekdaysStrictRegex && isStrict ? - this._weekdaysStrictRegex : this._weekdaysRegex; + return this._weekdaysRegex; } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; } +} - var defaultWeekdaysShortRegex = matchWord; - function weekdaysShortRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysShortStrictRegex; - } else { - return this._weekdaysShortRegex; - } +var defaultWeekdaysShortRegex = matchWord; +function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; } else { - if (!hasOwnProp(this, '_weekdaysShortRegex')) { - this._weekdaysShortRegex = defaultWeekdaysShortRegex; - } - return this._weekdaysShortStrictRegex && isStrict ? - this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + return this._weekdaysShortRegex; } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; } +} - var defaultWeekdaysMinRegex = matchWord; - function weekdaysMinRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysMinStrictRegex; - } else { - return this._weekdaysMinRegex; - } +var defaultWeekdaysMinRegex = matchWord; +function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; } else { - if (!hasOwnProp(this, '_weekdaysMinRegex')) { - this._weekdaysMinRegex = defaultWeekdaysMinRegex; - } - return this._weekdaysMinStrictRegex && isStrict ? - this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + return this._weekdaysMinRegex; } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; } +} - function computeWeekdaysParse () { - function cmpLenRev(a, b) { - return b.length - a.length; - } +function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } - var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], - i, mom, minp, shortp, longp; - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - mom = create_utc__createUTC([2000, 1]).day(i); - minp = this.weekdaysMin(mom, ''); - shortp = this.weekdaysShort(mom, ''); - longp = this.weekdays(mom, ''); - minPieces.push(minp); - shortPieces.push(shortp); - longPieces.push(longp); - mixedPieces.push(minp); - mixedPieces.push(shortp); - mixedPieces.push(longp); - } - // Sorting makes sure if one weekday (or abbr) is a prefix of another it - // will match the longer piece. - minPieces.sort(cmpLenRev); - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - for (i = 0; i < 7; i++) { - shortPieces[i] = regexEscape(shortPieces[i]); - longPieces[i] = regexEscape(longPieces[i]); - mixedPieces[i] = regexEscape(mixedPieces[i]); - } + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } - this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._weekdaysShortRegex = this._weekdaysRegex; - this._weekdaysMinRegex = this._weekdaysRegex; + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; - this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); - this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); - this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); - } + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); +} - // FORMATTING +// FORMATTING - function hFormat() { - return this.hours() % 12 || 12; - } +function hFormat() { + return this.hours() % 12 || 12; +} - function kFormat() { - return this.hours() || 24; - } +function kFormat() { + return this.hours() || 24; +} - addFormatToken('H', ['HH', 2], 0, 'hour'); - addFormatToken('h', ['hh', 2], 0, hFormat); - addFormatToken('k', ['kk', 2], 0, kFormat); +addFormatToken('H', ['HH', 2], 0, 'hour'); +addFormatToken('h', ['hh', 2], 0, hFormat); +addFormatToken('k', ['kk', 2], 0, kFormat); - addFormatToken('hmm', 0, 0, function () { - return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); - }); +addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); +}); - addFormatToken('hmmss', 0, 0, function () { - return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2); - }); +addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); +}); - addFormatToken('Hmm', 0, 0, function () { - return '' + this.hours() + zeroFill(this.minutes(), 2); - }); +addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); +}); - addFormatToken('Hmmss', 0, 0, function () { - return '' + this.hours() + zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2); +addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); +}); + +function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); }); +} - function meridiem (token, lowercase) { - addFormatToken(token, 0, 0, function () { - return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); - }); - } +meridiem('a', true); +meridiem('A', false); - meridiem('a', true); - meridiem('A', false); +// ALIASES - // ALIASES +addUnitAlias('hour', 'h'); - addUnitAlias('hour', 'h'); +// PRIORITY +addUnitPriority('hour', 13); - // PRIORITY - addUnitPriority('hour', 13); +// PARSING - // PARSING +function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; +} - function matchMeridiem (isStrict, locale) { - return locale._meridiemParse; - } +addRegexToken('a', matchMeridiem); +addRegexToken('A', matchMeridiem); +addRegexToken('H', match1to2); +addRegexToken('h', match1to2); +addRegexToken('HH', match1to2, match2); +addRegexToken('hh', match1to2, match2); - addRegexToken('a', matchMeridiem); - addRegexToken('A', matchMeridiem); - addRegexToken('H', match1to2); - addRegexToken('h', match1to2); - addRegexToken('HH', match1to2, match2); - addRegexToken('hh', match1to2, match2); +addRegexToken('hmm', match3to4); +addRegexToken('hmmss', match5to6); +addRegexToken('Hmm', match3to4); +addRegexToken('Hmmss', match5to6); - addRegexToken('hmm', match3to4); - addRegexToken('hmmss', match5to6); - addRegexToken('Hmm', match3to4); - addRegexToken('Hmmss', match5to6); +addParseToken(['H', 'HH'], HOUR); +addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; +}); +addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; +}); +addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; +}); +addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; +}); +addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); +}); +addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); +}); - addParseToken(['H', 'HH'], HOUR); - addParseToken(['a', 'A'], function (input, array, config) { - config._isPm = config._locale.isPM(input); - config._meridiem = input; - }); - addParseToken(['h', 'hh'], function (input, array, config) { - array[HOUR] = toInt(input); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmmss', function (input, array, config) { - var pos1 = input.length - 4; - var pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('Hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - }); - addParseToken('Hmmss', function (input, array, config) { - var pos1 = input.length - 4; - var pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - }); +// LOCALES - // LOCALES +function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); +} - function localeIsPM (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); +var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; +function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; } +} - var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; - function localeMeridiem (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - } +// MOMENTS - // MOMENTS +// Setting the hour should keep the time, because the user explicitly +// specified which hour he wants. So trying to maintain the same hour (in +// a new timezone) makes sense. Adding/subtracting hours does not follow +// this rule. +var getSetHour = makeGetSet('Hours', true); - // Setting the hour should keep the time, because the user explicitly - // specified which hour he wants. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - var getSetHour = makeGetSet('Hours', true); +// months +// week +// weekdays +// meridiem +var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + ordinalParse: defaultOrdinalParse, + relativeTime: defaultRelativeTime, - var baseConfig = { - calendar: defaultCalendar, - longDateFormat: defaultLongDateFormat, - invalidDate: defaultInvalidDate, - ordinal: defaultOrdinal, - ordinalParse: defaultOrdinalParse, - relativeTime: defaultRelativeTime, + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, - months: defaultLocaleMonths, - monthsShort: defaultLocaleMonthsShort, + week: defaultLocaleWeek, - week: defaultLocaleWeek, + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, - weekdays: defaultLocaleWeekdays, - weekdaysMin: defaultLocaleWeekdaysMin, - weekdaysShort: defaultLocaleWeekdaysShort, + meridiemParse: defaultLocaleMeridiemParse +}; - meridiemParse: defaultLocaleMeridiemParse - }; +// internal storage for locale config files +var locales = {}; +var localeFamilies = {}; +var globalLocale; - // internal storage for locale config files - var locales = {}; - var globalLocale; +function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; +} - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } +// pick the locale from the array +// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each +// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root +function chooseLocale(names) { + var i = 0, j, next, locale, split; - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, j, next, locale, split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; } - i++; + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; } - return null; + i++; } + return null; +} - function loadLocale(name) { - var oldLocale = null; - // TODO: Find a better way to register and load all the locales in Node - if (!locales[name] && (typeof module !== 'undefined') && - module && module.exports) { - try { - oldLocale = globalLocale._abbr; - require('./locale/' + name); - // because defineLocale currently also sets the global locale, we - // want to undo that for lazy loaded locales - locale_locales__getSetGlobalLocale(oldLocale); - } catch (e) { } - } - return locales[name]; +function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we + // want to undo that for lazy loaded locales + getSetGlobalLocale(oldLocale); + } catch (e) { } } + return locales[name]; +} - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - function locale_locales__getSetGlobalLocale (key, values) { - var data; - if (key) { - if (isUndefined(values)) { - data = locale_locales__getLocale(key); - } - else { - data = defineLocale(key, values); - } - - if (data) { - // moment.duration._locale = moment._locale = data; - globalLocale = data; - } +// This function will load locale and then set the global locale. If +// no arguments are passed in, it will simply return the current global +// locale key. +function getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); } + else { + data = defineLocale(key, values); + } - return globalLocale._abbr; + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } } - function defineLocale (name, config) { - if (config !== null) { - var parentConfig = baseConfig; - config.abbr = name; - if (locales[name] != null) { - deprecateSimple('defineLocaleOverride', - 'use moment.updateLocale(localeName, config) to change ' + - 'an existing locale. moment.defineLocale(localeName, ' + - 'config) should only be used for creating a new locale ' + - 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); - parentConfig = locales[name]._config; - } else if (config.parentLocale != null) { - if (locales[config.parentLocale] != null) { - parentConfig = locales[config.parentLocale]._config; - } else { - // treat as if there is no base config - deprecateSimple('parentLocaleUndefined', - 'specified parentLocale is not defined yet. See http://momentjs.com/guides/#/warnings/parent-locale/'); + return globalLocale._abbr; +} + +function defineLocale (name, config) { + if (config !== null) { + var parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; } + localeFamilies[config.parentLocale].push({ + name: name, + config: config + }); + return null; } - locales[name] = new Locale(mergeConfigs(parentConfig, config)); + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); - // backwards compat for now: also set the locale - locale_locales__getSetGlobalLocale(name); - - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); } - } - function updateLocale(name, config) { - if (config != null) { - var locale, parentConfig = baseConfig; - // MERGE - if (locales[name] != null) { - parentConfig = locales[name]._config; - } - config = mergeConfigs(parentConfig, config); - locale = new Locale(config); - locale.parentLocale = locales[name]; - locales[name] = locale; + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); - // backwards compat for now: also set the locale - locale_locales__getSetGlobalLocale(name); - } else { - // pass null for config to unupdate, useful for tests - if (locales[name] != null) { - if (locales[name].parentLocale != null) { - locales[name] = locales[name].parentLocale; - } else if (locales[name] != null) { - delete locales[name]; - } - } - } + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; } +} - // returns locale data - function locale_locales__getLocale (key) { - var locale; - - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; +function updateLocale(name, config) { + if (config != null) { + var locale, parentConfig = baseConfig; + // MERGE + if (locales[name] != null) { + parentConfig = locales[name]._config; } + config = mergeConfigs(parentConfig, config); + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; - if (!key) { - return globalLocale; - } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; } - key = [key]; } + } + return locales[name]; +} - return chooseLocale(key); +// returns locale data +function getLocale (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; } - function locale_locales__listLocales() { - return keys(locales); + if (!key) { + return globalLocale; } - function checkOverflow (m) { - var overflow; - var a = m._a; + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } - if (a && getParsingFlags(m).overflow === -2) { - overflow = - a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : - a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : - a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : - a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : - a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : - a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : - -1; + return chooseLocale(key); +} - if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } - if (getParsingFlags(m)._overflowWeeks && overflow === -1) { - overflow = WEEK; - } - if (getParsingFlags(m)._overflowWeekday && overflow === -1) { - overflow = WEEKDAY; - } +function listLocales() { + return keys$1(locales); +} - getParsingFlags(m).overflow = overflow; +function checkOverflow (m) { + var overflow; + var a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } - return m; + getParsingFlags(m).overflow = overflow; } - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; - var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; + return m; +} - var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; +// iso 8601 regex +// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) +var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; +var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; - var isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], - ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], - ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], - ['GGGG-[W]WW', /\d{4}-W\d\d/, false], - ['YYYY-DDD', /\d{4}-\d{3}/], - ['YYYY-MM', /\d{4}-\d\d/, false], - ['YYYYYYMMDD', /[+-]\d{10}/], - ['YYYYMMDD', /\d{8}/], - // YYYYMM is NOT allowed by the standard - ['GGGG[W]WWE', /\d{4}W\d{3}/], - ['GGGG[W]WW', /\d{4}W\d{2}/, false], - ['YYYYDDD', /\d{7}/] - ]; +var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; - // iso time formats and regexes - var isoTimes = [ - ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], - ['HH:mm:ss', /\d\d:\d\d:\d\d/], - ['HH:mm', /\d\d:\d\d/], - ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], - ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], - ['HHmmss', /\d\d\d\d\d\d/], - ['HHmm', /\d\d\d\d/], - ['HH', /\d\d/] - ]; +var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] +]; - var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; +// iso time formats and regexes +var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] +]; - // date from iso format - function configFromISO(config) { - var i, l, - string = config._i, - match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), - allowTime, dateFormat, timeFormat, tzFormat; +var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; - if (match) { - getParsingFlags(config).iso = true; +// date from iso format +function configFromISO(config) { + var i, l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(match[1])) { - dateFormat = isoDates[i][0]; - allowTime = isoDates[i][2] !== false; + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; break; } } - if (dateFormat == null) { + if (timeFormat == null) { config._isValid = false; return; } - if (match[3]) { - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(match[3])) { - // match[2] should be 'T' or space - timeFormat = (match[2] || ' ') + isoTimes[i][0]; - break; - } - } - if (timeFormat == null) { - config._isValid = false; - return; - } - } - if (!allowTime && timeFormat != null) { + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { config._isValid = false; return; } - if (match[4]) { - if (tzRegex.exec(match[4])) { - tzFormat = 'Z'; - } else { - config._isValid = false; - return; - } - } - config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); - configFromStringAndFormat(config); - } else { - config._isValid = false; } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; } +} - // date from iso format or fallback - function configFromString(config) { - var matched = aspNetJsonRegex.exec(config._i); +// date from iso format or fallback +function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); - if (matched !== null) { - config._d = new Date(+matched[1]); - return; - } + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } - configFromISO(config); - if (config._isValid === false) { - delete config._isValid; - utils_hooks__hooks.createFromInputFallback(config); - } + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + hooks.createFromInputFallback(config); } +} - utils_hooks__hooks.createFromInputFallback = deprecate( - 'moment construction falls back to js Date. This is ' + - 'discouraged and will be removed in upcoming major ' + - 'release. Please refer to ' + - 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', - function (config) { - config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); - } - ); +hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non ISO date formats are ' + + 'discouraged and will be removed in an upcoming major release. Please refer to ' + + 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } +); - // Pick the first defined of two or three arguments. - function defaults(a, b, c) { - if (a != null) { - return a; - } - if (b != null) { - return b; - } - return c; +// Pick the first defined of two or three arguments. +function defaults(a, b, c) { + if (a != null) { + return a; } + if (b != null) { + return b; + } + return c; +} - function currentDateArray(config) { - // hooks is actually the exported moment object - var nowValue = new Date(utils_hooks__hooks.now()); - if (config._useUTC) { - return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; - } - return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; +function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; +} - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function configFromArray (config) { - var i, date, input = [], currentDate, yearToUse; +// convert an array to a date. +// the array should mirror the parameters below +// note: all values past the year are optional and will default to the lowest possible value. +// [year, month, day , hour, minute, second, millisecond] +function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; - if (config._d) { - return; - } + if (config._d) { + return; + } - currentDate = currentDateArray(config); + currentDate = currentDateArray(config); - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse)) { + getParsingFlags(config)._overflowDayOfYear = true; } - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } - if (config._dayOfYear > daysInYear(yearToUse)) { - getParsingFlags(config)._overflowDayOfYear = true; - } + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } - date = createUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } - // Check for 24:00:00.000 - if (config._a[HOUR] === 24 && - config._a[MINUTE] === 0 && - config._a[SECOND] === 0 && - config._a[MILLISECOND] === 0) { - config._nextDay = true; - config._a[HOUR] = 0; - } + if (config._nextDay) { + config._a[HOUR] = 24; + } +} - config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); - // Apply timezone offset from input. The actual utcOffset can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - } +function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; - if (config._nextDay) { - config._a[HOUR] = 24; + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; } - } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; + var curWeek = weekOfYear(createLocal(), dow, doy); - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); - week = defaults(w.W, 1); - weekday = defaults(w.E, 1); - if (weekday < 1 || weekday > 7) { + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { weekdayOverflow = true; } - } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); - week = defaults(w.w, 1); - - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < 0 || weekday > 6) { - weekdayOverflow = true; - } - } else if (w.e != null) { - // local weekday -- counting starts from begining of week - weekday = w.e + dow; - if (w.e < 0 || w.e > 6) { - weekdayOverflow = true; - } - } else { - // default to begining of week - weekday = dow; + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; } - } - if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { - getParsingFlags(config)._overflowWeeks = true; - } else if (weekdayOverflow != null) { - getParsingFlags(config)._overflowWeekday = true; } else { - temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; + // default to begining of week + weekday = dow; } } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } +} - // constant that refers to the ISO standard - utils_hooks__hooks.ISO_8601 = function () {}; +// constant that refers to the ISO standard +hooks.ISO_8601 = function () {}; - // date from string and format string - function configFromStringAndFormat(config) { - // TODO: Move this to another part of the creation flow to prevent circular deps - if (config._f === utils_hooks__hooks.ISO_8601) { - configFromISO(config); - return; - } +// date from string and format string +function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } - config._a = []; - getParsingFlags(config).empty = true; + config._a = []; + getParsingFlags(config).empty = true; - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; - tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - // console.log('token', token, 'parsedInput', parsedInput, - // 'regex', getParseRegexForToken(token, config)); + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - getParsingFlags(config).unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; + getParsingFlags(config).empty = false; } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - getParsingFlags(config).empty = false; - } - else { - getParsingFlags(config).unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { + else { getParsingFlags(config).unusedTokens.push(token); } + addTimeToArrayFromToken(token, parsedInput, config); } - - // add remaining unparsed input length to the string - getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - getParsingFlags(config).unusedInput.push(string); + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); } + } - // clear _12h flag if hour is <= 12 - if (config._a[HOUR] <= 12 && - getParsingFlags(config).bigHour === true && - config._a[HOUR] > 0) { - getParsingFlags(config).bigHour = undefined; - } + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } - getParsingFlags(config).parsedDateParts = config._a.slice(0); - getParsingFlags(config).meridiem = config._meridiem; - // handle meridiem - config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - - configFromArray(config); - checkOverflow(config); + // clear _12h flag if hour is <= 12 + if (config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; } + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - function meridiemFixWrap (locale, hour, meridiem) { - var isPm; + configFromArray(config); + checkOverflow(config); +} - if (meridiem == null) { - // nothing to do - return hour; + +function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; } - if (locale.meridiemHour != null) { - return locale.meridiemHour(hour, meridiem); - } else if (locale.isPM != null) { - // Fallback - isPm = locale.isPM(meridiem); - if (isPm && hour < 12) { - hour += 12; - } - if (!isPm && hour === 12) { - hour = 0; - } - return hour; - } else { - // this is not supposed to happen - return hour; + if (!isPm && hour === 12) { + hour = 0; } + return hour; + } else { + // this is not supposed to happen + return hour; } +} - // date from string and array of format strings - function configFromStringAndArray(config) { - var tempConfig, - bestMoment, +// date from string and array of format strings +function configFromStringAndArray(config) { + var tempConfig, + bestMoment, - scoreToBeat, - i, - currentScore; + scoreToBeat, + i, + currentScore; - if (config._f.length === 0) { - getParsingFlags(config).invalidFormat = true; - config._d = new Date(NaN); - return; + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } - tempConfig._f = config._f[i]; - configFromStringAndFormat(tempConfig); + if (!isValid(tempConfig)) { + continue; + } - if (!valid__isValid(tempConfig)) { - continue; - } + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; - // if there is any input that was not parsed add a penalty for that format - currentScore += getParsingFlags(tempConfig).charsLeftOver; + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; - //or tokens - currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + getParsingFlags(tempConfig).score = currentScore; - getParsingFlags(tempConfig).score = currentScore; - - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; } - - extend(config, bestMoment || tempConfig); } - function configFromObject(config) { - if (config._d) { - return; - } + extend(config, bestMoment || tempConfig); +} - var i = normalizeObjectUnits(config._i); - config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { - return obj && parseInt(obj, 10); - }); - - configFromArray(config); +function configFromObject(config) { + if (config._d) { + return; } - function createFromConfig (config) { - var res = new Moment(checkOverflow(prepareConfig(config))); - if (res._nextDay) { - // Adding is smart enough around DST - res.add(1, 'd'); - res._nextDay = undefined; - } + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); - return res; + configFromArray(config); +} + +function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; } - function prepareConfig (config) { - var input = config._i, - format = config._f; + return res; +} - config._locale = config._locale || locale_locales__getLocale(config._l); +function prepareConfig (config) { + var input = config._i, + format = config._f; - if (input === null || (format === undefined && input === '')) { - return valid__createInvalid({nullInput: true}); - } + config._locale = config._locale || getLocale(config._l); - if (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } + if (input === null || (format === undefined && input === '')) { + return createInvalid({nullInput: true}); + } - if (isMoment(input)) { - return new Moment(checkOverflow(input)); - } else if (isArray(format)) { - configFromStringAndArray(config); - } else if (isDate(input)) { - config._d = input; - } else if (format) { - configFromStringAndFormat(config); - } else { - configFromInput(config); - } + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } - if (!valid__isValid(config)) { - config._d = null; - } - - return config; + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); } - function configFromInput(config) { - var input = config._i; - if (input === undefined) { - config._d = new Date(utils_hooks__hooks.now()); - } else if (isDate(input)) { - config._d = new Date(input.valueOf()); - } else if (typeof input === 'string') { - configFromString(config); - } else if (isArray(input)) { - config._a = map(input.slice(0), function (obj) { - return parseInt(obj, 10); - }); - configFromArray(config); - } else if (typeof(input) === 'object') { - configFromObject(config); - } else if (typeof(input) === 'number') { - // from milliseconds - config._d = new Date(input); - } else { - utils_hooks__hooks.createFromInputFallback(config); - } + if (!isValid(config)) { + config._d = null; } - function createLocalOrUTC (input, format, locale, strict, isUTC) { - var c = {}; + return config; +} - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } +function configFromInput(config) { + var input = config._i; + if (input === undefined) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (typeof(input) === 'object') { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } +} - if ((isObject(input) && isObjectEmpty(input)) || - (isArray(input) && input.length === 0)) { - input = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c._isAMomentObject = true; - c._useUTC = c._isUTC = isUTC; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; +function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; - return createFromConfig(c); + if (locale === true || locale === false) { + strict = locale; + locale = undefined; } - function local__createLocal (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, false); + if ((isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0)) { + input = undefined; } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; - var prototypeMin = deprecate( - 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = local__createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other < this ? this : other; - } else { - return valid__createInvalid(); - } + return createFromConfig(c); +} + +function createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); +} + +var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); } - ); + } +); - var prototypeMax = deprecate( - 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = local__createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other > this ? this : other; - } else { - return valid__createInvalid(); - } +var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); } - ); + } +); - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; +// Pick a moment m from moments so that m[fn](other) is true for all +// other. This relies on the function fn to be transitive. +// +// moments should either be an array of moment objects or an array, whose +// first element is an array of moment objects. +function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; } - if (!moments.length) { - return local__createLocal(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (!moments[i].isValid() || moments[i][fn](res)) { - res = moments[i]; - } - } - return res; } + return res; +} - // TODO: Use [].sort instead? - function min () { - var args = [].slice.call(arguments, 0); +// TODO: Use [].sort instead? +function min () { + var args = [].slice.call(arguments, 0); - return pickBy('isBefore', args); - } + return pickBy('isBefore', args); +} - function max () { - var args = [].slice.call(arguments, 0); +function max () { + var args = [].slice.call(arguments, 0); - return pickBy('isAfter', args); - } + return pickBy('isAfter', args); +} - var now = function () { - return Date.now ? Date.now() : +(new Date()); - }; +var now = function () { + return Date.now ? Date.now() : +(new Date()); +}; - function Duration (duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; +function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; - this._data = {}; + this._data = {}; - this._locale = locale_locales__getLocale(); + this._locale = getLocale(); - this._bubble(); - } + this._bubble(); +} - function isDuration (obj) { - return obj instanceof Duration; +function isDuration (obj) { + return obj instanceof Duration; +} + +function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); } +} - // FORMATTING +// FORMATTING - function offset (token, separator) { - addFormatToken(token, 0, 0, function () { - var offset = this.utcOffset(); - var sign = '+'; - if (offset < 0) { - offset = -offset; - sign = '-'; - } - return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); - }); - } +function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); +} - offset('Z', ':'); - offset('ZZ', ''); +offset('Z', ':'); +offset('ZZ', ''); - // PARSING +// PARSING - addRegexToken('Z', matchShortOffset); - addRegexToken('ZZ', matchShortOffset); - addParseToken(['Z', 'ZZ'], function (input, array, config) { - config._useUTC = true; - config._tzm = offsetFromString(matchShortOffset, input); - }); +addRegexToken('Z', matchShortOffset); +addRegexToken('ZZ', matchShortOffset); +addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); +}); - // HELPERS +// HELPERS - // timezone chunker - // '+10:00' > ['10', '00'] - // '-1530' > ['-15', '30'] - var chunkOffset = /([\+\-]|\d\d)/gi; +// timezone chunker +// '+10:00' > ['10', '00'] +// '-1530' > ['-15', '30'] +var chunkOffset = /([\+\-]|\d\d)/gi; - function offsetFromString(matcher, string) { - var matches = ((string || '').match(matcher) || []); - var chunk = matches[matches.length - 1] || []; - var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; - var minutes = +(parts[1] * 60) + toInt(parts[2]); +function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher); - return parts[0] === '+' ? minutes : -minutes; + if (matches === null) { + return null; } - // Return a moment from input, that is local/utc/zone equivalent to model. - function cloneWithOffset(input, model) { - var res, diff; - if (model._isUTC) { - res = model.clone(); - diff = (isMoment(input) || isDate(input) ? input.valueOf() : local__createLocal(input).valueOf()) - res.valueOf(); - // Use low-level api, because this fn is low-level api. - res._d.setTime(res._d.valueOf() + diff); - utils_hooks__hooks.updateOffset(res, false); - return res; - } else { - return local__createLocal(input).local(); - } - } + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); - function getDateOffset (m) { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + return minutes === 0 ? + 0 : + parts[0] === '+' ? minutes : -minutes; +} + +// Return a moment from input, that is local/utc/zone equivalent to model. +function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); } +} - // HOOKS +function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; +} - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - utils_hooks__hooks.updateOffset = function () {}; +// HOOKS - // MOMENTS +// This function will be called whenever a moment is mutated. +// It is intended to keep the offset in sync with the timezone. +hooks.updateOffset = function () {}; - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - function getSetOffset (input, keepLocalTime) { - var offset = this._offset || 0, - localAdjust; - if (!this.isValid()) { - return input != null ? this : NaN; +// MOMENTS + +// keepLocalTime = true means only change the timezone, without +// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> +// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset +// +0200, so we adjust the time as needed, to be valid. +// +// Keeping the time actually adds/subtracts (one hour) +// from the actual represented time. That is why we call updateOffset +// a second time. In case it wants us to change the offset again +// _changeInProgress == true case, then we have to adjust, because +// there is no such time in the given timezone. +function getSetOffset (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16) { + input = input * 60; } - if (input != null) { - if (typeof input === 'string') { - input = offsetFromString(matchShortOffset, input); - } else if (Math.abs(input) < 16) { - input = input * 60; + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract(this, createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; } - if (!this._isUTC && keepLocalTime) { - localAdjust = getDateOffset(this); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.add(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - utils_hooks__hooks.updateOffset(this, true); - this._changeInProgress = null; - } - } - return this; - } else { - return this._isUTC ? offset : getDateOffset(this); } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); } +} - function getSetZone (input, keepLocalTime) { - if (input != null) { - if (typeof input !== 'string') { - input = -input; - } +function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } - this.utcOffset(input, keepLocalTime); + this.utcOffset(input, keepLocalTime); - return this; - } else { - return -this.utcOffset(); - } + return this; + } else { + return -this.utcOffset(); } +} - function setOffsetToUTC (keepLocalTime) { - return this.utcOffset(0, keepLocalTime); - } +function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); +} - function setOffsetToLocal (keepLocalTime) { - if (this._isUTC) { - this.utcOffset(0, keepLocalTime); - this._isUTC = false; +function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; - if (keepLocalTime) { - this.subtract(getDateOffset(this), 'm'); - } + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); } - return this; } + return this; +} - function setOffsetToParsedOffset () { - if (this._tzm) { - this.utcOffset(this._tzm); - } else if (typeof this._i === 'string') { - this.utcOffset(offsetFromString(matchOffset, this._i)); +function setOffsetToParsedOffset () { + if (this._tzm != null) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); } - return this; - } - - function hasAlignedHourOffset (input) { - if (!this.isValid()) { - return false; + else { + this.utcOffset(0, true); } - input = input ? local__createLocal(input).utcOffset() : 0; - - return (this.utcOffset() - input) % 60 === 0; } + return this; +} - function isDaylightSavingTime () { - return ( - this.utcOffset() > this.clone().month(0).utcOffset() || - this.utcOffset() > this.clone().month(5).utcOffset() - ); +function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; } + input = input ? createLocal(input).utcOffset() : 0; - function isDaylightSavingTimeShifted () { - if (!isUndefined(this._isDSTShifted)) { - return this._isDSTShifted; - } + return (this.utcOffset() - input) % 60 === 0; +} - var c = {}; +function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); +} - copyConfig(c, this); - c = prepareConfig(c); - - if (c._a) { - var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a); - this._isDSTShifted = this.isValid() && - compareArrays(c._a, other.toArray()) > 0; - } else { - this._isDSTShifted = false; - } - +function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { return this._isDSTShifted; } - function isLocal () { - return this.isValid() ? !this._isUTC : false; - } + var c = {}; - function isUtcOffset () { - return this.isValid() ? this._isUTC : false; - } + copyConfig(c, this); + c = prepareConfig(c); - function isUtc () { - return this.isValid() ? this._isUTC && this._offset === 0 : false; + if (c._a) { + var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; } - // ASP.NET json date format regex - var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/; + return this._isDSTShifted; +} - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - // and further modified to allow for strings containing both week and day - var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; +function isLocal () { + return this.isValid() ? !this._isUTC : false; +} - function create__createDuration (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - diffRes; +function isUtcOffset () { + return this.isValid() ? this._isUTC : false; +} - if (isDuration(input)) { - duration = { - ms : input._milliseconds, - d : input._days, - M : input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; - } else { - duration.milliseconds = input; - } - } else if (!!(match = aspNetRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : 0, - d : toInt(match[DATE]) * sign, - h : toInt(match[HOUR]) * sign, - m : toInt(match[MINUTE]) * sign, - s : toInt(match[SECOND]) * sign, - ms : toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : parseIso(match[2], sign), - M : parseIso(match[3], sign), - w : parseIso(match[4], sign), - d : parseIso(match[5], sign), - h : parseIso(match[6], sign), - m : parseIso(match[7], sign), - s : parseIso(match[8], sign) - }; - } else if (duration == null) {// checks for null or undefined - duration = {}; - } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { - diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); +function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; +} - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; - } +// ASP.NET json date format regex +var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; - ret = new Duration(duration); +// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html +// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere +// and further modified to allow for strings containing both week and day +var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; - if (isDuration(input) && hasOwnProp(input, '_locale')) { - ret._locale = input._locale; +function createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (isNumber(input)) { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); - return ret; + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; } - create__createDuration.fn = Duration.prototype; + ret = new Duration(duration); - function parseIso (inp, sign) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; } - function positiveMomentsDifference(base, other) { - var res = {milliseconds: 0, months: 0}; + return ret; +} - res.months = other.month() - base.month() + - (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; - } +createDuration.fn = Duration.prototype; - res.milliseconds = +other - +(base.clone().add(res.months, 'M')); +function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; +} - return res; +function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; } - function momentsDifference(base, other) { - var res; - if (!(base.isValid() && other.isValid())) { - return {milliseconds: 0, months: 0}; - } + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - other = cloneWithOffset(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; - } + return res; +} - return res; +function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; } - function absRound (number) { - if (number < 0) { - return Math.round(-1 * number) * -1; - } else { - return Math.round(number); - } + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; } - // TODO: remove 'name' arg after deprecation is removed - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + - 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); - tmp = val; val = period; period = tmp; - } + return res; +} - val = typeof val === 'string' ? +val : val; - dur = create__createDuration(val, period); - add_subtract__addSubtract(this, dur, direction); - return this; - }; - } - - function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = absRound(duration._days), - months = absRound(duration._months); - - if (!mom.isValid()) { - // No op - return; +// TODO: remove 'name' arg after deprecation is removed +function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); + tmp = val; val = period; period = tmp; } - updateOffset = updateOffset == null ? true : updateOffset; + val = typeof val === 'string' ? +val : val; + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; +} - if (milliseconds) { - mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); - } - if (days) { - get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); - } - if (months) { - setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); - } - if (updateOffset) { - utils_hooks__hooks.updateOffset(mom, days || months); - } +function addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; } - var add_subtract__add = createAdder(1, 'add'); - var add_subtract__subtract = createAdder(-1, 'subtract'); + updateOffset = updateOffset == null ? true : updateOffset; - function getCalendarFormat(myMoment, now) { - var diff = myMoment.diff(now, 'days', true); - return diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } +} - function moment_calendar__calendar (time, formats) { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're local/utc/offset or not. - var now = time || local__createLocal(), - sod = cloneWithOffset(now, this).startOf('day'), - format = utils_hooks__hooks.calendarFormat(this, sod) || 'sameElse'; +var add = createAdder(1, 'add'); +var subtract = createAdder(-1, 'subtract'); - var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); +function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; +} - return this.format(output || this.localeData().calendar(format, this, local__createLocal(now))); - } +function calendar$1 (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse'; - function clone () { - return new Moment(this); - } + var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); - function isAfter (input, units) { - var localInput = isMoment(input) ? input : local__createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); - if (units === 'millisecond') { - return this.valueOf() > localInput.valueOf(); - } else { - return localInput.valueOf() < this.clone().startOf(units).valueOf(); - } - } + return this.format(output || this.localeData().calendar(format, this, createLocal(now))); +} - function isBefore (input, units) { - var localInput = isMoment(input) ? input : local__createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); - if (units === 'millisecond') { - return this.valueOf() < localInput.valueOf(); - } else { - return this.clone().endOf(units).valueOf() < localInput.valueOf(); - } - } +function clone () { + return new Moment(this); +} - function isBetween (from, to, units, inclusivity) { - inclusivity = inclusivity || '()'; - return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && - (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); +function isAfter (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; } - - function isSame (input, units) { - var localInput = isMoment(input) ? input : local__createLocal(input), - inputMs; - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units || 'millisecond'); - if (units === 'millisecond') { - return this.valueOf() === localInput.valueOf(); - } else { - inputMs = localInput.valueOf(); - return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); - } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); } +} - function isSameOrAfter (input, units) { - return this.isSame(input, units) || this.isAfter(input,units); +function isBefore (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; } - - function isSameOrBefore (input, units) { - return this.isSame(input, units) || this.isBefore(input,units); + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); } +} - function diff (input, units, asFloat) { - var that, - zoneDelta, - delta, output; +function isBetween (from, to, units, inclusivity) { + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && + (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); +} - if (!this.isValid()) { - return NaN; - } +function isSame (input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } +} - that = cloneWithOffset(input, this); +function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input,units); +} - if (!that.isValid()) { - return NaN; - } +function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input,units); +} - zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; +function diff (input, units, asFloat) { + var that, + zoneDelta, + delta, output; - units = normalizeUnits(units); - - if (units === 'year' || units === 'month' || units === 'quarter') { - output = monthDiff(this, that); - if (units === 'quarter') { - output = output / 3; - } else if (units === 'year') { - output = output / 12; - } - } else { - delta = this - that; - output = units === 'second' ? delta / 1e3 : // 1000 - units === 'minute' ? delta / 6e4 : // 1000 * 60 - units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - delta; - } - return asFloat ? output : absFloor(output); + if (!this.isValid()) { + return NaN; } - function monthDiff (a, b) { - // difference in months - var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), - // b is in (anchor - 1 month, anchor + 1 month) - anchor = a.clone().add(wholeMonthDiff, 'months'), - anchor2, adjust; + that = cloneWithOffset(input, this); - if (b - anchor < 0) { - anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor - anchor2); - } else { - anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor2 - anchor); - } - - //check for negative zero, return zero if negative zero - return -(wholeMonthDiff + adjust) || 0; + if (!that.isValid()) { + return NaN; } - utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; - utils_hooks__hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; - function toString () { - return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); - } + units = normalizeUnits(units); - function moment_format__toISOString () { - var m = this.clone().utc(); - if (0 < m.year() && m.year() <= 9999) { - if (isFunction(Date.prototype.toISOString)) { - // native implementation is ~50x faster, use it when we can - return this.toDate().toISOString(); - } else { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; } + } else { + delta = this - that; + output = units === 'second' ? delta / 1e3 : // 1000 + units === 'minute' ? delta / 6e4 : // 1000 * 60 + units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + delta; } + return asFloat ? output : absFloor(output); +} - function format (inputString) { - if (!inputString) { - inputString = this.isUtc() ? utils_hooks__hooks.defaultFormatUtc : utils_hooks__hooks.defaultFormat; - } - var output = formatMoment(this, inputString); - return this.localeData().postformat(output); - } +function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; - function from (time, withoutSuffix) { - if (this.isValid() && - ((isMoment(time) && time.isValid()) || - local__createLocal(time).isValid())) { - return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); - } else { - return this.localeData().invalidDate(); - } + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); } - function fromNow (withoutSuffix) { - return this.from(local__createLocal(), withoutSuffix); - } + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; +} - function to (time, withoutSuffix) { - if (this.isValid() && - ((isMoment(time) && time.isValid()) || - local__createLocal(time).isValid())) { - return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); +hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; +hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + +function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); +} + +function toISOString () { + var m = this.clone().utc(); + if (0 < m.year() && m.year() <= 9999) { + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); } else { - return this.localeData().invalidDate(); + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); } + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); } +} - function toNow (withoutSuffix) { - return this.to(local__createLocal(), withoutSuffix); +/** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ +function inspect () { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; } + var func = 'moment'; + var zone = ''; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + var prefix = '[' + func + '("]'; + var year = (0 < this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; + var datetime = '-MM-DD[T]HH:mm:ss.SSS'; + var suffix = zone + '[")]'; - // If passed a locale key, it will set the locale for this - // instance. Otherwise, it will return the locale configuration - // variables for this instance. - function locale (key) { - var newLocaleData; + return this.format(prefix + year + datetime + suffix); +} - if (key === undefined) { - return this._locale._abbr; - } else { - newLocaleData = locale_locales__getLocale(key); - if (newLocaleData != null) { - this._locale = newLocaleData; - } - return this; - } +function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); +} - var lang = deprecate( - 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - return this.locale(key); - } - } - ); +function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } +} - function localeData () { - return this._locale; +function fromNow (withoutSuffix) { + return this.from(createLocal(), withoutSuffix); +} + +function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); } +} - function startOf (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'quarter': - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - case 'date': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - } +function toNow (withoutSuffix) { + return this.to(createLocal(), withoutSuffix); +} - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } - if (units === 'isoWeek') { - this.isoWeekday(1); - } +// If passed a locale key, it will set the locale for this +// instance. Otherwise, it will return the locale configuration +// variables for this instance. +function locale (key) { + var newLocaleData; - // quarters are also special - if (units === 'quarter') { - this.month(Math.floor(this.month() / 3) * 3); + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; } - return this; } +} - function endOf (units) { - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond') { - return this; +var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); } + } +); - // 'date' is an alias for 'day', so it should be considered as such. - if (units === 'date') { - units = 'day'; - } +function localeData () { + return this._locale; +} - return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); +function startOf (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + case 'date': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); } - function to_type__valueOf () { - return this._d.valueOf() - ((this._offset || 0) * 60000); + // weeks are a special case + if (units === 'week') { + this.weekday(0); } - - function unix () { - return Math.floor(this.valueOf() / 1000); + if (units === 'isoWeek') { + this.isoWeekday(1); } - function toDate () { - return new Date(this.valueOf()); + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); } - function toArray () { - var m = this; - return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; - } + return this; +} - function toObject () { - var m = this; - return { - years: m.year(), - months: m.month(), - date: m.date(), - hours: m.hours(), - minutes: m.minutes(), - seconds: m.seconds(), - milliseconds: m.milliseconds() - }; +function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; } - function toJSON () { - // new Date(NaN).toJSON() === null - return this.isValid() ? this.toISOString() : null; + // 'date' is an alias for 'day', so it should be considered as such. + if (units === 'date') { + units = 'day'; } - function moment_valid__isValid () { - return valid__isValid(this); - } + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); +} - function parsingFlags () { - return extend({}, getParsingFlags(this)); - } +function valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); +} - function invalidAt () { - return getParsingFlags(this).overflow; - } +function unix () { + return Math.floor(this.valueOf() / 1000); +} - function creationData() { - return { - input: this._i, - format: this._f, - locale: this._locale, - isUTC: this._isUTC, - strict: this._strict - }; - } +function toDate () { + return new Date(this.valueOf()); +} - // FORMATTING +function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; +} - addFormatToken(0, ['gg', 2], 0, function () { - return this.weekYear() % 100; - }); +function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; +} - addFormatToken(0, ['GG', 2], 0, function () { - return this.isoWeekYear() % 100; - }); +function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; +} - function addWeekYearFormatToken (token, getter) { - addFormatToken(0, [token, token.length], 0, getter); - } +function isValid$1 () { + return isValid(this); +} - addWeekYearFormatToken('gggg', 'weekYear'); - addWeekYearFormatToken('ggggg', 'weekYear'); - addWeekYearFormatToken('GGGG', 'isoWeekYear'); - addWeekYearFormatToken('GGGGG', 'isoWeekYear'); +function parsingFlags () { + return extend({}, getParsingFlags(this)); +} - // ALIASES +function invalidAt () { + return getParsingFlags(this).overflow; +} - addUnitAlias('weekYear', 'gg'); - addUnitAlias('isoWeekYear', 'GG'); +function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; +} - // PRIORITY +// FORMATTING - addUnitPriority('weekYear', 1); - addUnitPriority('isoWeekYear', 1); +addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; +}); +addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; +}); - // PARSING +function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); +} - addRegexToken('G', matchSigned); - addRegexToken('g', matchSigned); - addRegexToken('GG', match1to2, match2); - addRegexToken('gg', match1to2, match2); - addRegexToken('GGGG', match1to4, match4); - addRegexToken('gggg', match1to4, match4); - addRegexToken('GGGGG', match1to6, match6); - addRegexToken('ggggg', match1to6, match6); +addWeekYearFormatToken('gggg', 'weekYear'); +addWeekYearFormatToken('ggggg', 'weekYear'); +addWeekYearFormatToken('GGGG', 'isoWeekYear'); +addWeekYearFormatToken('GGGGG', 'isoWeekYear'); - addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { - week[token.substr(0, 2)] = toInt(input); - }); +// ALIASES - addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { - week[token] = utils_hooks__hooks.parseTwoDigitYear(input); - }); +addUnitAlias('weekYear', 'gg'); +addUnitAlias('isoWeekYear', 'GG'); - // MOMENTS +// PRIORITY - function getSetWeekYear (input) { - return getSetWeekYearHelper.call(this, - input, - this.week(), - this.weekday(), - this.localeData()._week.dow, - this.localeData()._week.doy); - } +addUnitPriority('weekYear', 1); +addUnitPriority('isoWeekYear', 1); - function getSetISOWeekYear (input) { - return getSetWeekYearHelper.call(this, - input, this.isoWeek(), this.isoWeekday(), 1, 4); - } - function getISOWeeksInYear () { - return weeksInYear(this.year(), 1, 4); - } +// PARSING - function getWeeksInYear () { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - } +addRegexToken('G', matchSigned); +addRegexToken('g', matchSigned); +addRegexToken('GG', match1to2, match2); +addRegexToken('gg', match1to2, match2); +addRegexToken('GGGG', match1to4, match4); +addRegexToken('gggg', match1to4, match4); +addRegexToken('GGGGG', match1to6, match6); +addRegexToken('ggggg', match1to6, match6); - function getSetWeekYearHelper(input, week, weekday, dow, doy) { - var weeksTarget; - if (input == null) { - return weekOfYear(this, dow, doy).year; - } else { - weeksTarget = weeksInYear(input, dow, doy); - if (week > weeksTarget) { - week = weeksTarget; - } - return setWeekAll.call(this, input, week, weekday, dow, doy); +addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); +}); + +addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); +}); + +// MOMENTS + +function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); +} + +function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); +} + +function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); +} + +function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); +} + +function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; } + return setWeekAll.call(this, input, week, weekday, dow, doy); } +} - function setWeekAll(weekYear, week, weekday, dow, doy) { - var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), - date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); +function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); - this.year(date.getUTCFullYear()); - this.month(date.getUTCMonth()); - this.date(date.getUTCDate()); - return this; - } + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; +} - // FORMATTING +// FORMATTING - addFormatToken('Q', 0, 'Qo', 'quarter'); +addFormatToken('Q', 0, 'Qo', 'quarter'); - // ALIASES +// ALIASES - addUnitAlias('quarter', 'Q'); +addUnitAlias('quarter', 'Q'); - // PRIORITY +// PRIORITY - addUnitPriority('quarter', 7); +addUnitPriority('quarter', 7); - // PARSING +// PARSING - addRegexToken('Q', match1); - addParseToken('Q', function (input, array) { - array[MONTH] = (toInt(input) - 1) * 3; - }); +addRegexToken('Q', match1); +addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; +}); - // MOMENTS +// MOMENTS - function getSetQuarter (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); - } +function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); +} - // FORMATTING +// FORMATTING - addFormatToken('D', ['DD', 2], 'Do', 'date'); +addFormatToken('D', ['DD', 2], 'Do', 'date'); - // ALIASES +// ALIASES - addUnitAlias('date', 'D'); +addUnitAlias('date', 'D'); - // PRIOROITY - addUnitPriority('date', 9); +// PRIOROITY +addUnitPriority('date', 9); - // PARSING +// PARSING - addRegexToken('D', match1to2); - addRegexToken('DD', match1to2, match2); - addRegexToken('Do', function (isStrict, locale) { - return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; - }); +addRegexToken('D', match1to2); +addRegexToken('DD', match1to2, match2); +addRegexToken('Do', function (isStrict, locale) { + return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; +}); - addParseToken(['D', 'DD'], DATE); - addParseToken('Do', function (input, array) { - array[DATE] = toInt(input.match(match1to2)[0], 10); - }); +addParseToken(['D', 'DD'], DATE); +addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0], 10); +}); - // MOMENTS +// MOMENTS - var getSetDayOfMonth = makeGetSet('Date', true); +var getSetDayOfMonth = makeGetSet('Date', true); - // FORMATTING +// FORMATTING - addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); +addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); - // ALIASES +// ALIASES - addUnitAlias('dayOfYear', 'DDD'); +addUnitAlias('dayOfYear', 'DDD'); - // PRIORITY - addUnitPriority('dayOfYear', 4); +// PRIORITY +addUnitPriority('dayOfYear', 4); - // PARSING +// PARSING - addRegexToken('DDD', match1to3); - addRegexToken('DDDD', match3); - addParseToken(['DDD', 'DDDD'], function (input, array, config) { - config._dayOfYear = toInt(input); - }); +addRegexToken('DDD', match1to3); +addRegexToken('DDDD', match3); +addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); +}); - // HELPERS +// HELPERS - // MOMENTS +// MOMENTS - function getSetDayOfYear (input) { - var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); - } +function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); +} - // FORMATTING +// FORMATTING - addFormatToken('m', ['mm', 2], 0, 'minute'); +addFormatToken('m', ['mm', 2], 0, 'minute'); - // ALIASES +// ALIASES - addUnitAlias('minute', 'm'); +addUnitAlias('minute', 'm'); - // PRIORITY +// PRIORITY - addUnitPriority('minute', 14); +addUnitPriority('minute', 14); - // PARSING +// PARSING - addRegexToken('m', match1to2); - addRegexToken('mm', match1to2, match2); - addParseToken(['m', 'mm'], MINUTE); +addRegexToken('m', match1to2); +addRegexToken('mm', match1to2, match2); +addParseToken(['m', 'mm'], MINUTE); - // MOMENTS +// MOMENTS - var getSetMinute = makeGetSet('Minutes', false); +var getSetMinute = makeGetSet('Minutes', false); - // FORMATTING +// FORMATTING - addFormatToken('s', ['ss', 2], 0, 'second'); +addFormatToken('s', ['ss', 2], 0, 'second'); - // ALIASES +// ALIASES - addUnitAlias('second', 's'); +addUnitAlias('second', 's'); - // PRIORITY +// PRIORITY - addUnitPriority('second', 15); +addUnitPriority('second', 15); - // PARSING +// PARSING - addRegexToken('s', match1to2); - addRegexToken('ss', match1to2, match2); - addParseToken(['s', 'ss'], SECOND); +addRegexToken('s', match1to2); +addRegexToken('ss', match1to2, match2); +addParseToken(['s', 'ss'], SECOND); - // MOMENTS +// MOMENTS - var getSetSecond = makeGetSet('Seconds', false); +var getSetSecond = makeGetSet('Seconds', false); - // FORMATTING +// FORMATTING - addFormatToken('S', 0, 0, function () { - return ~~(this.millisecond() / 100); - }); +addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); +}); - addFormatToken(0, ['SS', 2], 0, function () { - return ~~(this.millisecond() / 10); - }); +addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); +}); - addFormatToken(0, ['SSS', 3], 0, 'millisecond'); - addFormatToken(0, ['SSSS', 4], 0, function () { - return this.millisecond() * 10; - }); - addFormatToken(0, ['SSSSS', 5], 0, function () { - return this.millisecond() * 100; - }); - addFormatToken(0, ['SSSSSS', 6], 0, function () { - return this.millisecond() * 1000; - }); - addFormatToken(0, ['SSSSSSS', 7], 0, function () { - return this.millisecond() * 10000; - }); - addFormatToken(0, ['SSSSSSSS', 8], 0, function () { - return this.millisecond() * 100000; - }); - addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { - return this.millisecond() * 1000000; - }); +addFormatToken(0, ['SSS', 3], 0, 'millisecond'); +addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; +}); +addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; +}); +addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; +}); +addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; +}); +addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; +}); +addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; +}); - // ALIASES +// ALIASES - addUnitAlias('millisecond', 'ms'); +addUnitAlias('millisecond', 'ms'); - // PRIORITY +// PRIORITY - addUnitPriority('millisecond', 16); +addUnitPriority('millisecond', 16); - // PARSING +// PARSING - addRegexToken('S', match1to3, match1); - addRegexToken('SS', match1to3, match2); - addRegexToken('SSS', match1to3, match3); +addRegexToken('S', match1to3, match1); +addRegexToken('SS', match1to3, match2); +addRegexToken('SSS', match1to3, match3); - var token; - for (token = 'SSSS'; token.length <= 9; token += 'S') { - addRegexToken(token, matchUnsigned); - } +var token; +for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); +} - function parseMs(input, array) { - array[MILLISECOND] = toInt(('0.' + input) * 1000); - } +function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); +} - for (token = 'S'; token.length <= 9; token += 'S') { - addParseToken(token, parseMs); - } - // MOMENTS +for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); +} +// MOMENTS - var getSetMillisecond = makeGetSet('Milliseconds', false); +var getSetMillisecond = makeGetSet('Milliseconds', false); - // FORMATTING +// FORMATTING - addFormatToken('z', 0, 0, 'zoneAbbr'); - addFormatToken('zz', 0, 0, 'zoneName'); +addFormatToken('z', 0, 0, 'zoneAbbr'); +addFormatToken('zz', 0, 0, 'zoneName'); - // MOMENTS +// MOMENTS - function getZoneAbbr () { - return this._isUTC ? 'UTC' : ''; - } +function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; +} - function getZoneName () { - return this._isUTC ? 'Coordinated Universal Time' : ''; - } +function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; +} - var momentPrototype__proto = Moment.prototype; +var proto = Moment.prototype; - momentPrototype__proto.add = add_subtract__add; - momentPrototype__proto.calendar = moment_calendar__calendar; - momentPrototype__proto.clone = clone; - momentPrototype__proto.diff = diff; - momentPrototype__proto.endOf = endOf; - momentPrototype__proto.format = format; - momentPrototype__proto.from = from; - momentPrototype__proto.fromNow = fromNow; - momentPrototype__proto.to = to; - momentPrototype__proto.toNow = toNow; - momentPrototype__proto.get = stringGet; - momentPrototype__proto.invalidAt = invalidAt; - momentPrototype__proto.isAfter = isAfter; - momentPrototype__proto.isBefore = isBefore; - momentPrototype__proto.isBetween = isBetween; - momentPrototype__proto.isSame = isSame; - momentPrototype__proto.isSameOrAfter = isSameOrAfter; - momentPrototype__proto.isSameOrBefore = isSameOrBefore; - momentPrototype__proto.isValid = moment_valid__isValid; - momentPrototype__proto.lang = lang; - momentPrototype__proto.locale = locale; - momentPrototype__proto.localeData = localeData; - momentPrototype__proto.max = prototypeMax; - momentPrototype__proto.min = prototypeMin; - momentPrototype__proto.parsingFlags = parsingFlags; - momentPrototype__proto.set = stringSet; - momentPrototype__proto.startOf = startOf; - momentPrototype__proto.subtract = add_subtract__subtract; - momentPrototype__proto.toArray = toArray; - momentPrototype__proto.toObject = toObject; - momentPrototype__proto.toDate = toDate; - momentPrototype__proto.toISOString = moment_format__toISOString; - momentPrototype__proto.toJSON = toJSON; - momentPrototype__proto.toString = toString; - momentPrototype__proto.unix = unix; - momentPrototype__proto.valueOf = to_type__valueOf; - momentPrototype__proto.creationData = creationData; +proto.add = add; +proto.calendar = calendar$1; +proto.clone = clone; +proto.diff = diff; +proto.endOf = endOf; +proto.format = format; +proto.from = from; +proto.fromNow = fromNow; +proto.to = to; +proto.toNow = toNow; +proto.get = stringGet; +proto.invalidAt = invalidAt; +proto.isAfter = isAfter; +proto.isBefore = isBefore; +proto.isBetween = isBetween; +proto.isSame = isSame; +proto.isSameOrAfter = isSameOrAfter; +proto.isSameOrBefore = isSameOrBefore; +proto.isValid = isValid$1; +proto.lang = lang; +proto.locale = locale; +proto.localeData = localeData; +proto.max = prototypeMax; +proto.min = prototypeMin; +proto.parsingFlags = parsingFlags; +proto.set = stringSet; +proto.startOf = startOf; +proto.subtract = subtract; +proto.toArray = toArray; +proto.toObject = toObject; +proto.toDate = toDate; +proto.toISOString = toISOString; +proto.inspect = inspect; +proto.toJSON = toJSON; +proto.toString = toString; +proto.unix = unix; +proto.valueOf = valueOf; +proto.creationData = creationData; - // Year - momentPrototype__proto.year = getSetYear; - momentPrototype__proto.isLeapYear = getIsLeapYear; +// Year +proto.year = getSetYear; +proto.isLeapYear = getIsLeapYear; - // Week Year - momentPrototype__proto.weekYear = getSetWeekYear; - momentPrototype__proto.isoWeekYear = getSetISOWeekYear; +// Week Year +proto.weekYear = getSetWeekYear; +proto.isoWeekYear = getSetISOWeekYear; - // Quarter - momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; +// Quarter +proto.quarter = proto.quarters = getSetQuarter; - // Month - momentPrototype__proto.month = getSetMonth; - momentPrototype__proto.daysInMonth = getDaysInMonth; +// Month +proto.month = getSetMonth; +proto.daysInMonth = getDaysInMonth; - // Week - momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; - momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; - momentPrototype__proto.weeksInYear = getWeeksInYear; - momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; +// Week +proto.week = proto.weeks = getSetWeek; +proto.isoWeek = proto.isoWeeks = getSetISOWeek; +proto.weeksInYear = getWeeksInYear; +proto.isoWeeksInYear = getISOWeeksInYear; - // Day - momentPrototype__proto.date = getSetDayOfMonth; - momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; - momentPrototype__proto.weekday = getSetLocaleDayOfWeek; - momentPrototype__proto.isoWeekday = getSetISODayOfWeek; - momentPrototype__proto.dayOfYear = getSetDayOfYear; +// Day +proto.date = getSetDayOfMonth; +proto.day = proto.days = getSetDayOfWeek; +proto.weekday = getSetLocaleDayOfWeek; +proto.isoWeekday = getSetISODayOfWeek; +proto.dayOfYear = getSetDayOfYear; - // Hour - momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; +// Hour +proto.hour = proto.hours = getSetHour; - // Minute - momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; +// Minute +proto.minute = proto.minutes = getSetMinute; - // Second - momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; +// Second +proto.second = proto.seconds = getSetSecond; - // Millisecond - momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; +// Millisecond +proto.millisecond = proto.milliseconds = getSetMillisecond; - // Offset - momentPrototype__proto.utcOffset = getSetOffset; - momentPrototype__proto.utc = setOffsetToUTC; - momentPrototype__proto.local = setOffsetToLocal; - momentPrototype__proto.parseZone = setOffsetToParsedOffset; - momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; - momentPrototype__proto.isDST = isDaylightSavingTime; - momentPrototype__proto.isLocal = isLocal; - momentPrototype__proto.isUtcOffset = isUtcOffset; - momentPrototype__proto.isUtc = isUtc; - momentPrototype__proto.isUTC = isUtc; +// Offset +proto.utcOffset = getSetOffset; +proto.utc = setOffsetToUTC; +proto.local = setOffsetToLocal; +proto.parseZone = setOffsetToParsedOffset; +proto.hasAlignedHourOffset = hasAlignedHourOffset; +proto.isDST = isDaylightSavingTime; +proto.isLocal = isLocal; +proto.isUtcOffset = isUtcOffset; +proto.isUtc = isUtc; +proto.isUTC = isUtc; - // Timezone - momentPrototype__proto.zoneAbbr = getZoneAbbr; - momentPrototype__proto.zoneName = getZoneName; +// Timezone +proto.zoneAbbr = getZoneAbbr; +proto.zoneName = getZoneName; - // Deprecations - momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); - momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); - momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); - momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); - momentPrototype__proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); +// Deprecations +proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); +proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); +proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); +proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); +proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); - var momentPrototype = momentPrototype__proto; +function createUnix (input) { + return createLocal(input * 1000); +} - function moment__createUnix (input) { - return local__createLocal(input * 1000); - } +function createInZone () { + return createLocal.apply(null, arguments).parseZone(); +} - function moment__createInZone () { - return local__createLocal.apply(null, arguments).parseZone(); - } +function preParsePostFormat (string) { + return string; +} - function preParsePostFormat (string) { - return string; - } +var proto$1 = Locale.prototype; - var prototype__proto = Locale.prototype; +proto$1.calendar = calendar; +proto$1.longDateFormat = longDateFormat; +proto$1.invalidDate = invalidDate; +proto$1.ordinal = ordinal; +proto$1.preparse = preParsePostFormat; +proto$1.postformat = preParsePostFormat; +proto$1.relativeTime = relativeTime; +proto$1.pastFuture = pastFuture; +proto$1.set = set; - prototype__proto.calendar = locale_calendar__calendar; - prototype__proto.longDateFormat = longDateFormat; - prototype__proto.invalidDate = invalidDate; - prototype__proto.ordinal = ordinal; - prototype__proto.preparse = preParsePostFormat; - prototype__proto.postformat = preParsePostFormat; - prototype__proto.relativeTime = relative__relativeTime; - prototype__proto.pastFuture = pastFuture; - prototype__proto.set = locale_set__set; +// Month +proto$1.months = localeMonths; +proto$1.monthsShort = localeMonthsShort; +proto$1.monthsParse = localeMonthsParse; +proto$1.monthsRegex = monthsRegex; +proto$1.monthsShortRegex = monthsShortRegex; - // Month - prototype__proto.months = localeMonths; - prototype__proto.monthsShort = localeMonthsShort; - prototype__proto.monthsParse = localeMonthsParse; - prototype__proto.monthsRegex = monthsRegex; - prototype__proto.monthsShortRegex = monthsShortRegex; +// Week +proto$1.week = localeWeek; +proto$1.firstDayOfYear = localeFirstDayOfYear; +proto$1.firstDayOfWeek = localeFirstDayOfWeek; - // Week - prototype__proto.week = localeWeek; - prototype__proto.firstDayOfYear = localeFirstDayOfYear; - prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; +// Day of Week +proto$1.weekdays = localeWeekdays; +proto$1.weekdaysMin = localeWeekdaysMin; +proto$1.weekdaysShort = localeWeekdaysShort; +proto$1.weekdaysParse = localeWeekdaysParse; - // Day of Week - prototype__proto.weekdays = localeWeekdays; - prototype__proto.weekdaysMin = localeWeekdaysMin; - prototype__proto.weekdaysShort = localeWeekdaysShort; - prototype__proto.weekdaysParse = localeWeekdaysParse; +proto$1.weekdaysRegex = weekdaysRegex; +proto$1.weekdaysShortRegex = weekdaysShortRegex; +proto$1.weekdaysMinRegex = weekdaysMinRegex; - prototype__proto.weekdaysRegex = weekdaysRegex; - prototype__proto.weekdaysShortRegex = weekdaysShortRegex; - prototype__proto.weekdaysMinRegex = weekdaysMinRegex; +// Hours +proto$1.isPM = localeIsPM; +proto$1.meridiem = localeMeridiem; - // Hours - prototype__proto.isPM = localeIsPM; - prototype__proto.meridiem = localeMeridiem; +function get$1 (format, index, field, setter) { + var locale = getLocale(); + var utc = createUTC().set(setter, index); + return locale[field](utc, format); +} - function lists__get (format, index, field, setter) { - var locale = locale_locales__getLocale(); - var utc = create_utc__createUTC().set(setter, index); - return locale[field](utc, format); +function listMonthsImpl (format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; } - function listMonthsImpl (format, index, field) { - if (typeof format === 'number') { + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; +} + +// () +// (5) +// (fmt, 5) +// (fmt) +// (true) +// (true, 5) +// (true, fmt, 5) +// (true, fmt) +function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { index = format; format = undefined; } format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; - if (index != null) { - return lists__get(format, index, field, 'month'); + if (isNumber(format)) { + index = format; + format = undefined; } - var i; - var out = []; - for (i = 0; i < 12; i++) { - out[i] = lists__get(format, i, field, 'month'); - } - return out; + format = format || ''; } - // () - // (5) - // (fmt, 5) - // (fmt) - // (true) - // (true, 5) - // (true, fmt, 5) - // (true, fmt) - function listWeekdaysImpl (localeSorted, format, index, field) { - if (typeof localeSorted === 'boolean') { - if (typeof format === 'number') { - index = format; - format = undefined; - } + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0; - format = format || ''; - } else { - format = localeSorted; - index = format; - localeSorted = false; + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } - if (typeof format === 'number') { - index = format; - format = undefined; - } + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; +} - format = format || ''; - } +function listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); +} - var locale = locale_locales__getLocale(), - shift = localeSorted ? locale._week.dow : 0; +function listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); +} - if (index != null) { - return lists__get(format, (index + shift) % 7, field, 'day'); - } +function listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); +} - var i; - var out = []; - for (i = 0; i < 7; i++) { - out[i] = lists__get(format, (i + shift) % 7, field, 'day'); - } - return out; - } +function listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); +} - function lists__listMonths (format, index) { - return listMonthsImpl(format, index, 'months'); - } +function listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); +} - function lists__listMonthsShort (format, index) { - return listMonthsImpl(format, index, 'monthsShort'); +getSetGlobalLocale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; } +}); - function lists__listWeekdays (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); - } +// Side effect imports +hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); +hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); - function lists__listWeekdaysShort (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); - } +var mathAbs = Math.abs; - function lists__listWeekdaysMin (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); - } +function abs () { + var data = this._data; - locale_locales__getSetGlobalLocale('en', { - ordinalParse: /\d{1,2}(th|st|nd|rd)/, - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); - // Side effect imports - utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); - utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); - var mathAbs = Math.abs; + return this; +} - function duration_abs__abs () { - var data = this._data; +function addSubtract$1 (duration, input, value, direction) { + var other = createDuration(input, value); - this._milliseconds = mathAbs(this._milliseconds); - this._days = mathAbs(this._days); - this._months = mathAbs(this._months); + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; - data.milliseconds = mathAbs(data.milliseconds); - data.seconds = mathAbs(data.seconds); - data.minutes = mathAbs(data.minutes); - data.hours = mathAbs(data.hours); - data.months = mathAbs(data.months); - data.years = mathAbs(data.years); + return duration._bubble(); +} - return this; - } +// supports only 2.0-style add(1, 's') or add(duration) +function add$1 (input, value) { + return addSubtract$1(this, input, value, 1); +} - function duration_add_subtract__addSubtract (duration, input, value, direction) { - var other = create__createDuration(input, value); +// supports only 2.0-style subtract(1, 's') or subtract(duration) +function subtract$1 (input, value) { + return addSubtract$1(this, input, value, -1); +} - duration._milliseconds += direction * other._milliseconds; - duration._days += direction * other._days; - duration._months += direction * other._months; - - return duration._bubble(); +function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); } +} - // supports only 2.0-style add(1, 's') or add(duration) - function duration_add_subtract__add (input, value) { - return duration_add_subtract__addSubtract(this, input, value, 1); - } +function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; - // supports only 2.0-style subtract(1, 's') or subtract(duration) - function duration_add_subtract__subtract (input, value) { - return duration_add_subtract__addSubtract(this, input, value, -1); + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; } - function absCeil (number) { - if (number < 0) { - return Math.floor(number); - } else { - return Math.ceil(number); - } - } + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; - function bubble () { - var milliseconds = this._milliseconds; - var days = this._days; - var months = this._months; - var data = this._data; - var seconds, minutes, hours, years, monthsFromDays; + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; - // if we have a mix of positive and negative values, bubble down first - // check: https://github.com/moment/moment/issues/2166 - if (!((milliseconds >= 0 && days >= 0 && months >= 0) || - (milliseconds <= 0 && days <= 0 && months <= 0))) { - milliseconds += absCeil(monthsToDays(months) + days) * 864e5; - days = 0; - months = 0; - } + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; + hours = absFloor(minutes / 60); + data.hours = hours % 24; - seconds = absFloor(milliseconds / 1000); - data.seconds = seconds % 60; + days += absFloor(hours / 24); - minutes = absFloor(seconds / 60); - data.minutes = minutes % 60; + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); - hours = absFloor(minutes / 60); - data.hours = hours % 24; + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; - days += absFloor(hours / 24); + data.days = days; + data.months = months; + data.years = years; - // convert days to months - monthsFromDays = absFloor(daysToMonths(days)); - months += monthsFromDays; - days -= absCeil(monthsToDays(monthsFromDays)); + return this; +} - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; +function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; +} - data.days = days; - data.months = months; - data.years = years; +function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; +} - return this; - } +function as (units) { + var days; + var months; + var milliseconds = this._milliseconds; - function daysToMonths (days) { - // 400 years have 146097 days (taking into account leap year rules) - // 400 years have 12 months === 4800 - return days * 4800 / 146097; - } + units = normalizeUnits(units); - function monthsToDays (months) { - // the reverse of daysToMonths - return months * 146097 / 4800; - } - - function as (units) { - var days; - var months; - var milliseconds = this._milliseconds; - - units = normalizeUnits(units); - - if (units === 'month' || units === 'year') { - days = this._days + milliseconds / 864e5; - months = this._months + daysToMonths(days); - return units === 'month' ? months : months / 12; - } else { - // handle milliseconds separately because of floating point math errors (issue #1867) - days = this._days + Math.round(monthsToDays(this._months)); - switch (units) { - case 'week' : return days / 7 + milliseconds / 6048e5; - case 'day' : return days + milliseconds / 864e5; - case 'hour' : return days * 24 + milliseconds / 36e5; - case 'minute' : return days * 1440 + milliseconds / 6e4; - case 'second' : return days * 86400 + milliseconds / 1000; - // Math.floor prevents floating point math errors here - case 'millisecond': return Math.floor(days * 864e5) + milliseconds; - default: throw new Error('Unknown unit ' + units); - } + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); } } +} - // TODO: Use this.as('ms')? - function duration_as__valueOf () { - return ( - this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6 - ); - } +// TODO: Use this.as('ms')? +function valueOf$1 () { + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); +} - function makeAs (alias) { - return function () { - return this.as(alias); - }; - } +function makeAs (alias) { + return function () { + return this.as(alias); + }; +} - var asMilliseconds = makeAs('ms'); - var asSeconds = makeAs('s'); - var asMinutes = makeAs('m'); - var asHours = makeAs('h'); - var asDays = makeAs('d'); - var asWeeks = makeAs('w'); - var asMonths = makeAs('M'); - var asYears = makeAs('y'); +var asMilliseconds = makeAs('ms'); +var asSeconds = makeAs('s'); +var asMinutes = makeAs('m'); +var asHours = makeAs('h'); +var asDays = makeAs('d'); +var asWeeks = makeAs('w'); +var asMonths = makeAs('M'); +var asYears = makeAs('y'); - function duration_get__get (units) { - units = normalizeUnits(units); - return this[units + 's'](); - } +function get$2 (units) { + units = normalizeUnits(units); + return this[units + 's'](); +} - function makeGetter(name) { - return function () { - return this._data[name]; - }; - } +function makeGetter(name) { + return function () { + return this._data[name]; + }; +} - var milliseconds = makeGetter('milliseconds'); - var seconds = makeGetter('seconds'); - var minutes = makeGetter('minutes'); - var hours = makeGetter('hours'); - var days = makeGetter('days'); - var months = makeGetter('months'); - var years = makeGetter('years'); +var milliseconds = makeGetter('milliseconds'); +var seconds = makeGetter('seconds'); +var minutes = makeGetter('minutes'); +var hours = makeGetter('hours'); +var days = makeGetter('days'); +var months = makeGetter('months'); +var years = makeGetter('years'); - function weeks () { - return absFloor(this.days() / 7); - } +function weeks () { + return absFloor(this.days() / 7); +} - var round = Math.round; - var thresholds = { - s: 45, // seconds to minute - m: 45, // minutes to hour - h: 22, // hours to day - d: 26, // days to month - M: 11 // months to year - }; +var round = Math.round; +var thresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year +}; - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } +// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize +function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); +} - function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { - var duration = create__createDuration(posNegDuration).abs(); - var seconds = round(duration.as('s')); - var minutes = round(duration.as('m')); - var hours = round(duration.as('h')); - var days = round(duration.as('d')); - var months = round(duration.as('M')); - var years = round(duration.as('y')); +function relativeTime$1 (posNegDuration, withoutSuffix, locale) { + var duration = createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); - var a = seconds < thresholds.s && ['s', seconds] || - minutes <= 1 && ['m'] || - minutes < thresholds.m && ['mm', minutes] || - hours <= 1 && ['h'] || - hours < thresholds.h && ['hh', hours] || - days <= 1 && ['d'] || - days < thresholds.d && ['dd', days] || - months <= 1 && ['M'] || - months < thresholds.M && ['MM', months] || - years <= 1 && ['y'] || ['yy', years]; + var a = seconds < thresholds.s && ['s', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; - a[2] = withoutSuffix; - a[3] = +posNegDuration > 0; - a[4] = locale; - return substituteTimeAgo.apply(null, a); + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); +} + +// This function allows you to set the rounding function for relative time strings +function getSetRelativeTimeRounding (roundingFunction) { + if (roundingFunction === undefined) { + return round; } + if (typeof(roundingFunction) === 'function') { + round = roundingFunction; + return true; + } + return false; +} - // This function allows you to set the rounding function for relative time strings - function duration_humanize__getSetRelativeTimeRounding (roundingFunction) { - if (roundingFunction === undefined) { - return round; - } - if (typeof(roundingFunction) === 'function') { - round = roundingFunction; - return true; - } +// This function allows you to set a threshold for relative time strings +function getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { return false; } - - // This function allows you to set a threshold for relative time strings - function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { - if (thresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return thresholds[threshold]; - } - thresholds[threshold] = limit; - return true; + if (limit === undefined) { + return thresholds[threshold]; } + thresholds[threshold] = limit; + return true; +} - function humanize (withSuffix) { - var locale = this.localeData(); - var output = duration_humanize__relativeTime(this, !withSuffix, locale); +function humanize (withSuffix) { + var locale = this.localeData(); + var output = relativeTime$1(this, !withSuffix, locale); - if (withSuffix) { - output = locale.pastFuture(+this, output); - } - - return locale.postformat(output); + if (withSuffix) { + output = locale.pastFuture(+this, output); } - var iso_string__abs = Math.abs; + return locale.postformat(output); +} - function iso_string__toISOString() { - // for ISO strings we do not use the normal bubbling rules: - // * milliseconds bubble up until they become hours - // * days do not bubble at all - // * months bubble up until they become years - // This is because there is no context-free conversion between hours and days - // (think of clock changes) - // and also not between days and months (28-31 days per month) - var seconds = iso_string__abs(this._milliseconds) / 1000; - var days = iso_string__abs(this._days); - var months = iso_string__abs(this._months); - var minutes, hours, years; +var abs$1 = Math.abs; - // 3600 seconds -> 60 minutes -> 1 hour - minutes = absFloor(seconds / 60); - hours = absFloor(minutes / 60); - seconds %= 60; - minutes %= 60; +function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + var seconds = abs$1(this._milliseconds) / 1000; + var days = abs$1(this._days); + var months = abs$1(this._months); + var minutes, hours, years; - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var Y = years; - var M = months; - var D = days; - var h = hours; - var m = minutes; - var s = seconds; - var total = this.asSeconds(); - if (!total) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds; + var total = this.asSeconds(); - return (total < 0 ? '-' : '') + - 'P' + - (Y ? Y + 'Y' : '') + - (M ? M + 'M' : '') + - (D ? D + 'D' : '') + - ((h || m || s) ? 'T' : '') + - (h ? h + 'H' : '') + - (m ? m + 'M' : '') + - (s ? s + 'S' : ''); + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; } - var duration_prototype__proto = Duration.prototype; + return (total < 0 ? '-' : '') + + 'P' + + (Y ? Y + 'Y' : '') + + (M ? M + 'M' : '') + + (D ? D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? h + 'H' : '') + + (m ? m + 'M' : '') + + (s ? s + 'S' : ''); +} - duration_prototype__proto.abs = duration_abs__abs; - duration_prototype__proto.add = duration_add_subtract__add; - duration_prototype__proto.subtract = duration_add_subtract__subtract; - duration_prototype__proto.as = as; - duration_prototype__proto.asMilliseconds = asMilliseconds; - duration_prototype__proto.asSeconds = asSeconds; - duration_prototype__proto.asMinutes = asMinutes; - duration_prototype__proto.asHours = asHours; - duration_prototype__proto.asDays = asDays; - duration_prototype__proto.asWeeks = asWeeks; - duration_prototype__proto.asMonths = asMonths; - duration_prototype__proto.asYears = asYears; - duration_prototype__proto.valueOf = duration_as__valueOf; - duration_prototype__proto._bubble = bubble; - duration_prototype__proto.get = duration_get__get; - duration_prototype__proto.milliseconds = milliseconds; - duration_prototype__proto.seconds = seconds; - duration_prototype__proto.minutes = minutes; - duration_prototype__proto.hours = hours; - duration_prototype__proto.days = days; - duration_prototype__proto.weeks = weeks; - duration_prototype__proto.months = months; - duration_prototype__proto.years = years; - duration_prototype__proto.humanize = humanize; - duration_prototype__proto.toISOString = iso_string__toISOString; - duration_prototype__proto.toString = iso_string__toISOString; - duration_prototype__proto.toJSON = iso_string__toISOString; - duration_prototype__proto.locale = locale; - duration_prototype__proto.localeData = localeData; +var proto$2 = Duration.prototype; - // Deprecations - duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); - duration_prototype__proto.lang = lang; +proto$2.abs = abs; +proto$2.add = add$1; +proto$2.subtract = subtract$1; +proto$2.as = as; +proto$2.asMilliseconds = asMilliseconds; +proto$2.asSeconds = asSeconds; +proto$2.asMinutes = asMinutes; +proto$2.asHours = asHours; +proto$2.asDays = asDays; +proto$2.asWeeks = asWeeks; +proto$2.asMonths = asMonths; +proto$2.asYears = asYears; +proto$2.valueOf = valueOf$1; +proto$2._bubble = bubble; +proto$2.get = get$2; +proto$2.milliseconds = milliseconds; +proto$2.seconds = seconds; +proto$2.minutes = minutes; +proto$2.hours = hours; +proto$2.days = days; +proto$2.weeks = weeks; +proto$2.months = months; +proto$2.years = years; +proto$2.humanize = humanize; +proto$2.toISOString = toISOString$1; +proto$2.toString = toISOString$1; +proto$2.toJSON = toISOString$1; +proto$2.locale = locale; +proto$2.localeData = localeData; - // Side effect imports +// Deprecations +proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); +proto$2.lang = lang; - // FORMATTING +// Side effect imports - addFormatToken('X', 0, 0, 'unix'); - addFormatToken('x', 0, 0, 'valueOf'); +// FORMATTING - // PARSING +addFormatToken('X', 0, 0, 'unix'); +addFormatToken('x', 0, 0, 'valueOf'); - addRegexToken('x', matchSigned); - addRegexToken('X', matchTimestamp); - addParseToken('X', function (input, array, config) { - config._d = new Date(parseFloat(input, 10) * 1000); - }); - addParseToken('x', function (input, array, config) { - config._d = new Date(toInt(input)); - }); +// PARSING - // Side effect imports +addRegexToken('x', matchSigned); +addRegexToken('X', matchTimestamp); +addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); +}); +addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); +}); +// Side effect imports - utils_hooks__hooks.version = '2.14.1'; - setHookCallback(local__createLocal); +hooks.version = '2.17.1'; - utils_hooks__hooks.fn = momentPrototype; - utils_hooks__hooks.min = min; - utils_hooks__hooks.max = max; - utils_hooks__hooks.now = now; - utils_hooks__hooks.utc = create_utc__createUTC; - utils_hooks__hooks.unix = moment__createUnix; - utils_hooks__hooks.months = lists__listMonths; - utils_hooks__hooks.isDate = isDate; - utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; - utils_hooks__hooks.invalid = valid__createInvalid; - utils_hooks__hooks.duration = create__createDuration; - utils_hooks__hooks.isMoment = isMoment; - utils_hooks__hooks.weekdays = lists__listWeekdays; - utils_hooks__hooks.parseZone = moment__createInZone; - utils_hooks__hooks.localeData = locale_locales__getLocale; - utils_hooks__hooks.isDuration = isDuration; - utils_hooks__hooks.monthsShort = lists__listMonthsShort; - utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; - utils_hooks__hooks.defineLocale = defineLocale; - utils_hooks__hooks.updateLocale = updateLocale; - utils_hooks__hooks.locales = locale_locales__listLocales; - utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; - utils_hooks__hooks.normalizeUnits = normalizeUnits; - utils_hooks__hooks.relativeTimeRounding = duration_humanize__getSetRelativeTimeRounding; - utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; - utils_hooks__hooks.calendarFormat = getCalendarFormat; - utils_hooks__hooks.prototype = momentPrototype; +setHookCallback(createLocal); - var _moment = utils_hooks__hooks; +hooks.fn = proto; +hooks.min = min; +hooks.max = max; +hooks.now = now; +hooks.utc = createUTC; +hooks.unix = createUnix; +hooks.months = listMonths; +hooks.isDate = isDate; +hooks.locale = getSetGlobalLocale; +hooks.invalid = createInvalid; +hooks.duration = createDuration; +hooks.isMoment = isMoment; +hooks.weekdays = listWeekdays; +hooks.parseZone = createInZone; +hooks.localeData = getLocale; +hooks.isDuration = isDuration; +hooks.monthsShort = listMonthsShort; +hooks.weekdaysMin = listWeekdaysMin; +hooks.defineLocale = defineLocale; +hooks.updateLocale = updateLocale; +hooks.locales = listLocales; +hooks.weekdaysShort = listWeekdaysShort; +hooks.normalizeUnits = normalizeUnits; +hooks.relativeTimeRounding = getSetRelativeTimeRounding; +hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; +hooks.calendarFormat = getCalendarFormat; +hooks.prototype = proto; - return _moment; +return hooks; -})); +}))); + },{}],7:[function(require,module,exports){ /** * @namespace Chart */ -var Chart = require(27)(); +var Chart = require(28)(); require(26)(Chart); +require(42)(Chart); require(22)(Chart); +require(31)(Chart); require(25)(Chart); require(21)(Chart); require(23)(Chart); require(24)(Chart); -require(28)(Chart); -require(32)(Chart); -require(30)(Chart); -require(31)(Chart); -require(33)(Chart); require(29)(Chart); +require(33)(Chart); require(34)(Chart); - +require(32)(Chart); require(35)(Chart); +require(30)(Chart); +require(27)(Chart); require(36)(Chart); + require(37)(Chart); require(38)(Chart); - -require(41)(Chart); require(39)(Chart); require(40)(Chart); -require(42)(Chart); + +require(45)(Chart); require(43)(Chart); require(44)(Chart); +require(46)(Chart); +require(47)(Chart); +require(48)(Chart); // Controllers must be loaded after elements // See Chart.core.datasetController.dataElementType require(15)(Chart); require(16)(Chart); @@ -5904,103 +6014,107 @@ require(13)(Chart); require(14)(Chart); window.Chart = module.exports = Chart; -},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"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,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"8":8,"9":9}],8:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { Chart.Bar = function(context, config) { config.type = 'bar'; return new Chart(context, config); }; }; + },{}],9:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { Chart.Bubble = function(context, config) { config.type = 'bubble'; return new Chart(context, config); }; }; + },{}],10:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { Chart.Doughnut = function(context, config) { config.type = 'doughnut'; return new Chart(context, config); }; }; + },{}],11:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { Chart.Line = function(context, config) { config.type = 'line'; return new Chart(context, config); }; }; + },{}],12:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { Chart.PolarArea = function(context, config) { config.type = 'polarArea'; return new Chart(context, config); }; }; + },{}],13:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { - + Chart.Radar = function(context, config) { - config.options = Chart.helpers.configMerge({ aspectRatio: 1 }, config.options); config.type = 'radar'; return new Chart(context, config); }; }; },{}],14:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { 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 + 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" + type: 'linear', + position: 'left', + id: 'y-axis-1' }] }, tooltips: { callbacks: { @@ -6025,25 +6139,26 @@ config.type = 'scatter'; return new Chart(context, config); }; }; + },{}],15:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.bar = { hover: { - mode: "label" + mode: 'label' }, scales: { xAxes: [{ - type: "category", + type: 'category', // Specific to Bar Controller categoryPercentage: 0.8, barPercentage: 0.9, @@ -6051,11 +6166,11 @@ gridLines: { offsetGridLines: true } }], yAxes: [{ - type: "linear" + type: 'linear' }] } }; Chart.controllers.bar = Chart.DatasetController.extend({ @@ -6063,25 +6178,37 @@ dataElementType: Chart.elements.Rectangle, initialize: function(chart, datasetIndex) { Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + + meta.stack = dataset.stack; // Use this to indicate that this is a bar dataset. - this.getMeta().bar = true; + meta.bar = true; }, - // Get the number of datasets that display bars. We use this to correctly calculate the bar width - getBarCount: function() { + // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible + getStackCount: function() { var me = this; - var barCount = 0; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + + var stacks = []; helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { - var meta = me.chart.getDatasetMeta(datasetIndex); - if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) { - ++barCount; + var dsMeta = me.chart.getDatasetMeta(datasetIndex); + if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) && + (yScale.options.stacked === false || + (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); } }, me); - return barCount; + + return stacks.length; }, update: function(reset) { var me = this; helpers.each(me.getMeta().data, function(rectangle, index) { @@ -6097,162 +6224,155 @@ 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); // The index argument for compatible + rectangle._model = { + x: me.calculateBarX(index, me.index, ruler), + y: reset ? scaleBase : me.calculateBarY(index, me.index), - // Tooltip - label: me.chart.data.labels[index], - datasetLabel: dataset.label, + // 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) - } - }); + // Appearance + horizontal: false, + base: reset ? scaleBase : me.calculateBarBase(me.index, index), + width: me.calculateBarWidth(ruler), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), + borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, + borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) + }; + rectangle.pivot(); }, calculateBarBase: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); - var base = 0; + var base = yScale.getBaseValue(); + var original = base; - if (yScale.options.stacked) { + if ((yScale.options.stacked === true) || + (yScale.options.stacked === undefined && meta.stack !== undefined)) { var chart = me.chart; var datasets = chart.data.datasets; var value = Number(datasets[datasetIndex].data[index]); for (var i = 0; i < datasetIndex; i++) { var currentDs = datasets[i]; var currentDsMeta = chart.getDatasetMeta(i); - if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { + if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i) && + meta.stack === currentDsMeta.stack) { var currentVal = Number(currentDs.data[index]); - base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); + base += value < 0 ? Math.min(currentVal, original) : Math.max(currentVal, original); } } return yScale.getPixelForValue(base); } return yScale.getBasePixel(); }, - getRuler: function(index) { + getRuler: function() { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); - var datasetCount = me.getBarCount(); + var stackCount = me.getStackCount(); - var tickWidth; - - if (xScale.options.type === 'category') { - tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index); - } else { - // Average width - tickWidth = xScale.width / xScale.ticks.length; - } + var 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 fullBarWidth = categoryWidth / stackCount; - if (xScale.ticks.length !== me.chart.data.labels.length) { - var perc = xScale.ticks.length / me.chart.data.labels.length; - fullBarWidth = fullBarWidth * perc; - } - var barWidth = fullBarWidth * xScale.options.barPercentage; var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); return { - datasetCount: datasetCount, + stackCount: stackCount, tickWidth: tickWidth, categoryWidth: categoryWidth, categorySpacing: categorySpacing, fullBarWidth: fullBarWidth, barWidth: barWidth, barSpacing: barSpacing }; }, - calculateBarWidth: function(index) { - var xScale = this.getScaleForId(this.getMeta().xAxisID); + calculateBarWidth: function(ruler) { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); if (xScale.options.barThickness) { return xScale.options.barThickness; } - var ruler = this.getRuler(index); - return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth; + return ruler.barWidth; }, - // 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; + // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible + getStackIndex: function(datasetIndex) { + var me = this; + var meta = me.chart.getDatasetMeta(datasetIndex); + var yScale = me.getScaleForId(meta.yAxisID); + var dsMeta, j; + var stacks = [meta.stack]; for (j = 0; j < datasetIndex; ++j) { - meta = this.chart.getDatasetMeta(j); - if (meta.bar && this.chart.isDatasetVisible(j)) { - ++barIndex; + dsMeta = this.chart.getDatasetMeta(j); + if (dsMeta.bar && this.chart.isDatasetVisible(j) && + (yScale.options.stacked === false || + (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); } } - return barIndex; + return stacks.length - 1; }, - calculateBarX: function(index, datasetIndex) { + calculateBarX: function(index, datasetIndex, ruler) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); - var barIndex = me.getBarIndex(datasetIndex); - - var ruler = me.getRuler(index); + var stackIndex = me.getStackIndex(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; - } - return leftTick + (ruler.barWidth / 2) + ruler.categorySpacing + - (ruler.barWidth * barIndex) + + (ruler.barWidth * stackIndex) + (ruler.barSpacing / 2) + - (ruler.barSpacing * barIndex); + (ruler.barSpacing * stackIndex); }, calculateBarY: function(index, datasetIndex) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var value = Number(me.getDataset().data[index]); - if (yScale.options.stacked) { + if (yScale.options.stacked || + (yScale.options.stacked === undefined && meta.stack !== undefined)) { + var base = yScale.getBaseValue(); + var sumPos = base, + sumNeg = base; - 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)) { + if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i) && + meta.stack === dsMeta.stack) { var stackedVal = Number(ds.data[index]); if (stackedVal < 0) { sumNeg += stackedVal || 0; } else { sumPos += stackedVal || 0; @@ -6260,27 +6380,32 @@ } } if (value < 0) { return yScale.getPixelForValue(sumNeg + value); - } else { - return yScale.getPixelForValue(sumPos + value); } + return yScale.getPixelForValue(sumPos + 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]; + var metaData = me.getMeta().data; + var dataset = me.getDataset(); + var i, len; + + Chart.canvasHelpers.clipArea(me.chart.chart.ctx, me.chart.chartArea); + for (i = 0, len = metaData.length; i < len; ++i) { + var d = dataset.data[i]; if (d !== null && d !== undefined && !isNaN(d)) { - rectangle.transition(easingDecimal).draw(); + metaData[i].transition(easingDecimal).draw(); } - }, me); + } + Chart.canvasHelpers.unclipArea(me.chart.chart.ctx); }, setHoverStyle: function(rectangle) { var dataset = this.chart.data.datasets[rectangle._datasetIndex]; var index = rectangle._index; @@ -6309,21 +6434,21 @@ // 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" + mode: 'label' }, scales: { xAxes: [{ - type: "linear", - position: "bottom" + type: 'linear', + position: 'bottom' }], yAxes: [{ - position: "left", - type: "category", + position: 'left', + type: 'category', // Specific to Horizontal Bar Controller categoryPercentage: 0.8, barPercentage: 0.9, @@ -6354,213 +6479,177 @@ return title; }, label: function(tooltipItem, data) { var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; - return datasetLabel + ': ' + tooltipItem.xLabel; + return datasetLabel + ': ' + tooltipItem.xLabel; } } } }; Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ + + // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible + getStackCount: function() { + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); + + var stacks = []; + helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { + var dsMeta = me.chart.getDatasetMeta(datasetIndex); + if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) && + (xScale.options.stacked === false || + (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); + } + }, me); + + return stacks.length; + }, + updateElement: function(rectangle, index, reset) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var yScale = me.getScaleForId(meta.yAxisID); var scaleBase = xScale.getBasePixel(); var custom = rectangle.custom || {}; var dataset = me.getDataset(); var rectangleElementOptions = me.chart.options.elements.rectangle; - 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: reset ? scaleBase : me.calculateBarX(index, me.index), - y: me.calculateBarY(index, me.index), + var ruler = me.getRuler(index); // The index argument for compatible + rectangle._model = { + x: reset ? scaleBase : me.calculateBarX(index, me.index), + y: me.calculateBarY(index, me.index, ruler), - // Tooltip - label: me.chart.data.labels[index], - datasetLabel: dataset.label, + // Tooltip + label: me.chart.data.labels[index], + datasetLabel: dataset.label, - // 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) - }, + // Appearance + horizontal: true, + base: reset ? scaleBase : me.calculateBarBase(me.index, index), + height: me.calculateBarHeight(ruler), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), + borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, + borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) + }; - 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; - - // 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.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] - ]; - - // 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]; - } - - // 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(); - } - }, - - inRange: function (mouseX, mouseY) { - var vm = this._view; - var inRange = false; - - 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); - } - } - - return inRange; - } - }); - rectangle.pivot(); }, - calculateBarBase: function (datasetIndex, index) { + calculateBarBase: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); - var base = 0; + var base = xScale.getBaseValue(); + var originalBase = base; - if (xScale.options.stacked) { + if (xScale.options.stacked || + (xScale.options.stacked === undefined && meta.stack !== undefined)) { var chart = me.chart; var datasets = chart.data.datasets; var value = Number(datasets[datasetIndex].data[index]); for (var i = 0; i < datasetIndex; i++) { var currentDs = datasets[i]; var currentDsMeta = chart.getDatasetMeta(i); - if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i)) { + if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i) && + meta.stack === currentDsMeta.stack) { var currentVal = Number(currentDs.data[index]); - base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); + base += value < 0 ? Math.min(currentVal, originalBase) : Math.max(currentVal, originalBase); } } return xScale.getPixelForValue(base); } return xScale.getBasePixel(); }, - getRuler: function (index) { + getRuler: function() { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); - var datasetCount = me.getBarCount(); + var stackCount = me.getStackCount(); - 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 tickHeight = yScale.height / yScale.ticks.length; var categoryHeight = tickHeight * yScale.options.categoryPercentage; var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2; - var fullBarHeight = categoryHeight / datasetCount; + var fullBarHeight = categoryHeight / stackCount; - if (yScale.ticks.length !== me.chart.data.labels.length) { - var perc = yScale.ticks.length / me.chart.data.labels.length; - fullBarHeight = fullBarHeight * perc; - } - var barHeight = fullBarHeight * yScale.options.barPercentage; var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); return { - datasetCount: datasetCount, + stackCount: stackCount, tickHeight: tickHeight, categoryHeight: categoryHeight, categorySpacing: categorySpacing, fullBarHeight: fullBarHeight, barHeight: barHeight, barSpacing: barSpacing }; }, - calculateBarHeight: function (index) { + calculateBarHeight: function(ruler) { var me = this; - var yScale = me.getScaleForId(me.getMeta().yAxisID); + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); if (yScale.options.barThickness) { return yScale.options.barThickness; } - var ruler = me.getRuler(index); - return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight; + return ruler.barHeight; }, - calculateBarX: function (index, datasetIndex) { + // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible + getStackIndex: function(datasetIndex) { var me = this; + var meta = me.chart.getDatasetMeta(datasetIndex); + var xScale = me.getScaleForId(meta.xAxisID); + var dsMeta, j; + var stacks = [meta.stack]; + + for (j = 0; j < datasetIndex; ++j) { + dsMeta = this.chart.getDatasetMeta(j); + if (dsMeta.bar && this.chart.isDatasetVisible(j) && + (xScale.options.stacked === false || + (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || + (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { + stacks.push(dsMeta.stack); + } + } + + return stacks.length - 1; + }, + + calculateBarX: function(index, datasetIndex) { + var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var value = Number(me.getDataset().data[index]); - if (xScale.options.stacked) { + if (xScale.options.stacked || + (xScale.options.stacked === undefined && meta.stack !== undefined)) { + var base = xScale.getBaseValue(); + var sumPos = base, + sumNeg = base; - 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.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) { + if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i) && + meta.stack === dsMeta.stack) { var stackedVal = Number(ds.data[index]); if (stackedVal < 0) { sumNeg += stackedVal || 0; } else { sumPos += stackedVal || 0; @@ -6568,64 +6657,57 @@ } } if (value < 0) { return xScale.getPixelForValue(sumNeg + value); - } else { - return xScale.getPixelForValue(sumPos + value); } + return xScale.getPixelForValue(sumPos + value); } return xScale.getPixelForValue(value); }, - calculateBarY: function (index, datasetIndex) { + calculateBarY: function(index, datasetIndex, ruler) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); - var barIndex = me.getBarIndex(datasetIndex); - - var ruler = me.getRuler(index); + var stackIndex = me.getStackIndex(datasetIndex); var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0; - if (yScale.options.stacked) { - return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing; - } - return topTick + (ruler.barHeight / 2) + ruler.categorySpacing + - (ruler.barHeight * barIndex) + + (ruler.barHeight * stackIndex) + (ruler.barSpacing / 2) + - (ruler.barSpacing * barIndex); + (ruler.barSpacing * stackIndex); } }); }; },{}],16:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.bubble = { hover: { - mode: "single" + mode: 'single' }, scales: { xAxes: [{ - type: "linear", // bubble should probably use a linear scale by default - position: "bottom", - id: "x-axis-0" // need an ID so datasets can reference the scale + 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" + type: 'linear', + position: 'left', + id: 'y-axis-0' }] }, tooltips: { callbacks: { @@ -6634,11 +6716,11 @@ 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 + ')'; + return datasetLabel + ': (' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ', ' + dataPoint.r + ')'; } } } }; @@ -6725,22 +6807,22 @@ } }); }; },{}],17:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers, defaults = Chart.defaults; defaults.doughnut = { animation: { - //Boolean - Whether we animate the rotation of the Doughnut + // Boolean - Whether we animate the rotation of the Doughnut animateRotate: true, - //Boolean - Whether we animate scaling the Doughnut from the centre + // Boolean - Whether we animate scaling the Doughnut from the centre animateScale: false }, aspectRatio: 1, hover: { mode: 'single' @@ -6762,11 +6844,11 @@ text.push('</li>'); } } text.push('</ul>'); - return text.join(""); + return text.join(''); }, legend: { labels: { generateLabels: function(chart) { var data = chart.data; @@ -6791,47 +6873,61 @@ // Extra data used for toggling the correct item index: i }; }); - } else { - return []; } + return []; } }, onClick: function(e, legendItem) { var index = legendItem.index; var chart = this.chart; var i, ilen, meta; for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { meta = chart.getDatasetMeta(i); - meta.data[index].hidden = !meta.data[index].hidden; + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } } chart.update(); } }, - //The percentage of the chart that we cut out of the middle. + // The percentage of the chart that we cut out of the middle. cutoutPercentage: 50, - //The rotation of the chart, where the first data arc begins. + // The rotation of the chart, where the first data arc begins. rotation: Math.PI * -0.5, - //The total circumference of the chart. + // The total circumference of the chart. circumference: Math.PI * 2.0, // Need to override these to give a nice default tooltips: { callbacks: { title: function() { return ''; }, label: function(tooltipItem, data) { - return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; } } } }; @@ -6893,22 +6989,22 @@ var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; minSize = Math.min(availableWidth / size.width, availableHeight / size.height); offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; } - chart.borderWidth = me.getMaxBorderWidth(meta.data); + chart.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.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); chart.offsetX = offset.x * chart.outerRadius; chart.offsetY = offset.y * chart.outerRadius; meta.total = me.calculateTotal(); me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); - me.innerRadius = me.outerRadius - chart.radiusLength; + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); helpers.each(meta.data, function(arc, index) { me.updateElement(arc, index, reset); }); }, @@ -6980,68 +7076,67 @@ if (!isNaN(value) && !element.hidden) { total += Math.abs(value); } }); - /*if (total === 0) { + /* if (total === 0) { total = NaN; }*/ return total; }, calculateCircumference: function(value) { var total = this.getMeta().total; if (total > 0 && !isNaN(value)) { return (Math.PI * 2.0) * (value / total); - } else { - return 0; } + return 0; }, - - //gets the max border or hover width to properly scale pie charts - getMaxBorderWidth: function (elements) { - var max = 0, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(elements) { + var max = 0, index = this.index, length = elements.length, borderWidth, hoverWidth; - for (var i = 0; i < length; i++) { - borderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0; - hoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; - - max = borderWidth > max ? borderWidth : max; - max = hoverWidth > max ? hoverWidth : max; - } - return max; - } + for (var i = 0; i < length; i++) { + borderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0; + hoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + return max; + } }); }; },{}],18:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.line = { showLines: true, spanGaps: false, hover: { - mode: "label" + mode: 'label' }, scales: { xAxes: [{ - type: "category", + type: 'category', id: 'x-axis-0' }], yAxes: [{ - type: "linear", + type: 'linear', id: 'y-axis-0' }] } }; @@ -7053,23 +7148,10 @@ datasetElementType: Chart.elements.Line, dataElementType: Chart.elements.Point, - addElementAndReset: function(index) { - var me = this; - var options = me.chart.options; - var meta = me.getMeta(); - - Chart.DatasetController.prototype.addElementAndReset.call(me, index); - - // Make sure bezier control points are updated - if (lineEnabled(me.getDataset(), options) && meta.dataset._model.tension !== 0) { - me.updateBezierControlPoints(); - } - }, - update: function(reset) { var me = this; var meta = me.getMeta(); var line = meta.dataset; var points = meta.data || []; @@ -7097,11 +7179,11 @@ // 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 + // 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), @@ -7171,15 +7253,15 @@ getPointBorderWidth: function(point, index) { var borderWidth = this.chart.options.elements.point.borderWidth; var dataset = this.getDataset(); var custom = point.custom || {}; - if (custom.borderWidth) { + if (!isNaN(custom.borderWidth)) { borderWidth = custom.borderWidth; - } else if (dataset.pointBorderWidth) { + } else if (!isNaN(dataset.pointBorderWidth)) { borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); - } else if (dataset.borderWidth) { + } else if (!isNaN(dataset.borderWidth)) { borderWidth = dataset.borderWidth; } return borderWidth; }, @@ -7257,36 +7339,38 @@ } var rightValue = Number(yScale.getRightValue(value)); if (rightValue < 0) { return yScale.getPixelForValue(sumNeg + rightValue); - } else { - return yScale.getPixelForValue(sumPos + rightValue); } + return yScale.getPixelForValue(sumPos + rightValue); } return yScale.getPixelForValue(value); }, updateBezierControlPoints: function() { var me = this; var meta = me.getMeta(); var area = me.chart.chartArea; - - // Only consider points that are drawn in case the spanGaps option is used var points = (meta.data || []); - if (meta.dataset._model.spanGaps) points = points.filter(function(pt) { return !pt._model.skip; }); var i, ilen, point, model, controlPoints; + // 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; + }); + } + function capControlPoint(pt, min, max) { return Math.max(Math.min(pt, max), min); } - if (meta.dataset._model.cubicInterpolationMode == 'monotone') { + if (meta.dataset._model.cubicInterpolationMode === 'monotone') { helpers.splineCurveMonotone(points); - } - else { + } else { for (i = 0, ilen = points.length; i < ilen; ++i) { point = points[i]; model = point._model; controlPoints = helpers.splineCurve( helpers.previousItem(points, i)._model, @@ -7308,11 +7392,10 @@ 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); } } - }, draw: function(ease) { var me = this; var meta = me.getMeta(); @@ -7323,18 +7406,20 @@ // Transition Point Locations for (i=0, ilen=points.length; i<ilen; ++i) { points[i].transition(easingDecimal); } + Chart.canvasHelpers.clipArea(me.chart.chart.ctx, me.chart.chartArea); // Transition and Draw the line if (lineEnabled(me.getDataset(), me.chart.options)) { meta.dataset.transition(easingDecimal).draw(); } + Chart.canvasHelpers.unclipArea(me.chart.chart.ctx); // Draw the points for (i=0, ilen=points.length; i<ilen; ++i) { - points[i].draw(); + points[i].draw(me.chart.chartArea); } }, setHoverStyle: function(point) { // Point @@ -7368,27 +7453,27 @@ } }); }; },{}],19:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.polarArea = { scale: { - type: "radialLinear", + type: 'radialLinear', lineArc: true, // so that lines are circular ticks: { beginAtZero: true } }, - //Boolean - Whether to animate the rotation of the chart + // Boolean - Whether to animate the rotation of the chart animation: { animateRotate: true, animateScale: true }, @@ -7402,20 +7487,20 @@ var datasets = data.datasets; var labels = data.labels; if (datasets.length) { for (var i = 0; i < datasets[0].data.length; ++i) { - text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '">'); + text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>'); if (labels[i]) { text.push(labels[i]); } - text.push('</span></li>'); + text.push('</li>'); } } text.push('</ul>'); - return text.join(""); + return text.join(''); }, legend: { labels: { generateLabels: function(chart) { var data = chart.data; @@ -7440,13 +7525,12 @@ // Extra data used for toggling the correct item index: i }; }); - } else { - return []; } + return []; } }, onClick: function(e, legendItem) { var index = legendItem.index; @@ -7525,11 +7609,11 @@ if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { ++visibleCount; } } - //var negHalfPI = -0.5 * Math.PI; + // 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); @@ -7579,27 +7663,27 @@ calculateCircumference: function(value) { var count = this.getMeta().count; if (count > 0 && !isNaN(value)) { return (2 * Math.PI) / count; - } else { - return 0; } + return 0; } }); }; },{}],20:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.radar = { + aspectRatio: 1, scale: { - type: "radialLinear" + type: 'radialLinear' }, elements: { line: { tension: 0 // no bezier in radar } @@ -7612,17 +7696,10 @@ dataElementType: Chart.elements.Point, linkScales: helpers.noop, - addElementAndReset: function(index) { - Chart.DatasetController.prototype.addElementAndReset.call(this, index); - - // Make sure bezier control points are updated - this.updateBezierControlPoints(); - }, - update: function(reset) { var me = this; var meta = me.getMeta(); var line = meta.dataset; var points = meta.data; @@ -7667,11 +7744,10 @@ // Update Points helpers.each(points, function(point, index) { me.updateElement(point, index, reset); }, me); - // Update bezier control points me.updateBezierControlPoints(); }, updateElement: function(point, index, reset) { var me = this; @@ -7691,11 +7767,11 @@ _model: { x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales y: reset ? scale.yCenter : pointPosition.y, // Appearance - tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension), + tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), @@ -7777,28 +7853,28 @@ } }); }; },{}],21:[function(require,module,exports){ -/*global window: false */ -"use strict"; +/* global window: false */ +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.global.animation = { duration: 1000, - easing: "easeOutQuart", + easing: 'easeOutQuart', onProgress: helpers.noop, onComplete: helpers.noop }; Chart.Animation = Chart.Element.extend({ currentStep: null, // the current animation step numSteps: 60, // default number of steps - easing: "", // the easing to use for this animation + easing: '', // the easing to use for this animation render: null, // render function used by the animation service onAnimationProgress: null, // user specified callback to fire on each step of the animation onAnimationComplete: null // user specified callback to fire when the animation finishes }); @@ -7806,10 +7882,18 @@ Chart.animationService = { frameDuration: 17, animations: [], dropFrames: 0, request: null, + + /** + * @function Chart.animationService.addAnimation + * @param chartInstance {ChartController} the chart to animate + * @param animationObject {IAnimation} the animation that we will animate + * @param duration {Number} length of animation in ms + * @param lazy {Boolean} if true, the chart is not marked as animating to enable more responsive interactions + */ addAnimation: function(chartInstance, animationObject, duration, lazy) { var me = this; if (!lazy) { chartInstance.animating = true; @@ -7908,12 +7992,13 @@ me.requestAnimationFrame(); } } }; }; + },{}],22:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { // Global Chart canvas helpers object for drawing items to canvas var helpers = Chart.canvasHelpers = {}; @@ -7951,13 +8036,21 @@ ctx.fill(); break; case 'rect': size = 1 / Math.SQRT2 * radius; ctx.beginPath(); - ctx.fillRect(x - size, y - size, 2 * size, 2 * size); + ctx.fillRect(x - size, y - size, 2 * size, 2 * size); ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); break; + case 'rectRounded': + var offset = radius / Math.SQRT2; + var leftX = x - offset; + var topY = y - offset; + var sideSize = Math.SQRT2 * radius; + Chart.helpers.drawRoundedRectangle(ctx, leftX, topY, sideSize, sideSize, radius / 2); + ctx.fill(); + break; case 'rectRot': size = 1 / Math.SQRT2 * radius; ctx.beginPath(); ctx.moveTo(x - size, y); ctx.lineTo(x, y + size); @@ -8012,127 +8105,208 @@ break; } ctx.stroke(); }; + + helpers.clipArea = function(ctx, clipArea) { + ctx.save(); + ctx.beginPath(); + ctx.rect(clipArea.left, clipArea.top, clipArea.right - clipArea.left, clipArea.bottom - clipArea.top); + ctx.clip(); + }; + + helpers.unclipArea = function(ctx) { + ctx.restore(); + }; + }; + },{}],23:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; - //Create a dictionary of chart types, to allow for extension of existing types + var plugins = Chart.plugins; + var platform = Chart.platform; + + // Create a dictionary of chart types, to allow for extension of existing types Chart.types = {}; - //Store a reference to each instance - allowing us to globally resize chart instances on window resize. - //Destroy method on the chart will remove the instance of the chart from this reference. + // Store a reference to each instance - allowing us to globally resize chart instances on window resize. + // Destroy method on the chart will remove the instance of the chart from this reference. Chart.instances = {}; // Controllers available for dataset visualization eg. bar, line, slice, etc. Chart.controllers = {}; /** + * Initializes the given config with global and chart default values. + */ + function initConfig(config) { + config = config || {}; + + // Do NOT use configMerge() for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = helpers.configMerge( + Chart.defaults.global, + Chart.defaults[config.type], + config.options || {}); + + return config; + } + + /** + * Updates the config of the chart + * @param chart {Chart.Controller} chart to update the options for + */ + function updateConfig(chart) { + var newOptions = chart.options; + + // Update Scale(s) with options + if (newOptions.scale) { + chart.scale.options = newOptions.scale; + } else if (newOptions.scales) { + newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) { + chart.scales[scaleOptions.id].options = scaleOptions; + }); + } + + // Tooltip + chart.tooltip._options = newOptions.tooltips; + } + + /** * @class Chart.Controller * The main controller of a chart. */ - Chart.Controller = function(instance) { + Chart.Controller = function(item, config, instance) { + var me = this; - 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(); + config = initConfig(config); - Object.defineProperty(this, 'data', { + var context = platform.acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + instance.ctx = context; + instance.canvas = canvas; + instance.config = config; + instance.width = width; + instance.height = height; + instance.aspectRatio = height? width / height : null; + + me.id = helpers.uid(); + me.chart = instance; + me.config = config; + me.options = config.options; + me._bufferedRender = false; + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + Object.defineProperty(me, 'data', { get: function() { - return this.config.data; + return me.config.data; } }); - //Add the chart instance to the global namespace - Chart.instances[this.id] = this; - - if (this.options.responsive) { - // Silent resize before chart draws - this.resize(true); + 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; } - this.initialize(); + me.initialize(); + me.update(); - return this; + return me; }; - helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ { - + helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller.prototype */ { initialize: function() { var me = this; + // Before init plugin notification - Chart.plugins.notify('beforeInit', [me]); + plugins.notify(me, 'beforeInit'); + helpers.retinaScale(me.chart); + me.bindEvents(); - // Make sure controllers are built first so that each dataset is bound to an axis before the scales - // are built + if (me.options.responsive) { + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + // Make sure scales have IDs and are built before we build any controllers. me.ensureScalesHaveIDs(); - me.buildOrUpdateControllers(); me.buildScales(); - me.updateLayout(); - me.resetElements(); me.initToolTip(); - me.update(); // After init plugin notification - Chart.plugins.notify('afterInit', [me]); + plugins.notify(me, 'afterInit'); return me; }, clear: function() { helpers.clear(this.chart); return this; }, stop: function() { - // Stops any current animation loop occuring + // Stops any current animation loop occurring Chart.animationService.cancelAnimation(this); return this; }, - resize: function resize(silent) { + resize: function(silent) { var me = this; var chart = me.chart; + var options = me.options; 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); + var aspectRatio = (options.maintainAspectRatio && chart.aspectRatio) || null; - var sizeChanged = chart.width !== newWidth || chart.height !== newHeight; + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + var newWidth = Math.floor(helpers.getMaximumWidth(canvas)); + var newHeight = Math.floor(aspectRatio? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)); - if (!sizeChanged) { - return me; + if (chart.width === newWidth && chart.height === newHeight) { + return; } canvas.width = chart.width = newWidth; canvas.height = chart.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; helpers.retinaScale(chart); - // Notify any plugins about the resize - var newSize = { width: newWidth, height: newHeight }; - Chart.plugins.notify('resize', [me, newSize]); + if (!silent) { + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + plugins.notify(me, 'resize', [newSize]); - // Notify of resize - if (me.options.onResize) { - me.options.onResize(me, newSize); - } + // Notify of resize + if (me.options.onResize) { + me.options.onResize(me, newSize); + } - if (!silent) { me.stop(); me.update(me.options.responsiveAnimationDuration); } - - return me; }, ensureScalesHaveIDs: function() { var options = this.options; var scalesOptions = options.scales || {}; @@ -8161,17 +8335,20 @@ var items = []; if (options.scales) { items = items.concat( (options.scales.xAxes || []).map(function(xAxisOptions) { - return { options: xAxisOptions, dtype: 'category' }; }), + return {options: xAxisOptions, dtype: 'category'}; + }), (options.scales.yAxes || []).map(function(yAxisOptions) { - return { options: yAxisOptions, dtype: 'linear' }; })); + return {options: yAxisOptions, dtype: 'linear'}; + }) + ); } if (options.scale) { - items.push({ options: options.scale, dtype: 'radialLinear', isDefault: true }); + items.push({options: options.scale, dtype: 'radialLinear', isDefault: true}); } helpers.each(items, function(item) { var scaleOptions = item.options; var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype); @@ -8198,14 +8375,10 @@ }); Chart.scaleService.addScalesToLayout(this); }, - updateLayout: function() { - Chart.layoutService.update(this, this.chart.width, this.chart.height); - }, - buildOrUpdateControllers: function() { var me = this; var types = []; var newControllers = []; @@ -8235,21 +8408,38 @@ } return newControllers; }, + /** + * Reset the elements of all datasets + * @private + */ resetElements: function() { var me = this; helpers.each(me.data.datasets, function(dataset, datasetIndex) { me.getDatasetMeta(datasetIndex).controller.reset(); }, me); }, - update: function update(animationDuration, lazy) { + /** + * Resets the chart back to it's state before the initial animation + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function(animationDuration, lazy) { var me = this; - Chart.plugins.notify('beforeUpdate', [me]); + updateConfig(me); + + if (plugins.notify(me, 'beforeUpdate') === false) { + return; + } + // In case the entire data object changed me.tooltip._data = me.data; // Make sure dataset controllers are updated and new controllers are reset var newControllers = me.buildOrUpdateControllers(); @@ -8257,77 +8447,94 @@ // 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.layoutService.update(me, me.chart.width, me.chart.height); + me.updateLayout(); - // Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages - Chart.plugins.notify('afterScaleUpdate', [me]); - // Can only reset the new controllers after the scales have been updated helpers.each(newControllers, function(controller) { controller.reset(); }); me.updateDatasets(); // Do this before render so that any plugins that need final scale updates can use it - Chart.plugins.notify('afterUpdate', [me]); + plugins.notify(me, 'afterUpdate'); - me.render(animationDuration, lazy); + if (me._bufferedRender) { + me._bufferedRequest = { + lazy: lazy, + duration: animationDuration + }; + } else { + me.render(animationDuration, lazy); + } }, /** - * @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 + * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` + * hook, in which case, plugins will not be called on `afterLayout`. + * @private */ + updateLayout: function() { + var me = this; - /** - * @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 - */ + if (plugins.notify(me, 'beforeLayout') === false) { + return; + } + Chart.layoutService.update(this, this.chart.width, this.chart.height); + + /** + * Provided for backward compatibility, use `afterLayout` instead. + * @method IPlugin#afterScaleUpdate + * @deprecated since version 2.5.0 + * @todo remove at version 3 + */ + plugins.notify(me, 'afterScaleUpdate'); + plugins.notify(me, 'afterLayout'); + }, + /** - * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate - * extension, in which case no datasets will be updated and the afterDatasetsUpdate - * notification will be skipped. - * @protected - * @instance + * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` + * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. + * @private */ updateDatasets: function() { var me = this; - var i, ilen; - if (Chart.plugins.notify('beforeDatasetsUpdate', [ me ])) { - for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me.getDatasetMeta(i).controller.update(); - } + if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { + return; + } - Chart.plugins.notify('afterDatasetsUpdate', [ me ]); + for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.getDatasetMeta(i).controller.update(); } + + plugins.notify(me, 'afterDatasetsUpdate'); }, - render: function render(duration, lazy) { + render: function(duration, lazy) { var me = this; - Chart.plugins.notify('beforeRender', [me]); + if (plugins.notify(me, 'beforeRender') === false) { + return; + } + var animationOptions = me.options.animation; + var onComplete = function() { + plugins.notify(me, 'afterRender'); + var callback = animationOptions && animationOptions.onComplete; + if (callback && callback.call) { + callback.call(me); + } + }; + if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { var animation = new Chart.Animation(); - animation.numSteps = (duration || animationOptions.duration) / 16.66; //60 fps + animation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps animation.easing = animationOptions.easing; // render function animation.render = function(chartInstance, animationObject) { var easingFunction = helpers.easingEffects[animationObject.easing]; @@ -8337,176 +8544,98 @@ chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); }; // user events animation.onAnimationProgress = animationOptions.onProgress; - animation.onAnimationComplete = animationOptions.onComplete; + animation.onAnimationComplete = onComplete; Chart.animationService.addAnimation(me, animation, duration, lazy); } else { me.draw(); - if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) { - animationOptions.onComplete.call(me); - } + onComplete(); } + return me; }, - draw: function(ease) { + draw: function(easingValue) { var me = this; - var easingDecimal = ease || 1; + me.clear(); - Chart.plugins.notify('beforeDraw', [me, easingDecimal]); + if (easingValue === undefined || easingValue === null) { + easingValue = 1; + } + if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { + return; + } + // Draw all the scales helpers.each(me.boxes, function(box) { box.draw(me.chartArea); }, me); + if (me.scale) { me.scale.draw(); } - Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]); + me.drawDatasets(easingValue); + // Finally draw the tooltip + me.tooltip.transition(easingValue).draw(); + + plugins.notify(me, 'afterDraw', [easingValue]); + }, + + /** + * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` + * hook, in which case, plugins will not be called on `afterDatasetsDraw`. + * @private + */ + drawDatasets: function(easingValue) { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { + return; + } + // Draw each dataset via its respective controller (reversed to support proper line stacking) helpers.each(me.data.datasets, function(dataset, datasetIndex) { if (me.isDatasetVisible(datasetIndex)) { - me.getDatasetMeta(datasetIndex).controller.draw(ease); + me.getDatasetMeta(datasetIndex).controller.draw(easingValue); } }, me, true); - Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]); - - // Finally draw the tooltip - me.tooltip.transition(easingDecimal).draw(); - - Chart.plugins.notify('afterDraw', [me, easingDecimal]); + plugins.notify(me, 'afterDatasetsDraw', [easingValue]); }, // Get the single element that was clicked on // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw getElementAtEvent: function(e) { - var me = this; - var eventPosition = helpers.getRelativePosition(e, me.chart); - var elementsArray = []; - - 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; - } - }); - } - }); - - return elementsArray.slice(0, 1); + return Chart.Interaction.modes.single(this, e); }, getElementsAtEvent: function(e) { - var me = this; - var eventPosition = helpers.getRelativePosition(e, me.chart); - var elementsArray = []; - - 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); - - if (!found) { - return elementsArray; - } - - 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); - - return elementsArray; + return Chart.Interaction.modes.label(this, e, {intersect: true}); }, getElementsAtXAxis: function(e) { - var me = this; - var eventPosition = helpers.getRelativePosition(e, me.chart); - var elementsArray = []; + return Chart.Interaction.modes['x-axis'](this, e, {intersect: true}); + }, - 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); - - if (!found) { - return elementsArray; + getElementsAtEventForMode: function(e, mode, options) { + var method = Chart.Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); } - 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); - - return elementsArray; - }, - - 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; - } + return []; }, getDatasetAtEvent: function(e) { - var elementsArray = this.getElementAtEvent(e); - - if (elementsArray.length > 0) { - elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data; - } - - return elementsArray; + return Chart.Interaction.modes.dataset(this, e, {intersect: true}); }, getDatasetMeta: function(datasetIndex) { var me = this; var dataset = me.data.datasets[datasetIndex]; @@ -8515,27 +8644,27 @@ } 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 - }; + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null + }; } return meta; }, getVisibleDatasetCount: function() { var count = 0; for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) { - if (this.isDatasetVisible(i)) { + if (this.isDatasetVisible(i)) { count++; } } return count; }, @@ -8552,31 +8681,34 @@ return this.options.legendCallback(this); }, destroy: function() { var me = this; + var canvas = me.chart.canvas; + var meta, i, ilen; + me.stop(); - me.clear(); - helpers.unbindEvents(me, me.events); - helpers.removeResizeListener(me.chart.canvas.parentNode); - // Reset canvas height/width attributes - var canvas = me.chart.canvas; - canvas.width = me.chart.width; - canvas.height = me.chart.height; + // 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; + } + } - // 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); + if (canvas) { + me.unbindEvents(); + helpers.clear(me.chart); + platform.releaseContext(me.chart.ctx); + me.chart.canvas = null; + me.chart.ctx = null; } - // 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; + plugins.notify(me, 'destroy'); - Chart.plugins.notify('destroy', [me]); - delete Chart.instances[me.id]; }, toBase64Image: function() { return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); @@ -8588,76 +8720,141 @@ _chart: me.chart, _chartInstance: me, _data: me.data, _options: me.options.tooltips }, me); + me.tooltip.initialize(); }, + /** + * @private + */ bindEvents: function() { var me = this; - helpers.bindEvents(me, me.options.events, function(evt) { - me.eventHandler(evt); + var listeners = me._listeners = {}; + var listener = function() { + me.eventHandler.apply(me, arguments); + }; + + helpers.each(me.options.events, function(type) { + platform.addEventListener(me, type, listener); + listeners[type] = listener; }); + + // 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) { + listener = function() { + me.resize(); + }; + + platform.addEventListener(me, 'resize', listener); + listeners.resize = listener; + } }, + /** + * @private + */ + unbindEvents: function() { + var me = this; + var listeners = me._listeners; + if (!listeners) { + return; + } + + delete me._listeners; + helpers.each(listeners, function(listener, type) { + platform.removeEventListener(me, type, listener); + }); + }, + updateHoverStyle: function(elements, mode, enabled) { var method = enabled? 'setHoverStyle' : 'removeHoverStyle'; var element, i, ilen; - switch (mode) { - case 'single': - elements = [ elements[0] ]; - break; - case 'label': - case 'dataset': - case 'x-axis': - // elements = elements; - break; - default: - // unsupported mode - return; - } - for (i=0, ilen=elements.length; i<ilen; ++i) { element = elements[i]; if (element) { this.getDatasetMeta(element._datasetIndex).controller[method](element); } } }, - eventHandler: function eventHandler(e) { + /** + * @private + */ + eventHandler: function(e) { var me = this; var tooltip = me.tooltip; + + if (plugins.notify(me, 'beforeEvent', [e]) === false) { + return; + } + + // Buffer any update calls so that renders do not occur + me._bufferedRender = true; + me._bufferedRequest = null; + + var changed = me.handleEvent(e); + changed |= tooltip && tooltip.handleEvent(e); + + plugins.notify(me, 'afterEvent', [e]); + + 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(); + + // We only need to render at this point. Updating will cause scales to be + // recomputed generating flicker & using more memory than necessary. + me.render(me.options.hover.animationDuration, true); + } + + me._bufferedRender = false; + me._bufferedRequest = null; + + return me; + }, + + /** + * Handle an event + * @private + * @param {IEvent} 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 tooltipsOptions = options.tooltips; + var changed = false; me.lastActive = me.lastActive || []; - me.lastTooltipActive = me.lastTooltipActive || []; // 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); + me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); } // On Hover hook if (hoverOptions.onHover) { - hoverOptions.onHover.call(me, me.active); + // Need to call with native event here to not break backwards compatibility + hoverOptions.onHover.call(me, e.native, me.active); } if (e.type === 'mouseup' || e.type === 'click') { if (options.onClick) { - options.onClick.call(me, e, me.active); + // Use e.native here for backwards compatibility + options.onClick.call(me, e.native, me.active); } - if (me.legend && me.legend.handleEvent) { - me.legend.handleEvent(e); - } } // Remove styling for last active (even if it may still be active) if (me.lastActive.length) { me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); @@ -8666,56 +8863,101 @@ // Built in hover styling if (me.active.length && hoverOptions.mode) { me.updateHoverStyle(me.active, hoverOptions.mode, true); } - // Built in Tooltips - if (tooltipsOptions.enabled || tooltipsOptions.custom) { - tooltip.initialize(); - tooltip._active = me.tooltipActive; - tooltip.update(true); - } + changed = !helpers.arrayEquals(me.active, me.lastActive); - // Hover animations - tooltip.pivot(); - - 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)) { - - me.stop(); - - if (tooltipsOptions.enabled || tooltipsOptions.custom) { - tooltip.update(true); - } - - // 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); - } - } - // Remember Last Actives me.lastActive = me.active; - me.lastTooltipActive = me.tooltipActive; - return me; + + return changed; } }); }; },{}],24:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; - var noop = helpers.noop; + var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + + /** + * 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; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); + } + + /** + * 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; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; + } + // Base class for all dataset controllers (line, bar, etc) Chart.DatasetController = function(chart, datasetIndex) { - this.initialize.call(this, chart, datasetIndex); + this.initialize(chart, datasetIndex); }; helpers.extend(Chart.DatasetController.prototype, { /** @@ -8769,10 +9011,19 @@ reset: function() { this.update(true); }, + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + createMetaDataset: function() { var me = this; var type = me.datasetElementType; return type && new type({ _chart: me.chart.chart, @@ -8796,49 +9047,54 @@ var data = me.getDataset().data || []; var metaData = meta.data; var i, ilen; for (i=0, ilen=data.length; i<ilen; ++i) { - metaData[i] = metaData[i] || me.createMetaData(meta, i); + metaData[i] = metaData[i] || me.createMetaData(i); } meta.dataset = meta.dataset || me.createMetaDataset(); }, addElementAndReset: function(index) { - var me = this; - var element = me.createMetaData(index); - me.getMeta().data.splice(index, 0, element); - me.updateElement(element, index, true); + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); }, buildOrUpdateElements: function() { - // Handle the number of data points changing - var meta = this.getMeta(), - md = meta.data, - numData = this.getDataset().data.length, - numMetaData = md.length; + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); - // 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); + // 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); } + + listenArrayEvents(data, me); + me._data = data; } + + // 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(); }, - update: noop, + update: helpers.noop, draw: function(ease) { var easingDecimal = ease || 1; - helpers.each(this.getMeta().data, function(element) { - element.transition(easingDecimal).draw(); - }); + var i, len; + var metaData = this.getMeta().data; + for (i = 0, len = metaData.length; i < len; ++i) { + metaData[i].transition(easingDecimal).draw(); + } }, removeHoverStyle: function(element, elementOpts) { var dataset = this.chart.data.datasets[element._datasetIndex], index = element._index, @@ -8860,135 +9116,188 @@ model = element._model; 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); + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i=0; i<count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + this.insertElements(this.getDataset().data.length-1, arguments.length); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); } - - }); - + }); Chart.DatasetController.extend = helpers.inherits; }; + },{}],25:[function(require,module,exports){ -"use strict"; +'use strict'; module.exports = function(Chart) { - var helpers = Chart.helpers; + var helpers = Chart.helpers; - Chart.elements = {}; + Chart.elements = {}; - Chart.Element = function(configuration) { - helpers.extend(this, configuration); - this.initialize.apply(this, arguments); - }; + Chart.Element = function(configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); + }; - helpers.extend(Chart.Element.prototype, { + helpers.extend(Chart.Element.prototype, { - initialize: function() { - this.hidden = false; - }, + 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; - }, + 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); - } + transition: function(ease) { + var me = this; - // No animation -> No Transition - if (ease === 1) { - me._view = me._model; - me._start = null; - return me; - } + if (!me._view) { + me._view = helpers.clone(me._model); + } - if (!me._start) { - me.pivot(); - } + // No animation -> No Transition + if (ease === 1) { + me._view = me._model; + me._start = null; + return me; + } - helpers.each(me._model, function(value, key) { + if (!me._start) { + me.pivot(); + } - if (key[0] === '_') { - // Only non-underscored properties - } + helpers.each(me._model, function(value, key) { - // 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 (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); - // No unnecessary computations - else if (value === me._view[key]) { - // It's the same! Woohoo! - } + return me; + }, - // 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); + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, - return me; - }, + hasValue: function() { + return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } + }); - tooltipPosition: function() { - return { - x: this._model.x, - y: this._model.y - }; - }, + Chart.Element.extend = helpers.inherits; - 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(2); module.exports = function(Chart) { - //Global Chart helpers object for utility methods and classes + // Global Chart helpers object for utility methods and classes var helpers = Chart.helpers = {}; - //-- Basic js utility methods + // -- 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; @@ -9021,52 +9330,40 @@ } }); return objClone; }; helpers.extend = function(base) { - var setFn = function(value, key) { base[key] = value; }; + 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); + var baseHasProperty = base.hasOwnProperty(key); + var baseVal = baseHasProperty ? 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(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]; - - helpers.each(value, function(valueObj, index) { - - 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 - } - }); - - } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { + 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(base[key], value); - + base[key] = helpers.configMerge(baseVal, value); } else { // can just overwrite the value in this case base[key] = value; } }); @@ -9099,11 +9396,11 @@ 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") { + } 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 @@ -9126,37 +9423,40 @@ }; 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) { + 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 = []; + } + var filtered = []; - helpers.each(collection, function(item) { - if (filterCallback(item)) { - filtered.push(item); - } - }); + helpers.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); - return filtered; - } + return filtered; }; helpers.findIndex = Array.prototype.findIndex? - function(array, callback, scope) { return array.findIndex(callback, scope); } : 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; } @@ -9186,76 +9486,82 @@ 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); + // 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); }; var Surrogate = function() { this.constructor = ChartElement; }; - Surrogate.prototype = parent.prototype; + Surrogate.prototype = me.prototype; ChartElement.prototype = new Surrogate(); ChartElement.extend = helpers.inherits; if (extensions) { helpers.extend(ChartElement.prototype, extensions); } - ChartElement.__super__ = parent.prototype; + ChartElement.__super__ = me.prototype; return ChartElement; }; helpers.noop = function() {}; helpers.uid = (function() { var id = 0; return function() { return id++; }; - })(); - //-- Math methods + }()); + // -- 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.almostWhole = function(x, epsilon) { + var rounded = Math.round(x); + return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); + }; helpers.max = function(array) { return array.reduce(function(max, value) { if (!isNaN(value)) { return Math.max(max, value); - } else { - return max; } + 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; } + return min; }, Number.POSITIVE_INFINITY); }; helpers.sign = Math.sign? - function(x) { return Math.sign(x); } : 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.log10(x); + } : + function(x) { return Math.log(x) / Math.LN10; }; helpers.toRadians = function(degrees) { return degrees * (Math.PI / 180); }; @@ -9277,16 +9583,19 @@ 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 + // Props to Rob Spencer at scaled innovation for his post on splining between points + // http://scaledinnovation.com/analytics/splines/aboutSplines.html // This function must also respect "skipped" points var previous = firstPoint.skip ? middlePoint : firstPoint, current = middlePoint, @@ -9334,47 +9643,68 @@ // 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; + if (pointCurrent.model.skip) { + continue; + } + 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); + var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); + + // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 + pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; } - 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; + + 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; + } } // 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 (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) - { + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } + + if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { pointCurrent.mK = pointAfter.mK = 0; continue; } + alphaK = pointCurrent.mK / pointCurrent.deltaK; betaK = pointAfter.mK / pointCurrent.deltaK; squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); - if (squaredMagnitude <= 9) continue; + if (squaredMagnitude <= 9) { + continue; + } + tauK = 3 / Math.sqrt(squaredMagnitude); pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; - pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; } // Compute control points var deltaX; for (i = 0; i < pointsLen; ++i) { pointCurrent = pointsWithTangents[i]; - if (pointCurrent.model.skip) continue; + if (pointCurrent.model.skip) { + continue; + } + 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; @@ -9389,11 +9719,10 @@ }; 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]; @@ -9414,26 +9743,24 @@ } 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 { - if (fraction <= 1.0) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } + niceFraction = 10; } return niceFraction * Math.pow(10, exponent); }; - //Easing functions adapted from Robert Penner's easing equations - //http://www.robertpenner.com/easing/ + // 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) { @@ -9615,43 +9942,32 @@ 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); } + 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/ + // 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 + }()); + // -- DOM methods helpers.getRelativePosition = function(evt, chart) { var mouseX, mouseY; var e = evt.originalEvent || evt, canvas = evt.currentTarget || evt.srcElement, boundingRect = canvas.getBoundingClientRect(); @@ -9689,49 +10005,32 @@ }; helpers.addEvent = function(node, eventType, method) { if (node.addEventListener) { node.addEventListener(eventType, method); } else if (node.attachEvent) { - node.attachEvent("on" + eventType, method); + node.attachEvent('on' + eventType, method); } else { - node["on" + eventType] = method; + 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); + node.detachEvent('on' + eventType, handler); } else { - node["on" + eventType] = helpers.noop; + 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 || {}; - 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); - }); - }; - // 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); - if (styleValue.indexOf('%') != -1) { + if (styleValue.indexOf('%') !== -1) { // percentage * size in dimension valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; } } else { valueInPixels = styleValue; @@ -9743,11 +10042,11 @@ /** * Returns if the given value contains an effective constraint. * @private */ function isConstrainedValue(value) { - return value !== undefined && value !== null && value !== 'none'; + return value !== undefined && value !== null && value !== 'none'; } // 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) @@ -9778,54 +10077,55 @@ 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 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 padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom')); - var h = container.clientHeight - padding; + 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 ctx = chart.ctx; - var canvas = chart.canvas; - var width = canvas.width; - var height = canvas.height; var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1; + if (pixelRatio === 1) { + return; + } - if (pixelRatio !== 1) { - canvas.height = height * pixelRatio; - canvas.width = width * pixelRatio; - ctx.scale(pixelRatio, pixelRatio); + var canvas = chart.canvas; + var height = chart.height; + var width = chart.width; - // 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; - } + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.scale(pixelRatio, pixelRatio); - canvas.style.width = width + 'px'; + // 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 + // -- 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; + 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 || []; @@ -9861,11 +10161,11 @@ } gc.splice(0, gcLen); } return longest; }; - helpers.measureText = function (ctx, data, gc, longest, string) { + helpers.measureText = function(ctx, data, gc, longest, string) { var textWidth = data[string]; if (!textWidth) { textWidth = data[string] = ctx.measureText(string).width; gc.push(string); } @@ -9898,74 +10198,33 @@ ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); }; helpers.color = function(c) { if (!color) { - console.log('Color.js not found!'); + console.error('Color.js not found!'); return c; } /* global CanvasGradient */ if (c instanceof CanvasGradient) { return color(Chart.defaults.global.defaultColor); } 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 (hiddenIframe.classlist) { - // can use classlist - hiddenIframe.classlist.add(hiddenIframeClass); - } else { - hiddenIframe.setAttribute('class', hiddenIframeClass); - } - - // Set the style - hiddenIframe.tabIndex = -1; - 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; - - // Insert the iframe so that contentWindow is available - node.insertBefore(hiddenIframe, node.firstChild); - - (hiddenIframe.contentWindow || hiddenIframe).onresize = function() { - if (callback) { - callback(); - } - }; - }; - helpers.removeResizeListener = function(node) { - var hiddenIframe = node.querySelector('.chartjs-hidden-iframe'); - - // Remove the resize detect iframe - if (hiddenIframe) { - hiddenIframe.parentNode.removeChild(hiddenIframe); - } - }; helpers.isArray = Array.isArray? - function(obj) { return Array.isArray(obj); } : function(obj) { + return Array.isArray(obj); + } : + function(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }; - //! @see http://stackoverflow.com/a/14853974 + // ! @see http://stackoverflow.com/a/14853974 helpers.arrayEquals = function(a0, a1) { var i, ilen, v0, v1; - if (!a0 || !a1 || a0.length != a1.length) { + if (!a0 || !a1 || a0.length !== a1.length) { return false; } for (i = 0, ilen=a0.length; i < ilen; ++i) { v0 = a0[i]; @@ -9973,11 +10232,11 @@ if (v0 instanceof Array && v1 instanceof Array) { if (!helpers.arrayEquals(v0, v1)) { return false; } - } else if (v0 != v1) { + } else if (v0 !== v1) { // NOTE: two different object instances will never be equal: {x:20} != {x:20} return false; } } @@ -9986,93 +10245,354 @@ helpers.callCallback = function(fn, args, _tArg) { if (fn && typeof fn.call === 'function') { fn.apply(_tArg, args); } }; - helpers.getHoverColor = function(color) { + helpers.getHoverColor = function(colorValue) { /* global CanvasPattern */ - return (color instanceof CanvasPattern) ? - color : - helpers.color(color).saturate(0.5).darken(0.1).rgbString(); + return (colorValue instanceof CanvasPattern) ? + colorValue : + helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); }; }; },{"2":2}],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 get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {Point} the event position + */ + function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers.getRelativePosition(e, chart); + } + + /** + * 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; + + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!chart.isDatasetVisible(i)) { + continue; } - }; - // Support a jQuery'd canvas element - if (context.length && context[0].getContext) { - context = context[0]; + 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); + } + } } + } - // Support a canvas domnode - if (context.getContext) { - context = context.getContext("2d"); + /** + * 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 = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; + } + + /** + * 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 (!distanceMetric) { + distanceMetric = helpers.distanceBetweenPoints; } - me.ctx = context; - me.canvas = context.canvas; + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } - context.canvas.style.display = context.canvas.style.display || 'block'; + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); - // 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); + 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); + } + }); - me.aspectRatio = me.width / me.height; + return 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; + function indexMode(chart, e, options) { + var position = 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 = []; + + if (!items.length) { + return []; } - // 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; + chart.data.datasets.forEach(function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex), + element = meta.data[items[0]._index]; - // 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); - - // 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(); + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } } }); - return me.controller ? me.controller : me; + return elements; + } + /** + * @interface IInteractionOptions + */ + /** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + + /** + * Contains interaction related functions + * @namespace Chart.Interaction + */ + Chart.Interaction = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart.chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + */ + label: indexMode, + + /** + * 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, + + /** + * 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 = getRelativePosition(e, chart.chart); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @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); + }, + + /** + * 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 = getRelativePosition(e, chart.chart); + return getIntersectItems(chart, position); + }, + + /** + * 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 = getRelativePosition(e, chart.chart); + var nearestItems = getNearestItems(chart, position, options.intersect); + + // 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; + + if (ret === 0) { + // if equal sort by dataset index + ret = a._datasetIndex - b._datasetIndex; + } + + return ret; + }); + } + + // Return only 1 item + return nearestItems.slice(0, 1); + }, + + /** + * 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 = getRelativePosition(e, chart.chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // 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; + }, + + /** + * 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 = getRelativePosition(e, chart.chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // 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; + } + } }; +}; - //Globally expose the defaults to allow for user updating/changing +},{}],28:[function(require,module,exports){ +'use strict'; + +module.exports = function() { + + // 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; + }; + + // 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"], + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], hover: { onHover: null, - mode: 'single', + mode: 'nearest', + intersect: true, animationDuration: 400 }, onClick: null, defaultColor: 'rgba(0,0,0,0.1)', defaultFontColor: '#666', @@ -10095,23 +10615,22 @@ } text.push('</li>'); } text.push('</ul>'); - return text.join(""); + return text.join(''); } } }; Chart.Chart = Chart; return Chart; - }; -},{}],28:[function(require,module,exports){ -"use strict"; +},{}],29:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; @@ -10141,29 +10660,47 @@ if (!chartInstance) { return; } - var xPadding = 0; - var yPadding = 0; + var layoutOptions = chartInstance.options.layout; + var padding = layoutOptions ? layoutOptions.padding : null; + var leftPadding = 0; + var rightPadding = 0; + var topPadding = 0; + var bottomPadding = 0; + + 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; + } + var leftBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "left"; + return box.options.position === 'left'; }); var rightBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "right"; + return box.options.position === 'right'; }); var topBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "top"; + return box.options.position === 'top'; }); var bottomBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "bottom"; + return box.options.position === 'bottom'; }); // Boxes that overlay the chartarea such as the radialLinear scale var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) { - return box.options.position === "chartArea"; + return box.options.position === 'chartArea'; }); // 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); @@ -10208,12 +10745,12 @@ // 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 // Step 1 - var chartWidth = width - (2 * xPadding); - var chartHeight = height - (2 * yPadding); + var chartWidth = width - leftPadding - rightPadding; + var chartHeight = height - topPadding - bottomPadding; var chartAreaWidth = chartWidth / 2; // min 50% var chartAreaHeight = chartHeight / 2; // min 50% // Step 2 var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); @@ -10224,12 +10761,10 @@ // Step 4 var maxChartAreaWidth = chartWidth; var maxChartAreaHeight = chartHeight; var minBoxSizes = []; - helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); - function getMinimumBoxSize(box) { var minSize; var isHorizontal = box.isHorizontal(); if (isHorizontal) { @@ -10241,48 +10776,57 @@ } minBoxSizes.push({ horizontal: isHorizontal, minSize: minSize, - box: box + box: box, }); } - // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could - // be if the axes are drawn at their minimum sizes. + helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); - // Steps 5 & 6 - var totalLeftBoxesWidth = xPadding; - var totalRightBoxesWidth = xPadding; - var totalTopBoxesHeight = yPadding; - var totalBottomBoxesHeight = yPadding; + // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) + var maxHorizontalLeftPadding = 0; + var maxHorizontalRightPadding = 0; + var maxVerticalTopPadding = 0; + var maxVerticalBottomPadding = 0; - // Update, and calculate the left and right margins for the horizontal boxes - helpers.each(leftBoxes.concat(rightBoxes), fitBox); - - helpers.each(leftBoxes, function(box) { - totalLeftBoxesWidth += box.width; + helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) { + if (horizontalBox.getPadding) { + var boxPadding = horizontalBox.getPadding(); + maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left); + maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right); + } }); - helpers.each(rightBoxes, function(box) { - totalRightBoxesWidth += box.width; + helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) { + if (verticalBox.getPadding) { + var boxPadding = verticalBox.getPadding(); + maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top); + maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom); + } }); - // Set the Left and Right margins for the horizontal boxes - helpers.each(topBoxes.concat(bottomBoxes), fitBox); + // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. + // Steps 5 & 6 + var totalLeftBoxesWidth = leftPadding; + var totalRightBoxesWidth = rightPadding; + var totalTopBoxesHeight = topPadding; + var totalBottomBoxesHeight = bottomPadding; // Function to fit a box function fitBox(box) { - var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { - return minBoxSize.box === box; + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { + return minBox.box === box; }); if (minBoxSize) { if (box.isHorizontal()) { var scaleMargin = { - left: totalLeftBoxesWidth, - right: totalRightBoxesWidth, + left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding), + right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding), top: 0, bottom: 0 }; // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends @@ -10292,25 +10836,36 @@ box.update(minBoxSize.minSize.width, maxChartAreaHeight); } } } + // Update, and calculate the left and right margins for the horizontal boxes + helpers.each(leftBoxes.concat(rightBoxes), fitBox); + + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); + + // Set the Left and Right margins for the horizontal boxes + helpers.each(topBoxes.concat(bottomBoxes), fitBox); + // Figure out how much margin is on the top and bottom of the vertical boxes helpers.each(topBoxes, function(box) { totalTopBoxesHeight += box.height; }); helpers.each(bottomBoxes, function(box) { totalBottomBoxesHeight += box.height; }); - // Let the left layout know the final margin - helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); - function finalFitVerticalBox(box) { - var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { - return minBoxSize.box === box; + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { + return minSize.box === box; }); var scaleMargin = { left: 0, right: 0, @@ -10321,15 +10876,18 @@ if (minBoxSize) { box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); } } + // Let the left layout know the final margin + helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); + // 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; + totalLeftBoxesWidth = leftPadding; + totalRightBoxesWidth = rightPadding; + totalTopBoxesHeight = topPadding; + totalBottomBoxesHeight = bottomPadding; helpers.each(leftBoxes, function(box) { totalLeftBoxesWidth += box.width; }); @@ -10342,10 +10900,19 @@ }); helpers.each(bottomBoxes, function(box) { totalBottomBoxesHeight += box.height; }); + // We may be adding some padding to account for rotated x axis labels + var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0); + totalLeftBoxesWidth += leftPaddingAddition; + totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0); + + var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0); + totalTopBoxesHeight += topPaddingAddition; + totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 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; @@ -10374,26 +10941,17 @@ maxChartAreaHeight = newMaxChartAreaHeight; maxChartAreaWidth = newMaxChartAreaWidth; } // Step 7 - Position the boxes - var left = xPadding; - var top = yPadding; + var left = leftPadding + leftPaddingAddition; + var top = topPadding + topPaddingAddition; - helpers.each(leftBoxes.concat(topBoxes), placeBox); - - // Account for chart width and height - left += maxChartAreaWidth; - top += maxChartAreaHeight; - - helpers.each(rightBoxes, placeBox); - helpers.each(bottomBoxes, placeBox); - function placeBox(box) { if (box.isHorizontal()) { - box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth; - box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth; + box.left = box.options.fullWidth ? leftPadding : totalLeftBoxesWidth; + box.right = box.options.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; box.top = top; box.bottom = top + box.height; // Move to next point top = box.bottom; @@ -10408,10 +10966,19 @@ // Move to next point left = box.right; } } + helpers.each(leftBoxes.concat(topBoxes), placeBox); + + // Account for chart width and height + left += maxChartAreaWidth; + top += maxChartAreaHeight; + + helpers.each(rightBoxes, placeBox); + helpers.each(bottomBoxes, placeBox); + // Step 8 chartInstance.chartArea = { left: totalLeftBoxesWidth, top: totalTopBoxesHeight, right: totalLeftBoxesWidth + maxChartAreaWidth, @@ -10429,12 +10996,12 @@ }); } }; }; -},{}],29:[function(require,module,exports){ -"use strict"; +},{}],30:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; var noop = helpers.noop; @@ -10457,10 +11024,12 @@ // We hid a dataset ... rerender the chart ci.update(); }, + onHover: null, + labels: { boxWidth: 40, padding: 10, // Generates labels shown in the legend // Valid properties to return: @@ -10494,10 +11063,22 @@ }, this) : []; } } }; + /** + * 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; + } + Chart.Legend = Chart.Element.extend({ initialize: function(config) { helpers.extend(this, config); @@ -10506,11 +11087,11 @@ // Are we in doughnut mode which has a different data type this.doughnutMode = false; }, - // These methods are ordered by lifecyle. Utilities then follow. + // 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 beforeUpdate: noop, update: function(maxWidth, maxHeight, margins) { @@ -10580,14 +11161,24 @@ // beforeBuildLabels: noop, buildLabels: function() { var me = this; - me.legendItems = me.options.labels.generateLabels.call(me, me.chart); - if(me.options.reverse){ - me.legendItems.reverse(); + var labelOpts = me.options.labels; + var legendItems = labelOpts.generateLabels.call(me, me.chart); + + if (labelOpts.filter) { + legendItems = legendItems.filter(function(item) { + return labelOpts.filter(item, me.chart.data); + }); } + + if (me.options.reverse) { + legendItems.reverse(); + } + + me.legendItems = legendItems; }, afterBuildLabels: noop, // @@ -10630,19 +11221,17 @@ // 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; - ctx.textAlign = "left"; + ctx.textAlign = 'left'; ctx.textBaseline = 'top'; helpers.each(me.legendItems, function(legendItem, i) { - var boxWidth = labelOpts.usePointStyle ? - fontSize * Math.sqrt(2) : - labelOpts.boxWidth; - + var boxWidth = getBoxWidth(labelOpts, fontSize); 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; } @@ -10666,14 +11255,11 @@ var currentColWidth = 0; var currentColHeight = 0; var itemHeight = fontSize + vPadding; 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; - + var boxWidth = getBoxWidth(labelOpts, fontSize); var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; // If too tall, go to new column if (currentColHeight + itemHeight > minSize.height) { totalWidth += currentColWidth + labelOpts.padding; @@ -10707,14 +11293,14 @@ }, afterFit: noop, // Shared Methods isHorizontal: function() { - return this.options.position === "top" || this.options.position === "bottom"; + return this.options.position === 'top' || this.options.position === 'bottom'; }, - // Actualy draw the legend on the canvas + // 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, @@ -10731,18 +11317,18 @@ fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily), labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); // Canvas setup - ctx.textAlign = "left"; + 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; - var boxWidth = labelOpts.boxWidth, + var boxWidth = getBoxWidth(labelOpts, fontSize), hitboxes = me.legendHitBoxes; // current position var drawLegendBox = function(x, y, legendItem) { if (isNaN(boxWidth) || boxWidth <= 0) { @@ -10756,30 +11342,32 @@ 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); if (ctx.setLineDash) { // IE 9 and 10 do not support line dash ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash)); } if (opts.labels && opts.labels.usePointStyle) { - // Recalulate x and y for drawPoint() because its expecting + // 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; // Draw pointStyle as legend symbol Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); - } - else { + } else { // Draw box as legend symbol - ctx.strokeRect(x, y, boxWidth, fontSize); + if (!isLineWidthZero) { + ctx.strokeRect(x, y, boxWidth, fontSize); + } ctx.fillRect(x, y, boxWidth, fontSize); } ctx.restore(); }; @@ -10813,28 +11401,24 @@ } 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, + 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++; - } + } else if (y + itemHeight > me.bottom) { + x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; + y = cursor.y = me.top + labelOpts.padding; + cursor.line++; } drawLegendBox(x, y, legendItem); hitboxes[i].left = x; @@ -10851,81 +11435,151 @@ }); } }, - // Handle an event + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @return {Boolean} true if a change occured + */ handleEvent: function(e) { var me = this; - var position = helpers.getRelativePosition(e, me.chart.chart), - x = position.x, - y = position.y, - opts = me.options; + 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; + } + + // Chart event already has relative position in it + var x = e.x, + y = e.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 (opts.onClick) { - opts.onClick.call(me, e, me.legendItems[i]); + if (type === 'click') { + // use e.native for backwards compatibility + opts.onClick.call(me, e.native, me.legendItems[i]); + changed = true; + break; + } else if (type === 'mousemove') { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, me.legendItems[i]); + changed = true; + break; } - break; } } } + + return changed; } }); + function createNewLegendAndAttach(chartInstance, legendOpts) { + var legend = new Chart.Legend({ + ctx: chartInstance.chart.ctx, + options: legendOpts, + chart: chartInstance + }); + chartInstance.legend = legend; + Chart.layoutService.addBox(chartInstance, legend); + } + // Register the legend plugin Chart.plugins.register({ beforeInit: function(chartInstance) { - var opts = chartInstance.options; - var legendOpts = opts.legend; + var legendOpts = chartInstance.options.legend; if (legendOpts) { - chartInstance.legend = new Chart.Legend({ - ctx: chartInstance.chart.ctx, - options: legendOpts, - chart: chartInstance - }); + createNewLegendAndAttach(chartInstance, legendOpts); + } + }, + beforeUpdate: function(chartInstance) { + var legendOpts = chartInstance.options.legend; - Chart.layoutService.addBox(chartInstance, chartInstance.legend); + if (legendOpts) { + legendOpts = helpers.configMerge(Chart.defaults.global.legend, legendOpts); + + if (chartInstance.legend) { + chartInstance.legend.options = legendOpts; + } else { + createNewLegendAndAttach(chartInstance, legendOpts); + } + } else { + Chart.layoutService.removeBox(chartInstance, chartInstance.legend); + delete chartInstance.legend; } + }, + afterEvent: function(chartInstance, e) { + var legend = chartInstance.legend; + if (legend) { + legend.handleEvent(e); + } } }); }; -},{}],30:[function(require,module,exports){ -"use strict"; +},{}],31:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { - var noop = Chart.helpers.noop; + var helpers = Chart.helpers; + Chart.defaults.global.plugins = {}; + /** * The plugin service singleton * @namespace Chart.plugins * @since 2.1.0 */ Chart.plugins = { + /** + * Globally registered plugins. + * @private + */ _plugins: [], /** + * This identifier is used to invalidate the descriptors cache attached to each chart + * when a global plugin is registered or unregistered. In this case, the cache ID is + * incremented and descriptors are regenerated during following API calls. + * @private + */ + _cacheId: 0, + + /** * 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); } }); + + this._cacheId++; }, /** * Unregisters the given plugin(s) only if registered. * @param {Array|Object} plugins plugin instance(s). @@ -10936,18 +11590,21 @@ var idx = p.indexOf(plugin); if (idx !== -1) { p.splice(idx, 1); } }); + + this._cacheId++; }, /** - * Remove all registered p^lugins. + * Remove all registered plugins. * @since 2.1.5 */ clear: function() { this._plugins = []; + this._cacheId++; }, /** * Returns the number of registered plugins? * @returns {Number} @@ -10956,103 +11613,280 @@ count: function() { return this._plugins.length; }, /** - * Returns all registered plugin intances. + * Returns all registered plugin instances. * @returns {Array} array of plugin objects. * @since 2.1.5 */ getAll: function() { return this._plugins; }, /** - * Calls registered plugins on the specified extension, with the given args. This - * method immediately returns as soon as a plugin explicitly returns false. The + * Calls enabled plugins for `chart` on the specified hook and 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. + * @param {Object} chart - The chart instance for which plugins should be called. + * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). + * @param {Array} [args] - Extra arguments to apply to the hook 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; + notify: function(chart, hook, args) { + var descriptors = this.descriptors(chart); + var ilen = descriptors.length; + var i, descriptor, plugin, params, method; for (i=0; i<ilen; ++i) { - plugin = plugins[i]; - if (typeof plugin[extension] === 'function') { - if (plugin[extension].apply(plugin, args || []) === false) { + descriptor = descriptors[i]; + plugin = descriptor.plugin; + method = plugin[hook]; + if (typeof method === 'function') { + params = [chart].concat(args || []); + params.push(descriptor.options); + if (method.apply(plugin, params) === false) { return false; } } } return true; - } - }; + }, - /** - * Plugin extension methods. - * @interface Chart.PluginBase - * @since 2.1.0 - */ - Chart.PluginBase = Chart.Element.extend({ - // Called at start of chart init - beforeInit: noop, + /** + * Returns descriptors of enabled plugins for the given chart. + * @returns {Array} [{ plugin, options }] + * @private + */ + descriptors: function(chart) { + var cache = chart._plugins || (chart._plugins = {}); + if (cache.id === this._cacheId) { + return cache.descriptors; + } - // Called at end of chart init - afterInit: noop, + var plugins = []; + var descriptors = []; + var config = (chart && chart.config) || {}; + var defaults = Chart.defaults.global.plugins; + var options = (config.options && config.options.plugins) || {}; - // Called at start of update - beforeUpdate: noop, + this._plugins.concat(config.plugins || []).forEach(function(plugin) { + var idx = plugins.indexOf(plugin); + if (idx !== -1) { + return; + } - // Called at end of update - afterUpdate: noop, + var id = plugin.id; + var opts = options[id]; + if (opts === false) { + return; + } - // Called at start of draw - beforeDraw: noop, + if (opts === true) { + opts = helpers.clone(defaults[id]); + } - // Called at end of draw - afterDraw: noop, + plugins.push(plugin); + descriptors.push({ + plugin: plugin, + options: opts || {} + }); + }); - // Called during destroy - destroy: noop - }); + cache.descriptors = descriptors; + cache.id = this._cacheId; + return descriptors; + } + }; /** + * Plugin extension hooks. + * @interface IPlugin + * @since 2.1.0 + */ + /** + * @method IPlugin#beforeInit + * @desc Called before initializing `chart`. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#afterInit + * @desc Called after `chart` has been initialized and before the first update. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeUpdate + * @desc Called before updating `chart`. If any plugin returns `false`, the update + * is cancelled (and thus subsequent render(s)) until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart update. + */ + /** + * @method IPlugin#afterUpdate + * @desc Called after `chart` has been updated and before rendering. Note that this + * hook will not be called if the chart update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetsUpdate + * @desc Called before updating the `chart` datasets. If any plugin returns `false`, + * the datasets update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} false to cancel the datasets update. + * @since version 2.1.5 + */ + /** + * @method IPlugin#afterDatasetsUpdate + * @desc Called after the `chart` datasets have been updated. Note that this hook + * will not be called if the datasets update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @since version 2.1.5 + */ + /** + * @method IPlugin#beforeLayout + * @desc Called before laying out `chart`. If any plugin returns `false`, + * the layout update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart layout. + */ + /** + * @method IPlugin#afterLayout + * @desc Called after the `chart` has been layed out. Note that this hook will not + * be called if the layout update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeRender + * @desc Called before rendering `chart`. If any plugin returns `false`, + * the rendering is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart rendering. + */ + /** + * @method IPlugin#afterRender + * @desc Called after the `chart` has been fully rendered (and animation completed). Note + * that this hook will not be called if the rendering has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDraw + * @desc Called before drawing `chart` at every animation frame specified by the given + * easing value. If any plugin returns `false`, the frame drawing is cancelled until + * another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart drawing. + */ + /** + * @method IPlugin#afterDraw + * @desc Called after the `chart` has been drawn for the specific easing value. Note + * that this hook will not be called if the drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetsDraw + * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, + * the datasets drawing is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ + /** + * @method IPlugin#afterDatasetsDraw + * @desc Called after the `chart` datasets have been drawn. Note that this hook + * will not be called if the datasets drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeEvent + * @desc Called before processing the specified `event`. If any plugin returns `false`, + * the event will be discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#afterEvent + * @desc Called after the `event` has been consumed. Note that this hook + * will not be called if the `event` has been previously discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#resize + * @desc Called after the chart as been resized. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} size - The new canvas display size (eq. canvas.style width & height). + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#destroy + * @desc Called after the chart as been destroyed. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + + /** * Provided for backward compatibility, use Chart.plugins instead * @namespace Chart.pluginService * @deprecated since version 2.1.5 - * @todo remove me at version 3 + * @todo remove at version 3 + * @private */ Chart.pluginService = Chart.plugins; + + /** + * Provided for backward compatibility, inheriting from Chart.PlugingBase has no + * effect, instead simply create/register plugins via plain JavaScript objects. + * @interface Chart.PluginBase + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + Chart.PluginBase = helpers.inherits({}); }; -},{}],31:[function(require,module,exports){ -"use strict"; +},{}],32:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.scale = { display: true, - position: "left", + position: 'left', // grid line settings gridLines: { display: true, - color: "rgba(0, 0, 0, 0.1)", + 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)", + zeroLineColor: 'rgba(0,0,0,0.25)', offsetGridLines: false, borderDash: [], borderDashOffset: 0.0 }, @@ -11069,24 +11903,58 @@ ticks: { beginAtZero: false, minRotation: 0, maxRotation: 50, mirror: false, - padding: 10, + padding: 0, 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; - } + callback: Chart.Ticks.formatters.values } }; + function computeTextSize(context, tick, font) { + return helpers.isArray(tick) ? + helpers.longestText(context, font, tick) : + context.measureText(tick).width; + } + + function parseFontOptions(options) { + var getValueOrDefault = helpers.getValueOrDefault; + var globalDefaults = Chart.defaults.global; + var size = getValueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var style = getValueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); + var family = getValueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); + + return { + size: size, + style: style, + family: family, + font: helpers.fontString(size, style, family) + }; + } + Chart.Scale = Chart.Element.extend({ + /** + * Get the padding needed for the scale + * @method getPadding + * @private + * @returns {Padding} the necessary padding + */ + getPadding: function() { + var me = this; + return { + left: me.paddingLeft || 0, + top: me.paddingTop || 0, + right: me.paddingRight || 0, + bottom: me.paddingBottom || 0 + }; + }, // 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 @@ -11106,10 +11974,11 @@ left: 0, right: 0, top: 0, bottom: 0 }, margins); + me.longestTextCache = me.longestTextCache || {}; // Dimensions me.beforeSetDimensions(); me.setDimensions(); me.afterSetDimensions(); @@ -11199,17 +12068,12 @@ 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); + var tickOpts = me.options.ticks; + me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback); }, afterTickToLabelConversion: function() { helpers.callCallback(this.options.afterTickToLabelConversion, [this]); }, @@ -11219,76 +12083,46 @@ 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; + var tickOpts = me.options.ticks; - //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; + // Get the width of each grid by calculating the difference + // between x offsets between 0 and 1. + var tickFont = parseFontOptions(tickOpts); + context.font = tickFont.font; - var firstWidth = context.measureText(me.ticks[0]).width; - var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width; - var firstRotated; + var labelRotation = tickOpts.minRotation || 0; - me.labelRotation = optionTicks.minRotation || 0; - me.paddingRight = 0; - me.paddingLeft = 0; + if (me.options.display && me.isHorizontal()) { + var originalLabelWidth = helpers.longestText(context, tickFont.font, me.ticks, me.longestTextCache); + var labelWidth = originalLabelWidth; + var cosRotation; + var sinRotation; - if (me.options.display) { - if (me.isHorizontal()) { - me.paddingRight = lastWidth / 2 + 3; - me.paddingLeft = firstWidth / 2 + 3; + // Allow 3 pixels x2 padding either side for label readability + var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; - if (!me.longestTextCache) { - me.longestTextCache = {}; - } - var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache); - var labelWidth = originalLabelWidth; - var cosRotation; - var sinRotation; + // Max label rotation can be set or default to 90 - also act as a loop counter + while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { + var angleRadians = helpers.toRadians(labelRotation); + cosRotation = Math.cos(angleRadians); + sinRotation = Math.sin(angleRadians); - // 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; - - //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)); - - firstRotated = cosRotation * firstWidth; - - // We're right aligning the text now. - if (firstRotated + tickFontSize / 2 > me.yLabelWidth) { - me.paddingLeft = firstRotated + tickFontSize / 2; - } - - me.paddingRight = tickFontSize / 2; - - if (sinRotation * originalLabelWidth > me.maxHeight) { - // go back one step - me.labelRotation--; - break; - } - - me.labelRotation++; - labelWidth = cosRotation * originalLabelWidth; + if (sinRotation * originalLabelWidth > me.maxHeight) { + // go back one step + labelRotation--; + break; } + + 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); - } + me.labelRotation = labelRotation; }, afterCalculateTickRotation: function() { helpers.callCallback(this.options.afterCalculateTickRotation, [this]); }, @@ -11304,24 +12138,18 @@ width: 0, height: 0 }; 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(); - 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); - - var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize); - + var tickFont = parseFontOptions(tickOpts); + var scaleLabelFontSize = parseFontOptions(scaleLabelOpts).size * 1.5; var tickMarkLength = opts.gridLines.tickMarkLength; // Width if (isHorizontal) { // subtract the margins to line up with the chartArea if we are a full width scale @@ -11338,89 +12166,95 @@ } // Are we showing a title for the scale? if (scaleLabelOpts.display && display) { if (isHorizontal) { - minSize.height += (scaleLabelFontSize * 1.5); + minSize.height += scaleLabelFontSize; } else { - minSize.width += (scaleLabelFontSize * 1.5); + minSize.width += scaleLabelFontSize; } } + // Don't bother fitting the ticks if we are not showing them if (tickOpts.display && display) { - // Don't bother fitting the ticks if we are not showing them - if (!me.longestTextCache) { - me.longestTextCache = {}; - } - - var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache); + var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, me.ticks, me.longestTextCache); var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks); - var lineSpace = tickFontSize * 0.5; + var lineSpace = tickFont.size * 0.5; if (isHorizontal) { // A horizontal axis is more constrained by the height. me.longestLabelWidth = largestTextWidth; + var angleRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(angleRadians); + var sinRotation = Math.sin(angleRadians); + // TODO - improve this calculation - var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines); + var labelHeight = (sinRotation * largestTextWidth) + + (tickFont.size * tallestLabelHeightInLines) + + (lineSpace * tallestLabelHeightInLines); minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight); - me.ctx.font = tickLabelFont; + me.ctx.font = tickFont.font; - var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width; - var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width; + var firstTick = me.ticks[0]; + var firstLabelWidth = computeTextSize(me.ctx, firstTick, tickFont.font); + var lastTick = me.ticks[me.ticks.length - 1]; + var lastLabelWidth = computeTextSize(me.ctx, lastTick, tickFont.font); + // 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 + if (me.labelRotation !== 0) { + me.paddingLeft = opts.position === 'bottom'? (cosRotation * firstLabelWidth) + 3: (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges + me.paddingRight = opts.position === 'bottom'? (cosRotation * lineSpace) + 3: (cosRotation * lastLabelWidth) + 3; + } else { + me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges + me.paddingRight = lastLabelWidth / 2 + 3; + } } 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; - // 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; - } - if (largestTextWidth < maxLabelWidth) { - // We don't need all the room - minSize.width += largestTextWidth; + if (tickOpts.mirror) { + largestTextWidth = 0; } else { - // Expand to max size - minSize.width = me.maxWidth; + largestTextWidth += me.options.ticks.padding; } - - me.paddingTop = tickFontSize / 2; - me.paddingBottom = tickFontSize / 2; + minSize.width += largestTextWidth; + me.paddingTop = tickFont.size / 2; + me.paddingBottom = tickFont.size / 2; } } + me.handleMargins(); + + me.width = minSize.width; + me.height = minSize.height; + }, + + /** + * Handle margins and padding interactions + * @private + */ + handleMargins: function() { + var me = this; 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); } - - me.width = minSize.width; - me.height = minSize.height; - }, + afterFit: function() { helpers.callCallback(this.options.afterFit, [this]); }, // Shared Methods isHorizontal: function() { - return this.options.position === "top" || this.options.position === "bottom"; + return this.options.position === 'top' || this.options.position === 'bottom'; }, isFullWidth: function() { return (this.options.fullWidth); }, @@ -11428,21 +12262,20 @@ 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)) { + // 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 (typeof(rawValue) === 'object') { if ((rawValue instanceof Date) || (rawValue.isValid)) { return rawValue; - } else { - return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); } + return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); } // Value is good, return it return rawValue; }, @@ -11470,44 +12303,45 @@ } 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 innerHeight = me.height - (me.paddingTop + me.paddingBottom); + return me.top + (index * (innerHeight / (me.ticks.length - 1))); }, // Utility for getting the pixel location of a percentage of scale - getPixelForDecimal: function(decimal /*, includeOffset*/ ) { + 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 finalVal = me.left + Math.round(valueOffset); finalVal += me.isFullWidth() ? me.margins.left : 0; return finalVal; - } else { - return me.top + (decimal * me.height); } + return me.top + (decimal * me.height); }, getBasePixel: function() { + return this.getPixelForValue(this.getBaseValue()); + }, + + getBaseValue: function() { var me = this; var min = me.min; var max = me.max; - return me.getPixelForValue( - me.beginAtZero? 0: + return me.beginAtZero ? 0: min < 0 && max < 0? max : min > 0 && max > 0? min : - 0); + 0; }, - // Actualy draw the scale on the canvas + // 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) { @@ -11530,23 +12364,18 @@ if (optionTicks.maxTicksLimit) { maxTicks = optionTicks.maxTicksLimit; } 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 tickFont = parseFontOptions(optionTicks); + + var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; var borderDash = helpers.getValueOrDefault(gridLines.borderDash, globalDefaults.borderDash); var borderDashOffset = helpers.getValueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); 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); + var scaleLabelFont = parseFontOptions(scaleLabel); var labelRotationRadians = helpers.toRadians(me.labelRotation); var cosRotation = Math.cos(labelRotationRadians); var longestRotatedLabel = me.longestLabelWidth * cosRotation; @@ -11583,14 +12412,14 @@ skipRatio = false; } } - 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 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; 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; @@ -11607,55 +12436,57 @@ var lineWidth, lineColor; if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) { // Draw the first index specially lineWidth = gridLines.zeroLineWidth; lineColor = gridLines.zeroLineColor; - } else { + } else { lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index); lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index); } // Common properties var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; - var textAlign, textBaseline = 'middle'; + var textAlign = 'middle'; + var textBaseline = 'middle'; if (isHorizontal) { - if (!isRotated) { - textBaseline = options.position === 'top' ? 'bottom' : 'top'; + + if (options.position === 'bottom') { + // bottom + textBaseline = !isRotated? 'top':'middle'; + textAlign = !isRotated? 'center': 'right'; + labelY = me.top + tl; + } else { + // top + textBaseline = !isRotated? 'bottom':'middle'; + textAlign = !isRotated? 'center': 'left'; + labelY = me.bottom - tl; } - textAlign = isRotated ? 'right' : 'center'; - 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; 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'; - } + var isLeft = options.position === 'left'; + var tickPadding = optionTicks.padding; + var labelXOffset; + + if (optionTicks.mirror) { + textAlign = isLeft ? 'left' : 'right'; + labelXOffset = tickPadding; } else { - // right side - if (optionTicks.mirror) { - labelX = me.left - optionTicks.padding; - textAlign = 'right'; - } else { - labelX = me.left + optionTicks.padding; - textAlign = 'left'; - } + textAlign = isLeft ? 'right' : 'left'; + labelXOffset = tl + tickPadding; } + labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; + var yLineValue = me.getPixelForTick(index); // xvalues for grid lines yLineValue += helpers.aliasPixel(lineWidth); labelY = me.getPixelForTick(index, gridLines.offsetGridLines); tx1 = xTickStart; @@ -11716,21 +12547,21 @@ if (optionTicks.display) { context.save(); context.translate(itemToDraw.labelX, itemToDraw.labelY); context.rotate(itemToDraw.rotation); - context.font = tickLabelFont; + context.font = tickFont.font; context.textBaseline = itemToDraw.textBaseline; context.textAlign = itemToDraw.textAlign; 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); + y += (tickFont.size * 1.5); } } else { context.fillText(label, 0, 0); } context.restore(); @@ -11743,25 +12574,25 @@ 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); + scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFont.size / 2) : me.top + (scaleLabelFont.size / 2); } else { var isLeft = options.position === 'left'; - scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2); + scaleLabelX = isLeft ? me.left + (scaleLabelFont.size / 2) : me.right - (scaleLabelFont.size / 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.font = scaleLabelFont.font; context.fillText(scaleLabel.labelString, 0, 0); context.restore(); } if (gridLines.drawBorder) { @@ -11791,12 +12622,12 @@ } } }); }; -},{}],32:[function(require,module,exports){ -"use strict"; +},{}],33:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; @@ -11832,17 +12663,228 @@ Chart.layoutService.addBox(chartInstance, scale); }); } }; }; -},{}],33:[function(require,module,exports){ -"use strict"; +},{}],34:[function(require,module,exports){ +'use strict'; + module.exports = function(Chart) { var helpers = Chart.helpers; + /** + * 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 + */ + + /** + * 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. + + 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; + + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (generationOptions.min && generationOptions.max && generationOptions.stepSize) { + // If very close to our whole number, use it. + if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { + niceMin = generationOptions.min; + niceMax = generationOptions.max; + } + } + + 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); + } + + // 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); + + return ticks; + }, + + /** + * 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; + + // 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)))); + + var endExp = Math.floor(helpers.log10(dataRange.max)); + var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + var exp; + var significand; + + if (tickVal === 0) { + exp = Math.floor(helpers.log10(dataRange.minNotZero)); + significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); + + ticks.push(tickVal); + tickVal = significand * Math.pow(10, exp); + } else { + exp = Math.floor(helpers.log10(tickVal)); + significand = Math.floor(tickVal / Math.pow(10, exp)); + } + + do { + ticks.push(tickVal); + + ++significand; + if (significand === 10) { + significand = 1; + ++exp; + } + + tickVal = significand * Math.pow(10, exp); + } while (exp < endExp || (exp === endExp && significand < endSignificand)); + + var lastTick = getValueOrDefault(generationOptions.max, tickVal); + ticks.push(lastTick); + + 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; + }, + + /** + * 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 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); + } + } + + var logDelta = helpers.log10(Math.abs(delta)); + var tickString = ''; + + 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 + } + + return tickString; + }, + + logarithmic: function(tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); + + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; + } + } + }; +}; + +},{}],35:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + 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) @@ -11857,24 +12899,18 @@ Chart.Title = Chart.Element.extend({ initialize: function(config) { var me = this; helpers.extend(me, config); - me.options = helpers.configMerge(Chart.defaults.global.title, config.options); // Contains hit boxes for each dataset (in dataset order) me.legendHitBoxes = []; }, - // These methods are ordered by lifecyle. Utilities then follow. + // These methods are ordered by lifecycle. Utilities then follow. - beforeUpdate: function () { - var chartOpts = this.chart.options; - if (chartOpts && chartOpts.title) { - this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title); - } - }, + beforeUpdate: noop, update: function(maxWidth, maxHeight, margins) { var me = this; // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) me.beforeUpdate(); @@ -11971,14 +13007,14 @@ afterFit: noop, // Shared Methods isHorizontal: function() { var pos = this.options.position; - return pos === "top" || pos === "bottom"; + return pos === 'top' || pos === 'bottom'; }, - // Actualy draw the title block on the canvas + // Actually draw the title block on the canvas draw: function() { var me = this, ctx = me.ctx, valueOrDefault = helpers.getValueOrDefault, opts = me.options, @@ -11993,87 +13029,118 @@ titleX, titleY, top = me.top, left = me.left, bottom = me.bottom, - right = me.right; + right = me.right, + maxWidth; ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour ctx.font = titleFont; // 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); } ctx.save(); ctx.translate(titleX, titleY); ctx.rotate(rotation); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(opts.text, 0, 0); + ctx.fillText(opts.text, 0, 0, maxWidth); ctx.restore(); } } }); + function createNewTitleBlockAndAttach(chartInstance, titleOpts) { + var title = new Chart.Title({ + ctx: chartInstance.chart.ctx, + options: titleOpts, + chart: chartInstance + }); + chartInstance.titleBlock = title; + Chart.layoutService.addBox(chartInstance, title); + } + // Register the title plugin Chart.plugins.register({ beforeInit: function(chartInstance) { - var opts = chartInstance.options; - var titleOpts = opts.title; + var titleOpts = chartInstance.options.title; if (titleOpts) { - chartInstance.titleBlock = new Chart.Title({ - ctx: chartInstance.chart.ctx, - options: titleOpts, - chart: chartInstance - }); + createNewTitleBlockAndAttach(chartInstance, titleOpts); + } + }, + beforeUpdate: function(chartInstance) { + var titleOpts = chartInstance.options.title; - Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock); + if (titleOpts) { + titleOpts = helpers.configMerge(Chart.defaults.global.title, titleOpts); + + if (chartInstance.titleBlock) { + chartInstance.titleBlock.options = titleOpts; + } else { + createNewTitleBlockAndAttach(chartInstance, titleOpts); + } + } else { + Chart.layoutService.removeBox(chartInstance, chartInstance.titleBlock); + delete chartInstance.titleBlock; } } }); }; -},{}],34:[function(require,module,exports){ -"use strict"; +},{}],36:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; + /** + * 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(); + } + Chart.defaults.global.tooltips = { enabled: true, custom: null, - mode: 'single', - backgroundColor: "rgba(0,0,0,0.8)", - titleFontStyle: "bold", + mode: 'nearest', + position: 'average', + intersect: true, + backgroundColor: 'rgba(0,0,0,0.8)', + titleFontStyle: 'bold', titleSpacing: 2, titleMarginBottom: 6, - titleFontColor: "#fff", - titleAlign: "left", + titleFontColor: '#fff', + titleAlign: 'left', bodySpacing: 2, - bodyFontColor: "#fff", - bodyAlign: "left", - footerFontStyle: "bold", + bodyFontColor: '#fff', + bodyAlign: 'left', + footerFontStyle: 'bold', footerSpacing: 2, footerMarginTop: 6, - footerFontColor: "#fff", - footerAlign: "left", + footerFontColor: '#fff', + footerAlign: 'left', yPadding: 6, xPadding: 6, - yAlign : 'center', - xAlign : 'center', 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 @@ -12127,54 +13194,21 @@ // 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); + // base = base.concat(toPush); Array.prototype.push.apply(base, toPush); } else { base.push(toPush); } } return base; } - function getAveragePosition(elements) { - if (!elements.length) { - return false; - } - - var i, len; - var xPositions = []; - var yPositions = []; - - 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); - } - } - - 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) - }; - } - - // Private helper to create a tooltip iteam model + // 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 @@ -12183,63 +13217,259 @@ return { xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', index: index, - datasetIndex: datasetIndex + datasetIndex: datasetIndex, + x: element._model.x, + y: element._model.y }; } - Chart.Tooltip = Chart.Element.extend({ - initialize: function() { - var me = this; - var globalDefaults = Chart.defaults.global; - var tooltipOpts = me._options; - var getValueOrDefault = helpers.getValueOrDefault; + /** + * 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; - helpers.extend(me, { - _model: { - // Positioning - xPadding: tooltipOpts.xPadding, - yPadding: tooltipOpts.yPadding, - xAlign : tooltipOpts.xAlign, - yAlign : tooltipOpts.yAlign, + return { + // Positioning + xPadding: tooltipOpts.xPadding, + yPadding: tooltipOpts.yPadding, + xAlign: tooltipOpts.xAlign, + yAlign: tooltipOpts.yAlign, - // 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, + // 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, - // 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, + // 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, - // 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, + // 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, - // Appearance - caretSize: tooltipOpts.caretSize, - cornerRadius: tooltipOpts.cornerRadius, - backgroundColor: tooltipOpts.backgroundColor, - opacity: 0, - legendColorBackground: tooltipOpts.multiKeyBackground - } - }); + // Appearance + caretSize: tooltipOpts.caretSize, + cornerRadius: tooltipOpts.cornerRadius, + backgroundColor: tooltipOpts.backgroundColor, + opacity: 0, + legendColorBackground: tooltipOpts.multiKeyBackground, + displayColors: tooltipOpts.displayColors + }; + } + + /** + * Get the size of the tooltip + */ + function getTooltipSize(tooltip, model) { + var ctx = tooltip._chart.ctx; + + var height = model.yPadding * 2; // Tooltip Padding + var width = 0; + + // 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; + + var titleLineCount = model.title.length; + var footerLineCount = model.footer.length; + var titleFontSize = model.titleFontSize, + bodyFontSize = model.bodyFontSize, + footerFontSize = model.footerFontSize; + + 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 + + // Title width + var widthPadding = 0; + var maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; + + ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); + helpers.each(model.title, maxLineWidth); + + // Body width + ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); + helpers.each(model.beforeBody.concat(model.afterBody), 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); + }); + + // Reset back to 0 + widthPadding = 0; + + // Footer width + ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); + helpers.each(model.footer, maxLineWidth); + + // Add padding + width += 2 * model.xPadding; + + return { + width: width, + height: height + }; + } + + /** + * 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'; + + if (model.y < size.height) { + yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + yAlign = 'bottom'; + } + + 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; + + 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)); + }; + } + + 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 (lf(model.x)) { + xAlign = 'left'; + + // 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'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } + + var opts = tooltip._options; + return { + xAlign: opts.xAlign ? opts.xAlign : xAlign, + yAlign: opts.yAlign ? opts.yAlign : yAlign + }; + } + + /** + * @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; + + var caretSize = vm.caretSize, + caretPadding = vm.caretPadding, + cornerRadius = vm.cornerRadius, + xAlign = alignment.xAlign, + yAlign = alignment.yAlign, + paddingAndSize = caretSize + caretPadding, + radiusAndPadding = cornerRadius + caretPadding; + + if (xAlign === 'right') { + x -= size.width; + } else if (xAlign === 'center') { + x -= (size.width / 2); + } + + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= size.height + paddingAndSize; + } else { + y -= (size.height / 2); + } + + 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; + } + + return { + x: x, + y: y + }; + } + + Chart.Tooltip = Chart.Element.extend({ + initialize: function() { + this._model = getBaseModel(this._options); }, // Get the title // Args are: (tooltipItem, data) getTitle: function() { @@ -12312,240 +13542,114 @@ }, update: function(changed) { var me = this; var opts = me._options; - var model = me._model; + + // 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; var data = me._data; var chartInstance = me._chartInstance; + // 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 + }; + var i, len; if (active.length) { model.opacity = 1; - var labelColors = [], - tooltipPosition = getAveragePosition(active); + var labelColors = []; + tooltipPosition = Chart.Tooltip.positioners[opts.position](active, me._eventPosition); var tooltipItems = []; for (i = 0, len = active.length; i < len; ++i) { tooltipItems.push(createTooltipItem(active[i])); } - // 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 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); }); } - // 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)); + // 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); }); } - // 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 + // Determine colors for boxes + helpers.each(tooltipItems, function(tooltipItem) { + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance)); }); - // We need to determine alignment of - var tooltipSize = me.getTooltipSize(model); - me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas + // 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); - helpers.extend(model, me.getBackgroundPoint(model, tooltipSize)); - } else { - me._model.opacity = 0; - } + // 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; - if (changed && opts.custom) { - opts.custom.call(me, model); - } + // data points + model.dataPoints = tooltipItems; - return me; - }, - getTooltipSize: function(vm) { - var ctx = this._chart.ctx; - - var size = { - height: vm.yPadding * 2, // Tooltip Padding - width: 0 - }; - - // 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; - - var titleLineCount = vm.title.length; - var footerLineCount = vm.footer.length; - var titleFontSize = vm.titleFontSize, - bodyFontSize = vm.bodyFontSize, - footerFontSize = vm.footerFontSize; - - 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 - - // Title width - var widthPadding = 0; - var maxLineWidth = function(line) { - size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding); - }; - - ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); - helpers.each(vm.title, maxLineWidth); - - // Body width - ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); - helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth); - - // 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); - }); - - // Reset back to 0 - widthPadding = 0; - - // Footer width - ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); - helpers.each(vm.footer, maxLineWidth); - - // Add padding - size.width += 2 * vm.xPadding; - - return size; - }, - determineAlignment: function(size) { - var me = this; - var model = me._model; - var chart = me._chart; - var chartArea = me._chartInstance.chartArea; - - if (model.y < size.height) { - model.yAlign = 'top'; - } else if (model.y > (chart.height - size.height)) { - model.yAlign = 'bottom'; - } - - 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; - - if (model.yAlign === 'center') { - lf = function(x) { - return x <= midX; - }; - rf = function(x) { - return x > midX; - }; + // 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 { - lf = function(x) { - return x <= (size.width / 2); - }; - rf = function(x) { - return x >= (chart.width - (size.width / 2)); - }; + model.opacity = 0; } - 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'; - }; + model.xAlign = alignment.xAlign; + model.yAlign = alignment.yAlign; + model.x = backgroundPoint.x; + model.y = backgroundPoint.y; + model.width = tooltipSize.width; + model.height = tooltipSize.height; - if (lf(model.x)) { - model.xAlign = 'left'; + // Point where the caret on the tooltip points to + model.caretX = tooltipPosition.x; + model.caretY = tooltipPosition.y; - // 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'; + me._model = model; - // Is tooltip too wide and goes outside left edge of canvas? - if (orf(model.x)) { - model.xAlign = 'center'; - model.yAlign = yf(model.y); - } + if (changed && opts.custom) { + opts.custom.call(me, model); } - }, - getBackgroundPoint: function(vm, size) { - // Background Position - var pt = { - x: vm.x, - y: vm.y - }; - var caretSize = vm.caretSize, - caretPadding = vm.caretPadding, - cornerRadius = vm.cornerRadius, - xAlign = vm.xAlign, - yAlign = vm.yAlign, - paddingAndSize = caretSize + caretPadding, - radiusAndPadding = cornerRadius + caretPadding; - - if (xAlign === 'right') { - pt.x -= size.width; - } else if (xAlign === 'center') { - pt.x -= (size.width / 2); - } - - if (yAlign === 'top') { - pt.y += paddingAndSize; - } else if (yAlign === 'bottom') { - pt.y -= size.height + paddingAndSize; - } else { - pt.y -= (size.height / 2); - } - - 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; - } - } - - return pt; + return me; }, drawCaret: function(tooltipPoint, size, opacity) { var vm = this._view; var ctx = this._chart.ctx; var x1, x2, x3; @@ -12598,12 +13702,11 @@ y2 = y1 + caretSize; y3 = y1; } } - var bgColor = helpers.color(vm.backgroundColor); - ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); + ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.lineTo(x3, y3); ctx.closePath(); @@ -12612,17 +13715,16 @@ drawTitle: function(pt, vm, ctx, opacity) { var title = vm.title; if (title.length) { ctx.textAlign = vm._titleAlign; - ctx.textBaseline = "top"; + ctx.textBaseline = 'top'; var titleFontSize = vm.titleFontSize, titleSpacing = vm.titleSpacing; - var titleFontColor = helpers.color(vm.titleFontColor); - ctx.fillStyle = titleFontColor.alpha(opacity * titleFontColor.alpha()).rgbString(); + ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); var i, len; for (i = 0, len = title.length; i < len; ++i) { ctx.fillText(title[i], pt.x, pt.y); @@ -12638,14 +13740,13 @@ var bodyFontSize = vm.bodyFontSize; var bodySpacing = vm.bodySpacing; var body = vm.body; ctx.textAlign = vm._bodyAlign; - ctx.textBaseline = "top"; + ctx.textBaseline = 'top'; - var bodyFontColor = helpers.color(vm.bodyFontColor); - var textColor = bodyFontColor.alpha(opacity * bodyFontColor.alpha()).rgbString(); + var textColor = mergeOpacity(vm.bodyFontColor, opacity); ctx.fillStyle = textColor; ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); // Before Body var xLinePadding = 0; @@ -12655,30 +13756,30 @@ }; // Before body lines helpers.each(vm.beforeBody, fillLineOfText); - var drawColorBoxes = body.length > 1; + var drawColorBoxes = vm.displayColors; xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; // Draw body lines now helpers.each(body, function(bodyItem, i) { helpers.each(bodyItem.before, fillLineOfText); 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.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Border - ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); + ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Inner square - ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); + ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); ctx.fillStyle = textColor; } @@ -12700,45 +13801,49 @@ if (footer.length) { pt.y += vm.footerMarginTop; ctx.textAlign = vm._footerAlign; - ctx.textBaseline = "top"; + ctx.textBaseline = 'top'; - var footerFontColor = helpers.color(vm.footerFontColor); - ctx.fillStyle = footerFontColor.alpha(opacity * footerFontColor.alpha()).rgbString(); + ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); 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 (vm.opacity === 0) { return; } - var tooltipSize = this.getTooltipSize(vm); + var tooltipSize = { + width: vm.width, + height: vm.height + }; var pt = { x: vm.x, y: vm.y }; // IE11/Edge does not like very small opacities, so snap to 0 var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; 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(); + this.drawBackground(pt, vm, ctx, tooltipSize, opacity); // Draw Caret this.drawCaret(pt, tooltipSize, opacity); // Draw Title, Body, and Footer @@ -12752,111 +13857,239 @@ this.drawBody(pt, vm, ctx, opacity); // Footer this.drawFooter(pt, vm, ctx, opacity); } + }, + + /** + * Handle an event + * @private + * @param {IEvent} 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; + + me._lastActive = me._lastActive || []; + + // Find Active Elements for tooltips + if (e.type === 'mouseout') { + me._active = []; + } else { + me._active = me._chartInstance.getElementsAtEventForMode(e, options.mode, options); + } + + // Remember Last Actives + changed = !helpers.arrayEquals(me._active, me._lastActive); + me._lastActive = me._active; + + if (options.enabled || options.custom) { + me._eventPosition = { + x: e.x, + y: e.y + }; + + 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 + }; + } + }; }; -},{}],35:[function(require,module,exports){ -"use strict"; +},{}],37:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { - var helpers = Chart.helpers, - globalOpts = Chart.defaults.global; + var helpers = Chart.helpers, + globalOpts = Chart.defaults.global; - globalOpts.elements.arc = { - backgroundColor: globalOpts.defaultColor, - borderColor: "#fff", - borderWidth: 2 - }; + globalOpts.elements.arc = { + backgroundColor: globalOpts.defaultColor, + borderColor: '#fff', + borderWidth: 2 + }; - Chart.elements.Arc = Chart.Element.extend({ - inLabelRange: function(mouseX) { - var vm = this._view; + Chart.elements.Arc = Chart.Element.extend({ + inLabelRange: function(mouseX) { + var vm = this._view; - 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; + 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; - if (vm) { - var pointRelativePosition = helpers.getAngleFromPoint(vm, { - x: chartX, - y: chartY - }), - angle = pointRelativePosition.angle, - distance = pointRelativePosition.distance; + if (vm) { + var pointRelativePosition = helpers.getAngleFromPoint(vm, { + x: chartX, + y: chartY + }), + angle = pointRelativePosition.angle, + distance = pointRelativePosition.distance; - //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; - } + // 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); + // 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); - } else { - return false; - } - }, - tooltipPosition: function() { - var vm = this._view; + 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 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; + var ctx = this._chart.ctx, + vm = this._view, + sA = vm.startAngle, + eA = vm.endAngle; - ctx.beginPath(); + ctx.beginPath(); - ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); - ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); + 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.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; - ctx.fillStyle = vm.backgroundColor; + ctx.fillStyle = vm.backgroundColor; - ctx.fill(); - ctx.lineJoin = 'bevel'; + ctx.fill(); + ctx.lineJoin = 'bevel'; - if (vm.borderWidth) { - ctx.stroke(); - } - } - }); + if (vm.borderWidth) { + ctx.stroke(); + } + } + }); }; -},{}],36:[function(require,module,exports){ -"use strict"; +},{}],38:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; var globalDefaults = Chart.defaults.global; @@ -12869,40 +14102,49 @@ 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 + fill: true, // do we fill in the area between the line and its base axis }; Chart.elements.Line = Chart.Element.extend({ draw: function() { var me = this; var vm = me._view; var spanGaps = vm.spanGaps; - var scaleZero = vm.scaleZero; + var fillPoint = vm.scaleZero; var loop = me._loop; + // Handle different fill modes for cartesian lines + if (!loop) { + if (vm.fill === 'top') { + fillPoint = vm.scaleTop; + } else if (vm.fill === 'bottom') { + fillPoint = vm.scaleBottom; + } + } + var ctx = me._chart.ctx; ctx.save(); // Helper function to draw a line to a point function lineToPoint(previousPoint, point) { - var vm = point._view; + var pointVM = point._view; if (point._view.steppedLine === true) { - ctx.lineTo(point._view.x, previousPoint._view.y); - ctx.lineTo(point._view.x, point._view.y); + ctx.lineTo(pointVM.x, previousPoint._view.y); + ctx.lineTo(pointVM.x, pointVM.y); } else if (point._view.tension === 0) { - ctx.lineTo(vm.x, vm.y); + ctx.lineTo(pointVM.x, pointVM.y); } else { ctx.bezierCurveTo( previousPoint._view.controlPointNextX, previousPoint._view.controlPointNextY, - vm.controlPointPreviousX, - vm.controlPointPreviousY, - vm.x, - vm.y + pointVM.controlPointPreviousX, + pointVM.controlPointPreviousY, + pointVM.x, + pointVM.y ); } } var points = me._children.slice(); // clone array @@ -12925,13 +14167,13 @@ currentVM = current._view; // First point moves to it's starting position no matter what if (index === 0) { if (loop) { - ctx.moveTo(scaleZero.x, scaleZero.y); + ctx.moveTo(fillPoint.x, fillPoint.y); } else { - ctx.moveTo(currentVM.x, scaleZero); + ctx.moveTo(currentVM.x, fillPoint); } if (!currentVM.skip) { lastDrawnIndex = index; ctx.lineTo(currentVM.x, currentVM.y); @@ -12941,29 +14183,27 @@ 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); + ctx.lineTo(fillPoint.x, fillPoint.y); } else { - ctx.lineTo(previous._view.x, scaleZero); + 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. + // 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 { - if (loop) { - ctx.lineTo(currentVM.x, currentVM.y); - } else { - ctx.lineTo(currentVM.x, scaleZero); - ctx.lineTo(currentVM.x, currentVM.y); - } + ctx.lineTo(currentVM.x, fillPoint); + ctx.lineTo(currentVM.x, currentVM.y); } } else { // Line to next point lineToPoint(previous, current); } @@ -12971,11 +14211,11 @@ } } } if (!loop && lastDrawnIndex !== -1) { - ctx.lineTo(points[lastDrawnIndex]._view.x, scaleZero); + ctx.lineTo(points[lastDrawnIndex]._view.x, fillPoint); } ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor; ctx.closePath(); ctx.fill(); @@ -13029,13 +14269,14 @@ ctx.stroke(); ctx.restore(); } }); }; -},{}],37:[function(require,module,exports){ -"use strict"; +},{}],39:[function(require,module,exports){ +'use strict'; + module.exports = function(Chart) { var helpers = Chart.helpers, globalOpts = Chart.defaults.global, defaultColor = globalOpts.defaultColor; @@ -13050,50 +14291,93 @@ hitRadius: 1, hoverRadius: 4, hoverBorderWidth: 1 }; + function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; + } + + function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; + } + 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) { + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { var vm = this._view; - return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; + 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() { + draw: function(chartArea) { var vm = this._view; + var model = this._model; var ctx = this._chart.ctx; var pointStyle = vm.pointStyle; var radius = vm.radius; var x = vm.x; var y = vm.y; + var color = Chart.helpers.color; + var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) + var ratio = 0; if (vm.skip) { return; } ctx.strokeStyle = vm.borderColor || defaultColor; ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth); ctx.fillStyle = vm.backgroundColor || defaultColor; + // Cliping for Points. + // going out from inner charArea? + if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right*errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom*errMargin < model.y))) { + // Point fade out + if (model.x < chartArea.left) { + ratio = (x - model.x) / (chartArea.left - model.x); + } else if (chartArea.right*errMargin < model.x) { + ratio = (model.x - x) / (model.x - chartArea.right); + } else if (model.y < chartArea.top) { + ratio = (y - model.y) / (chartArea.top - model.y); + } else if (chartArea.bottom*errMargin < model.y) { + ratio = (model.y - y) / (model.y - chartArea.bottom); + } + ratio = Math.round(ratio*100) / 100; + ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString(); + ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString(); + } + Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y); } }); }; -},{}],38:[function(require,module,exports){ -"use strict"; +},{}],40:[function(require,module,exports){ +'use strict'; module.exports = function(Chart) { var globalOpts = Chart.defaults.global; @@ -13102,79 +14386,194 @@ borderWidth: 0, borderColor: globalOpts.defaultColor, borderSkipped: 'bottom' }; + function isVertical(bar) { + return bar._view.width !== undefined; + } + + /** + * 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; + + 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; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; + } + Chart.elements.Rectangle = Chart.Element.extend({ draw: function() { var ctx = this._chart.ctx; var vm = this._view; + var left, right, top, bottom, signX, signY, borderSkipped; + var borderWidth = vm.borderWidth; - 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 (!vm.horizontal) { + // bar + left = vm.x - vm.width / 2; + right = vm.x + vm.width / 2; + top = vm.y; + bottom = vm.base; + signX = 1; + signY = bottom > top? 1: -1; + borderSkipped = vm.borderSkipped || 'bottom'; + } else { + // horizontal bar + left = vm.base; + right = vm.x; + top = vm.y - vm.height / 2; + bottom = vm.y + vm.height / 2; + signX = right > left? 1: -1; + signY = 1; + borderSkipped = vm.borderSkipped || 'left'; + } // 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; + if (borderWidth) { + // borderWidth shold be less than bar width and bar height. + var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); + borderWidth = borderWidth > barSize? barSize: borderWidth; + var halfStroke = borderWidth / 2; + // Adjust borderWidth when bar top position is near vm.base(zero). + var borderLeft = left + (borderSkipped !== 'left'? halfStroke * signX: 0); + var borderRight = right + (borderSkipped !== 'right'? -halfStroke * signX: 0); + var borderTop = top + (borderSkipped !== 'top'? halfStroke * signY: 0); + var borderBottom = bottom + (borderSkipped !== 'bottom'? -halfStroke * signY: 0); + // not become a vertical line? + if (borderLeft !== borderRight) { + top = borderTop; + bottom = borderBottom; + } + // not become a horizontal line? + if (borderTop !== borderBottom) { + left = borderLeft; + right = borderRight; + } } ctx.beginPath(); ctx.fillStyle = vm.backgroundColor; ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + ctx.lineWidth = borderWidth; // 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] + [left, bottom], + [left, top], + [right, top], + [right, bottom] ]; // Find first (starting) corner with fallback to 'bottom' var borders = ['bottom', 'left', 'top', 'right']; - var startCorner = borders.indexOf(vm.borderSkipped, 0); - if (startCorner === -1) + var startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { startCorner = 0; + } function cornerAt(index) { return corners[(startCorner + index) % 4]; } // Draw rectangle from 'startCorner' - ctx.moveTo.apply(ctx, cornerAt(0)); - for (var i = 1; i < 4; i++) - ctx.lineTo.apply(ctx, cornerAt(i)); + var corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); + for (var i = 1; i < 4; i++) { + corner = cornerAt(i); + ctx.lineTo(corner[0], corner[1]); + } + ctx.fill(); - if (vm.borderWidth) { + if (borderWidth) { ctx.stroke(); } }, height: function() { var vm = this._view; return vm.base - vm.y; }, inRange: function(mouseX, mouseY) { + var inRange = false; + + if (this._view) { + var bounds = getBarBounds(this); + inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + inLabelRange: function(mouseX, mouseY) { + var me = this; + if (!me._view) { + return false; + } + + var inRange = false; + var bounds = getBarBounds(me); + + 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; - 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; + 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}; }, - inLabelRange: function(mouseX) { + getArea: function() { var vm = this._view; - return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false; + return vm.width * Math.abs(vm.y - vm.base); }, tooltipPosition: function() { var vm = this._view; return { x: vm.x, @@ -13182,24 +14581,375 @@ }; } }); }; -},{}],39:[function(require,module,exports){ -"use strict"; +},{}],41:[function(require,module,exports){ +'use strict'; + +// Chart.Platform implementation for targeting a web browser module.exports = function(Chart) { + var helpers = Chart.helpers; + // DOM event types -> Chart.js event types. + // Note: only events with different types are mapped. + // https://developer.mozilla.org/en-US/docs/Web/Events + var eventTypeMap = { + // Touch events + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + + // Pointer events + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' + }; + + /** + * 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. + * @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; + } + + /** + * 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. + */ + function initCanvas(canvas, config) { + var style = canvas.style; + + // 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'); + + // 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 + } + } + }; + + // 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'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + 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; + } + } + } + + return canvas; + } + + function createEvent(type, chart, x, y, native) { + return { + type: type, + chart: chart, + native: native || null, + x: x !== undefined? x : null, + y: y !== undefined? y : null, + }; + } + + function fromNativeEvent(event, chart) { + var type = eventTypeMap[event.type] || event.type; + var pos = helpers.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); + } + + function createResizer(handler) { + 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;'; + + // Prevent the iframe to gain focus on tab. + // https://github.com/chartjs/Chart.js/issues/3090 + iframe.tabIndex = -1; + + // 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', handler); + + // The iframe size might have changed while loading, which can also + // happen if the size has been changed while detached from the DOM. + handler(); + }); + + return iframe; + } + + function addResizeListener(node, listener, chart) { + var stub = node._chartjs = { + ticking: 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 listener(createEvent('resize', chart)); + } + }); + } + }; + + // Let's keep track of this added iframe and thus avoid DOM query when removing it. + stub.resizer = createResizer(notify); + + node.insertBefore(stub.resizer, node.firstChild); + } + + function removeResizeListener(node) { + if (!node || !node._chartjs) { + return; + } + + var resizer = node._chartjs.resizer; + if (resizer) { + resizer.parentNode.removeChild(resizer); + node._chartjs.resizer = null; + } + + delete node._chartjs; + } + + return { + acquireContext: function(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]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + 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; + } + } + + return null; + }, + + releaseContext: function(context) { + var canvas = context.canvas; + if (!canvas._chartjs) { + return; + } + + 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); + } + }); + + helpers.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); + + // 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; + + delete canvas._chartjs; + }, + + addEventListener: function(chart, type, listener) { + var canvas = chart.chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + addResizeListener(canvas.parentNode, listener, chart.chart); + return; + } + + var stub = listener._chartjs || (listener._chartjs = {}); + var proxies = stub.proxies || (stub.proxies = {}); + var proxy = proxies[chart.id + '_' + type] = function(event) { + listener(fromNativeEvent(event, chart.chart)); + }; + + helpers.addEvent(canvas, type, proxy); + }, + + removeEventListener: function(chart, type, listener) { + var canvas = chart.chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + removeResizeListener(canvas.parentNode, listener); + return; + } + + var stub = listener._chartjs || {}; + var proxies = stub.proxies || {}; + var proxy = proxies[chart.id + '_' + type]; + if (!proxy) { + return; + } + + helpers.removeEvent(canvas, type, proxy); + } + }; +}; + +},{}],42:[function(require,module,exports){ +'use strict'; + +// By default, select the browser (DOM) platform. +// @TODO Make possible to select another platform at build time. +var implementation = require(41); + +module.exports = function(Chart) { + /** + * @namespace Chart.platform + * @see https://chartjs.gitbooks.io/proposals/content/Platform.html + * @since 2.4.0 + */ + Chart.platform = { + /** + * Called at chart construction time, returns a context2d instance implementing + * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. + * @param {*} item - The native item from which to acquire context (platform specific) + * @param {Object} options - The chart options + * @returns {CanvasRenderingContext2D} context2d instance + */ + acquireContext: function() {}, + + /** + * Called at chart destruction time, releases any resources associated to the context + * previously returned by the acquireContext() method. + * @param {CanvasRenderingContext2D} context - The context2d instance + * @returns {Boolean} true if the method succeeded, else false + */ + releaseContext: function() {}, + + /** + * Registers the specified listener on the given chart. + * @param {Chart} chart - Chart from which to listen for event + * @param {String} type - The ({@link IEvent}) type to listen for + * @param {Function} listener - Receives a notification (an object that implements + * the {@link IEvent} interface) when an event of the specified type occurs. + */ + addEventListener: function() {}, + + /** + * Removes the specified listener previously registered with addEventListener. + * @param {Chart} chart -Chart from which to remove the listener + * @param {String} type - The ({@link IEvent}) type to remove + * @param {Function} listener - The listener function to remove from the event target. + */ + removeEventListener: function() {} + }; + + /** + * @interface IPlatform + * Allows abstracting platform dependencies away from the chart + * @borrows Chart.platform.acquireContext as acquireContext + * @borrows Chart.platform.releaseContext as releaseContext + * @borrows Chart.platform.addEventListener as addEventListener + * @borrows Chart.platform.removeEventListener as removeEventListener + */ + + /** + * @interface IEvent + * @prop {String} type - The event type name, possible values are: + * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', + * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' + * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') + * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) + * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) + */ + + Chart.helpers.extend(Chart.platform, implementation(Chart)); +}; + +},{"41":41}],43:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + var helpers = Chart.helpers; // Default config for a category scale var defaultConfig = { - position: "bottom" + position: 'bottom' }; var DatasetScale = Chart.Scale.extend({ /** - * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use tose + * 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; @@ -13234,12 +14984,19 @@ 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); }, - getLabelForIndex: function(index) { - return this.ticks[index]; + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var isHorizontal = me.isHorizontal(); + + if (data.yLabels && !isHorizontal) { + return me.getRightValue(data.datasets[datasetIndex].data[index]); + } + return me.ticks[index - me.minIndex]; }, // 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; @@ -13251,48 +15008,43 @@ var idx = labels.indexOf(value); index = idx !== -1 ? idx : index; } if (me.isHorizontal()) { - var innerWidth = me.width - (me.paddingLeft + me.paddingRight); - var valueWidth = innerWidth / offsetAmt; - var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft; + var valueWidth = me.width / offsetAmt; + var widthOffset = (valueWidth * (index - me.minIndex)); - if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) { + if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) { widthOffset += (valueWidth / 2); - } + } 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; + } + var valueHeight = me.height / offsetAmt; + var heightOffset = (valueHeight * (index - me.minIndex)); - if (me.options.gridLines.offsetGridLines && includeOffset) { - heightOffset += (valueHeight / 2); - } - - return me.top + Math.round(heightOffset); + if (me.options.gridLines.offsetGridLines && includeOffset) { + heightOffset += (valueHeight / 2); } + + 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 valueDimension = (horz ? me.width : me.height) / offsetAmt; pixel -= horz ? me.left : me.top; if (me.options.gridLines.offsetGridLines) { pixel -= (valueDimension / 2); } - pixel -= horz ? me.paddingLeft : me.paddingTop; if (pixel <= 0) { value = 0; } else { value = Math.round(pixel / valueDimension); @@ -13303,48 +15055,25 @@ getBasePixel: function() { return this.bottom; } }); - Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); + Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig); }; -},{}],40:[function(require,module,exports){ -"use strict"; +},{}],44:[function(require,module,exports){ +'use strict'; + module.exports = function(Chart) { var helpers = Chart.helpers; var defaultConfig = { - position: "left", + 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]; - - // 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); - } - } - - var logDelta = helpers.log10(Math.abs(delta)); - var tickString = ''; - - 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 - } - - return tickString; - } + callback: Chart.Ticks.formatters.linear } }; var LinearScale = Chart.LinearScaleBase.extend({ determineDataLimits: function() { @@ -13361,27 +15090,47 @@ // First Calculate the range me.min = null; me.max = null; - if (opts.stacked) { - var valuesPerType = {}; - var hasPositiveValues = false; - var hasNegativeValues = false; + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + helpers.each(datasets, function(dataset, datasetIndex) { var meta = chart.getDatasetMeta(datasetIndex); - if (valuesPerType[meta.type] === undefined) { - valuesPerType[meta.type] = { + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = { positiveValues: [], negativeValues: [] }; } // Store these per type - var positiveValues = valuesPerType[meta.type].positiveValues; - var negativeValues = valuesPerType[meta.type].negativeValues; + var positiveValues = valuesPerStack[key].positiveValues; + var negativeValues = valuesPerStack[key].negativeValues; 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) { @@ -13391,24 +15140,20 @@ positiveValues[index] = positiveValues[index] || 0; negativeValues[index] = negativeValues[index] || 0; if (opts.relativePoints) { positiveValues[index] = 100; + } else if (value < 0) { + negativeValues[index] += value; } else { - if (value < 0) { - hasNegativeValues = true; - negativeValues[index] += value; - } else { - hasPositiveValues = true; - positiveValues[index] += value; - } + positiveValues[index] += value; } }); } }); - helpers.each(valuesPerType, function(valuesForType) { + helpers.each(valuesPerStack, 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); @@ -13471,48 +15216,42 @@ // 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; var rightValue = +me.getRightValue(value); var pixel; - var innerDimension; var range = me.end - start; 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)); + pixel = me.left + (me.width / range * (rightValue - start)); return Math.round(pixel); } + + pixel = me.bottom - (me.height / 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; + var innerDimension = isHorizontal ? me.width : me.height; + var offset = (isHorizontal ? pixel - me.left : me.bottom - 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); + Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig); }; -},{}],41:[function(require,module,exports){ -"use strict"; +},{}],45:[function(require,module,exports){ +'use strict'; + module.exports = function(Chart) { var helpers = Chart.helpers, noop = helpers.noop; @@ -13531,11 +15270,11 @@ 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 + // move the bottom down to 0 me.min = 0; } } if (tickOpts.min !== undefined) { @@ -13562,54 +15301,27 @@ handleDirectionalChanges: noop, buildTicks: function() { var me = this; var opts = me.options; - var ticks = me.ticks = []; var tickOpts = opts.ticks; - var getValueOrDefault = helpers.getValueOrDefault; // 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 - + // the graph. Make sure we always have at least 2 ticks var maxTicks = me.getTickLimit(); - - // Make sure we always have at least 2 ticks maxTicks = Math.max(2, maxTicks); - // 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. + 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); - 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 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); - } - - // 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); - me.handleDirectionalChanges(); // 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); @@ -13632,33 +15344,24 @@ Chart.Scale.prototype.convertTicksToLabels.call(me); } }); }; -},{}],42:[function(require,module,exports){ -"use strict"; +},{}],46:[function(require,module,exports){ +'use strict'; + module.exports = function(Chart) { var helpers = Chart.helpers; var defaultConfig = { - position: "left", + position: 'left', // label settings ticks: { - callback: function(value, index, arr) { - var remain = value / (Math.pow(10, Math.floor(helpers.log10(value)))); - - if (value === 0){ - return '0'; - } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) { - return value.toExponential(); - } else { - return ''; - } - } + callback: Chart.Ticks.formatters.logarithmic } }; var LogarithmicScale = Chart.Scale.extend({ determineDataLimits: function() { @@ -13677,22 +15380,44 @@ // Calculate Range me.min = null; me.max = null; me.minNotZero = null; - if (opts.stacked) { - var valuesPerType = {}; + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + helpers.each(datasets, function(dataset, datasetIndex) { var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - if (valuesPerType[meta.type] === undefined) { - valuesPerType[meta.type] = []; + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; } helpers.each(dataset.data, function(rawValue, index) { - var values = valuesPerType[meta.type]; + var values = valuesPerStack[key]; var value = +me.getRightValue(rawValue); if (isNaN(value) || meta.data[index].hidden) { return; } @@ -13706,11 +15431,11 @@ } }); } }); - helpers.each(valuesPerType, function(valuesForType) { + helpers.each(valuesPerStack, 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); }); @@ -13735,11 +15460,11 @@ me.max = value; } else if (value > me.max) { me.max = value; } - if(value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { + if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { me.minNotZero = value; } }); } }); @@ -13760,48 +15485,17 @@ }, buildTicks: function() { var me = this; var opts = me.options; var tickOpts = opts.ticks; - var getValueOrDefault = helpers.getValueOrDefault; - // 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 = []; + var generationOptions = { + min: tickOpts.min, + max: tickOpts.max + }; + var ticks = me.ticks = Chart.Ticks.generators.logarithmic(generationOptions, me); - // 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(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min)))); - - while (tickVal < me.max) { - ticks.push(tickVal); - - var exp; - var significand; - - if(tickVal === 0){ - exp = Math.floor(helpers.log10(me.minNotZero)); - significand = Math.round(me.minNotZero / Math.pow(10, exp)); - } else { - exp = Math.floor(helpers.log10(tickVal)); - significand = Math.floor(tickVal / Math.pow(10, exp)) + 1; - } - - if (significand === 10) { - significand = 1; - ++exp; - } - - tickVal = significand * Math.pow(10, exp); - } - - var lastTick = getValueOrDefault(tickOpts.max, tickVal); - ticks.push(lastTick); - if (!me.isHorizontal()) { // We are in a vertical orientation. The top value is the highest. So reverse the array ticks.reverse(); } @@ -13838,124 +15532,379 @@ var pixel; 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; if (me.isHorizontal()) { range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0 if (newVal === 0) { - pixel = me.left + paddingLeft; + pixel = me.left; } else { - innerDimension = me.width - (paddingLeft + me.paddingRight); + innerDimension = me.width; pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); - pixel += paddingLeft; } } else { - // Bottom - top since pixels increase downard on a screen - innerDimension = me.height - (paddingTop + paddingBottom); - if(start === 0 && !tickOpts.reverse){ + // Bottom - top since pixels increase downward on a screen + innerDimension = me.height; + 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; + pixel = me.bottom; + } else if (newVal === me.minNotZero) { + pixel = me.bottom - innerDimension * 0.02; } else { - pixel = me.bottom - paddingBottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero))); + pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero))); } - } else if (me.end === 0 && tickOpts.reverse){ + } 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; + pixel = me.top; + } else if (newVal === me.minNotZero) { + pixel = me.top + innerDimension * 0.02; } else { - pixel = me.top + paddingTop + innerDimension * 0.02 + (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero))); + pixel = me.top + 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))); - } + innerDimension = me.height; + pixel = me.bottom - (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; if (me.isHorizontal()) { - innerDimension = me.width - (me.paddingLeft + me.paddingRight); - value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension); + innerDimension = me.width; + value = me.start * Math.pow(10, (pixel - me.left) * 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; + innerDimension = me.height; + value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start; } return value; } }); - Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig); + Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); }; -},{}],43:[function(require,module,exports){ -"use strict"; +},{}],47:[function(require,module,exports){ +'use strict'; + module.exports = function(Chart) { var helpers = Chart.helpers; var globalDefaults = Chart.defaults.global; var defaultConfig = { display: true, - //Boolean - Whether to animate scaling the chart from the centre + // Boolean - Whether to animate scaling the chart from the centre animate: true, lineArc: false, - position: "chartArea", + position: 'chartArea', angleLines: { display: true, - color: "rgba(0, 0, 0, 0.1)", + color: 'rgba(0, 0, 0, 0.1)', lineWidth: 1 }, // label settings ticks: { - //Boolean - Show a backdrop to the scale label + // Boolean - Show a backdrop to the scale label showLabelBackdrop: true, - //String - The colour of the label backdrop - backdropColor: "rgba(255,255,255,0.75)", + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', - //Number - The backdrop padding above & below the label in pixels + // Number - The backdrop padding above & below the label in pixels backdropPaddingY: 2, - //Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2 + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + + callback: Chart.Ticks.formatters.linear }, pointLabels: { - //Number - Point label font size in pixels + // Number - Point label font size in pixels fontSize: 10, - //Function - Used to convert point labels + // Function - Used to convert point labels callback: function(label) { return label; } } }; + function getValueCount(scale) { + return !scale.options.lineArc ? scale.chart.data.labels.length : 0; + } + + function getPointLabelFontOptions(scale) { + var pointLabelOptions = scale.options.pointLabels; + var fontSize = helpers.getValueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); + var fontStyle = helpers.getValueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); + var fontFamily = helpers.getValueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); + var font = helpers.fontString(fontSize, fontStyle, fontFamily); + + return { + size: fontSize, + style: fontStyle, + family: fontFamily, + font: font + }; + } + + function measureLabelSize(ctx, fontSize, label) { + if (helpers.isArray(label)) { + return { + w: helpers.longestText(ctx, ctx.font, label), + h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) + }; + } + + return { + w: ctx.measureText(label).width, + h: fontSize + }; + } + + function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { + return { + start: pos - size - 5, + end: pos + }; + } + + return { + start: pos, + end: pos + size + 5 + }; + } + + /** + * Helper function to fit a radial linear scale with point labels + */ + function fitWithPointLabels(scale) { + /* + * 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 + */ + + var plFont = getPointLabelFontOptions(scale); + + // 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 = Math.min(scale.height / 2, scale.width / 2); + var furthestLimits = { + l: scale.width, + r: 0, + t: scale.height, + b: 0 + }; + var furthestAngles = {}; + var i; + var textSize; + var pointPosition; + + scale.ctx.font = plFont.font; + scale._pointLabelSizes = []; + + var valueCount = getValueCount(scale); + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, largestPossibleRadius); + textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians) % 360; + var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } + + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } + + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } + + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } + + scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); + } + + /** + * Helper function to fit a radial linear scale with no point labels + */ + function fit(scale) { + var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); + scale.drawingArea = Math.round(largestPossibleRadius); + scale.setCenterPoint(0, 0, 0, 0); + } + + function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } + + return 'right'; + } + + function fillText(ctx, text, position, fontSize) { + if (helpers.isArray(text)) { + var y = position.y; + var spacing = 1.5 * fontSize; + + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], position.x, y); + y+= spacing; + } + } else { + ctx.fillText(text, position.x, position.y); + } + } + + function adjustPointPositionForLabelHeight(angle, textSize, position) { + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } + } + + function drawPointLabels(scale) { + var ctx = scale.ctx; + var getValueOrDefault = helpers.getValueOrDefault; + var opts = scale.options; + var angleLineOpts = opts.angleLines; + var pointLabelOpts = opts.pointLabels; + + ctx.lineWidth = angleLineOpts.lineWidth; + ctx.strokeStyle = angleLineOpts.color; + + var outerDistance = scale.getDistanceFromCenterForValue(opts.reverse ? scale.min : scale.max); + + // Point Label Font + var plFont = getPointLabelFontOptions(scale); + + ctx.textBaseline = 'top'; + + for (var i = getValueCount(scale) - 1; i >= 0; i--) { + if (angleLineOpts.display) { + var outerPosition = scale.getPointPosition(i, outerDistance); + ctx.beginPath(); + ctx.moveTo(scale.xCenter, scale.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); + ctx.closePath(); + } + // Extra 3px out for some label spacing + var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); + + // Keep this in loop since we may support array properties here + var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); + ctx.font = plFont.font; + ctx.fillStyle = pointLabelFontColor; + + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians); + ctx.textAlign = getTextAlignForAngle(angle); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); + } + } + + function drawRadiusLine(scale, gridLineOpts, radius, index) { + var ctx = scale.ctx; + ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1); + ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + + if (scale.options.lineArc) { + // Draw circular arcs between the points + ctx.beginPath(); + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.stroke(); + } else { + // Draw straight lines connecting each index + var valueCount = getValueCount(scale); + + if (valueCount === 0) { + return; + } + + ctx.beginPath(); + var pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (var i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + + ctx.closePath(); + ctx.stroke(); + } + } + + function numberOrZero(param) { + return helpers.isNumber(param) ? param : 0; + } + 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 @@ -13969,39 +15918,32 @@ 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; + var min = Number.POSITIVE_INFINITY; + var max = Number.NEGATIVE_INFINITY; - helpers.each(chart.data.datasets, function(dataset, datasetIndex) { if (chart.isDatasetVisible(datasetIndex)) { var meta = chart.getDatasetMeta(datasetIndex); 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.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; - } + min = Math.min(value, min); + max = Math.max(value, max); }); } }); + me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); + me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero me.handleTickRangeOptions(); }, getTickLimit: function() { var tickOpts = this.options.ticks; @@ -14017,136 +15959,58 @@ }, 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 - */ - - 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); - - // 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; - - 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; - - // 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; - } - } 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 (this.options.lineArc) { + fit(this); + } else { + fitWithPointLabels(this); } + }, + /** + * Set radius reductions and determine new radius and center point + * @private + */ + setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { + var me = this; + var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); - xProtrusionLeft = furthestLeft; - xProtrusionRight = Math.ceil(furthestRight - this.width); + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); - furthestRightAngle = this.getIndexAngle(furthestRightIndex); - furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); - - radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); - radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); - - // Ensure we actually need to reduce the size of the chart - radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; - radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; - - this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2); - this.setCenterPoint(radiusReductionLeft, radiusReductionRight); + me.drawingArea = Math.min( + Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); }, - setCenterPoint: function(leftMovement, rightMovement) { + setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { var me = this; var maxRight = me.width - rightMovement - me.drawingArea, - maxLeft = leftMovement + me.drawingArea; + maxLeft = leftMovement + me.drawingArea, + maxTop = topMovement + me.drawingArea, + maxBottom = me.height - bottomMovement - me.drawingArea; 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); + me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); }, getIndexAngle: function(index) { - var angleMultiplier = (Math.PI * 2) / this.getValueCount(); + var angleMultiplier = (Math.PI * 2) / getValueCount(this); var startAngle = this.chart.options && this.chart.options.startAngle ? this.chart.options.startAngle : 0; var startAngleRadians = startAngle * Math.PI * 2 / 360; // Start from the top instead of right, so remove a quarter of the circle - return index * angleMultiplier - (Math.PI / 2) + startAngleRadians; + return index * angleMultiplier + startAngleRadians; }, getDistanceFromCenterForValue: function(value) { var me = this; if (value === null) { @@ -14155,17 +16019,16 @@ // 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; } + return (value - me.min) * scalingFactor; }, getPointPosition: function(index, distanceFromCenter) { var me = this; - var thisAngle = me.getIndexAngle(index); + var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); return { x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter }; }, @@ -14188,12 +16051,10 @@ 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 (opts.display) { var ctx = me.ctx; @@ -14209,33 +16070,11 @@ var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); var yHeight = me.yCenter - yCenterOffset; // 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); - - 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(); - } + drawRadiusLine(me, gridLineOpts, yCenterOffset, index); } if (tickOpts.display) { var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); ctx.font = tickLabelFont; @@ -14250,82 +16089,30 @@ tickFontSize + tickOpts.backdropPaddingY * 2 ); } ctx.textAlign = 'center'; - ctx.textBaseline = "middle"; + ctx.textBaseline = 'middle'; ctx.fillStyle = tickFontColor; ctx.fillText(label, me.xCenter, yHeight); } } }); if (!opts.lineArc) { - ctx.lineWidth = angleLineOpts.lineWidth; - ctx.strokeStyle = angleLineOpts.color; - - var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max); - - // 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); - - 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); - - // Keep this in loop since we may support array properties here - var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); - ctx.font = pointLabeFont; - ctx.fillStyle = pointLabelFontColor; - - var pointLabels = me.pointLabels; - - // 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) { - ctx.textAlign = 'center'; - } else if (angle < 180) { - ctx.textAlign = 'left'; - } else { - ctx.textAlign = 'right'; - } - - // 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'; - } - - ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y); - } + drawPointLabels(me); } } } }); - Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig); + Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); }; -},{}],44:[function(require,module,exports){ -/*global window: false */ -"use strict"; +},{}],48:[function(require,module,exports){ +/* global window: false */ +'use strict'; var moment = require(6); moment = typeof(moment) === 'function' ? moment : window.moment; module.exports = function(Chart) { @@ -14361,31 +16148,32 @@ maxStep: false }] }; var defaultConfig = { - position: "bottom", + position: 'bottom', 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', // 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 + 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 } @@ -14398,23 +16186,42 @@ } Chart.Scale.prototype.initialize.call(this); }, getLabelMoment: function(datasetIndex, index) { - if (typeof this.labelMoments[datasetIndex] != 'undefined') { + if (datasetIndex === null || index === null) { + return null; + } + + if (typeof this.labelMoments[datasetIndex] !== 'undefined') { return this.labelMoments[datasetIndex][index]; } return null; }, + getLabelDiff: function(datasetIndex, index) { + var me = this; + if (datasetIndex === null || index === null) { + return null; + } + + if (me.labelDiffs === undefined) { + me.buildLabelDiffs(); + } + + if (typeof me.labelDiffs[datasetIndex] !== 'undefined') { + return me.labelDiffs[datasetIndex][index]; + } + + 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); } + return tick.clone().startOf(me.tickUnit); }, determineDataLimits: function() { var me = this; me.labelMoments = []; @@ -14480,10 +16287,50 @@ // 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); @@ -14502,22 +16349,22 @@ 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 innerWidth = me.isHorizontal() ? me.width : me.height; // 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 = 'millisecond'; + 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]; @@ -14526,11 +16373,11 @@ 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 prefedined 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; } @@ -14586,46 +16433,48 @@ // 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) { + for (var i = me.unitScale; i <= me.scaleSizeInUnits; i += me.unitScale) { 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); - } + 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 if (me.scaleSizeInUnits === 0) { - me.ticks.push(me.lastTick.clone()); - me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, 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] : ''; + var value = me.chart.data.datasets[datasetIndex].data[index]; - if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') { - label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]); + if (value !== null && typeof value === 'object') { + label = me.getRightValue(value); } // Format nicely if (me.options.time.tooltipFormat) { label = me.parseTime(label).format(me.options.time.tooltipFormat); @@ -14639,52 +16488,54 @@ var tickOpts = this.options.ticks; var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback); if (callback) { return callback(formattedTick, index, ticks); - } else { - return formattedTick; } + 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 = me.parseTime(me.getRightValue(value)); + var offset = null; + if (index !== undefined && datasetIndex !== undefined) { + offset = me.getLabelDiff(datasetIndex, index); } - var labelMoment = value && value.isValid && value.isValid() ? value : me.getLabelMoment(datasetIndex, index); - if (labelMoment) { - var offset = labelMoment.diff(me.firstTick, me.tickUnit, true); + 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; - + var valueOffset = (me.width * decimal); return me.left + Math.round(valueOffset); - } else { - var innerHeight = me.height - (me.paddingTop + me.paddingBottom); - var heightOffset = (innerHeight * decimal) + me.paddingTop; - - return me.top + Math.round(heightOffset); } + + var heightOffset = (me.height * decimal); + 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; + var innerDimension = me.isHorizontal() ? me.width : me.height; + var offset = (pixel - (me.isHorizontal() ? me.left : me.top)) / innerDimension; offset *= me.scaleSizeInUnits; return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds'); }, parseTime: function(label) { var me = this; @@ -14702,17 +16553,17 @@ 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"); + 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); + Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig); }; },{"6":6}]},{},[7])(7) }); \ No newline at end of file