/*! * Material Design for Bootstrap 4 * Version: MDB FREE: 4.3.2 * * * Copyright: Material Design for Bootstrap * http://mdbootstrap.com/ * * Read the license: http://mdbootstrap.com/license/ * * * Documentation: http://mdbootstrap.com/ * * Getting started: http://mdbootstrap.com/getting-started/ * * Tutorials: http://mdbootstrap.com/bootstrap-tutorial/ * * Templates: http://mdbootstrap.com/templates/ * * Support: http://mdbootstrap.com/forums/forum/support/ * * Contact: office@mdbootstrap.com * * Atribution: Animate CSS, Twitter Bootstrap, Materialize CSS, Normalize CSS, Waves JS, WOW JS, Toastr, Chart.js , Hammer.js * */ /* jquery-easing.js global.js velocity.js chart.js wow.js scrolling-nav.js waves.js forms-basic.js */ /* * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ * * Uses the built in easing capabilities added In jQuery 1.1 * to offer multiple easing options * * TERMS OF USE - jQuery Easing * * Open source under the BSD License. * * Copyright © 2008 George McGinley Smith * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ // t: current time, b: begInnIng value, c: change In value, d: duration jQuery.easing['jswing'] = jQuery.easing['swing']; jQuery.extend(jQuery.easing, { def: 'easeOutQuad', swing: function(x, t, b, c, d) { //alert(jQuery.easing.default); return jQuery.easing[jQuery.easing.def](x, t, b, c, d); }, easeInQuad: function(x, t, b, c, d) { return c * (t /= d) * t + b; }, easeOutQuad: function(x, t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, easeInOutQuad: function(x, t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t + b; return -c / 2 * ((--t) * (t - 2) - 1) + b; }, easeInCubic: function(x, t, b, c, d) { return c * (t /= d) * t * t + b; }, easeOutCubic: function(x, t, b, c, d) { return c * ((t = t / d - 1) * t * t + 1) + b; }, easeInOutCubic: function(x, t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; return c / 2 * ((t -= 2) * t * t + 2) + b; }, easeInQuart: function(x, t, b, c, d) { return c * (t /= d) * t * t * t + b; }, easeOutQuart: function(x, t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; }, easeInOutQuart: function(x, t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; return -c / 2 * ((t -= 2) * t * t * t - 2) + b; }, easeInQuint: function(x, t, b, c, d) { return c * (t /= d) * t * t * t * t + b; }, easeOutQuint: function(x, t, b, c, d) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; }, easeInOutQuint: function(x, t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b; return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; }, easeInSine: function(x, t, b, c, d) { return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; }, easeOutSine: function(x, t, b, c, d) { return c * Math.sin(t / d * (Math.PI / 2)) + b; }, easeInOutSine: function(x, t, b, c, d) { return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; }, easeInExpo: function(x, t, b, c, d) { return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; }, easeOutExpo: function(x, t, b, c, d) { return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; }, easeInOutExpo: function(x, t, b, c, d) { if (t == 0) return b; if (t == d) return b + c; if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b; return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; }, easeInCirc: function(x, t, b, c, d) { return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; }, easeOutCirc: function(x, t, b, c, d) { return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; }, easeInOutCirc: function(x, t, b, c, d) { if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; }, easeInElastic: function(x, t, b, c, d) { var s = 1.70158; var p = 0; var a = c; if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3; if (a < Math.abs(c)) { a = c; var s = p / 4; } else var s = p / (2 * Math.PI) * Math.asin(c / a); return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; }, easeOutElastic: function(x, t, b, c, d) { var s = 1.70158; var p = 0; var a = c; if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3; if (a < Math.abs(c)) { a = c; var s = p / 4; } else var s = p / (2 * Math.PI) * Math.asin(c / a); return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; }, easeInOutElastic: function(x, t, b, c, d) { var s = 1.70158; var p = 0; var a = c; if (t == 0) return b; if ((t /= d / 2) == 2) return b + c; if (!p) p = d * (.3 * 1.5); if (a < Math.abs(c)) { a = c; var s = p / 4; } else var s = p / (2 * Math.PI) * Math.asin(c / a); if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b; }, easeInBack: function(x, t, b, c, d, s) { if (s == undefined) s = 1.70158; return c * (t /= d) * t * ((s + 1) * t - s) + b; }, easeOutBack: function(x, t, b, c, d, s) { if (s == undefined) s = 1.70158; return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; }, easeInOutBack: function(x, t, b, c, d, s) { if (s == undefined) s = 1.70158; if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; }, easeInBounce: function(x, t, b, c, d) { return c - jQuery.easing.easeOutBounce(x, d - t, 0, c, d) + b; }, easeOutBounce: function(x, t, b, c, d) { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } else if (t < (2 / 2.75)) { return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b; } else if (t < (2.5 / 2.75)) { return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b; } else { return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b; } }, easeInOutBounce: function(x, t, b, c, d) { if (t < d / 2) return jQuery.easing.easeInBounce(x, t * 2, 0, c, d) * .5 + b; return jQuery.easing.easeOutBounce(x, t * 2 - d, 0, c, d) * .5 + c * .5 + b; } }); /* * * TERMS OF USE - EASING EQUATIONS * * Open source under the BSD License. * * Copyright © 2001 Robert Penner * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ // Required for Meteor package, the use of window prevents export by Meteor (function(window) { if (window.Package) { Materialize = {}; } else { window.Materialize = {}; } })(window); // Unique ID Materialize.guid = (function() { function s4() { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); } return function() { return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } ; })(); Materialize.elementOrParentIsFixed = function(element) { var $element = $(element); var $checkElements = $element.add($element.parents()); var isFixed = false; $checkElements.each(function() { if ($(this).css("position") === "fixed") { isFixed = true; return false; } }); return isFixed; } ; // Velocity has conflicts when loaded with jQuery, this will check for it var Vel; if ($) { Vel = $.Velocity; } else if (jQuery) { Vel = jQuery.Velocity; } else { Vel = Velocity; } /*! VelocityJS.org (1.2.3). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */ /*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */ /*! Note that this has been modified by Materialize to confirm that Velocity is not already being imported. */ jQuery.Velocity ? console.log("Velocity is already loaded. You may be needlessly importing Velocity again; note that Materialize includes Velocity.") : (!function(e) { function t(e) { var t = e.length , a = r.type(e); return "function" === a || r.isWindow(e) ? !1 : 1 === e.nodeType && t ? !0 : "array" === a || 0 === t || "number" == typeof t && t > 0 && t - 1 in e } if (!e.jQuery) { var r = function(e, t) { return new r.fn.init(e,t) }; r.isWindow = function(e) { return null != e && e == e.window } , r.type = function(e) { return null == e ? e + "" : "object" == typeof e || "function" == typeof e ? n[i.call(e)] || "object" : typeof e } , r.isArray = Array.isArray || function(e) { return "array" === r.type(e) } , r.isPlainObject = function(e) { var t; if (!e || "object" !== r.type(e) || e.nodeType || r.isWindow(e)) return !1; try { if (e.constructor && !o.call(e, "constructor") && !o.call(e.constructor.prototype, "isPrototypeOf")) return !1 } catch (a) { return !1 } for (t in e) ; return void 0 === t || o.call(e, t) } , r.each = function(e, r, a) { var n, o = 0, i = e.length, s = t(e); if (a) { if (s) for (; i > o && (n = r.apply(e[o], a), n !== !1); o++) ; else for (o in e) if (n = r.apply(e[o], a), n === !1) break } else if (s) for (; i > o && (n = r.call(e[o], o, e[o]), n !== !1); o++) ; else for (o in e) if (n = r.call(e[o], o, e[o]), n === !1) break; return e } , r.data = function(e, t, n) { if (void 0 === n) { var o = e[r.expando] , i = o && a[o]; if (void 0 === t) return i; if (i && t in i) return i[t] } else if (void 0 !== t) { var o = e[r.expando] || (e[r.expando] = ++r.uuid); return a[o] = a[o] || {}, a[o][t] = n, n } } , r.removeData = function(e, t) { var n = e[r.expando] , o = n && a[n]; o && r.each(t, function(e, t) { delete o[t] }) } , r.extend = function() { var e, t, a, n, o, i, s = arguments[0] || {}, l = 1, u = arguments.length, c = !1; for ("boolean" == typeof s && (c = s, s = arguments[l] || {}, l++), "object" != typeof s && "function" !== r.type(s) && (s = {}), l === u && (s = this, l--); u > l; l++) if (null != (o = arguments[l])) for (n in o) e = s[n], a = o[n], s !== a && (c && a && (r.isPlainObject(a) || (t = r.isArray(a))) ? (t ? (t = !1, i = e && r.isArray(e) ? e : []) : i = e && r.isPlainObject(e) ? e : {}, s[n] = r.extend(c, i, a)) : void 0 !== a && (s[n] = a)); return s } , r.queue = function(e, a, n) { function o(e, r) { var a = r || []; return null != e && (t(Object(e)) ? !function(e, t) { for (var r = +t.length, a = 0, n = e.length; r > a; ) e[n++] = t[a++]; if (r !== r) for (; void 0 !== t[a]; ) e[n++] = t[a++]; return e.length = n, e }(a, "string" == typeof e ? [e] : e) : [].push.call(a, e)), a } if (e) { a = (a || "fx") + "queue"; var i = r.data(e, a); return n ? (!i || r.isArray(n) ? i = r.data(e, a, o(n)) : i.push(n), i) : i || [] } } , r.dequeue = function(e, t) { r.each(e.nodeType ? [e] : e, function(e, a) { t = t || "fx"; var n = r.queue(a, t) , o = n.shift(); "inprogress" === o && (o = n.shift()), o && ("fx" === t && n.unshift("inprogress"), o.call(a, function() { r.dequeue(a, t) })) }) } , r.fn = r.prototype = { init: function(e) { if (e.nodeType) return this[0] = e, this; throw new Error("Not a DOM node.") }, offset: function() { var t = this[0].getBoundingClientRect ? this[0].getBoundingClientRect() : { top: 0, left: 0 }; return { top: t.top + (e.pageYOffset || document.scrollTop || 0) - (document.clientTop || 0), left: t.left + (e.pageXOffset || document.scrollLeft || 0) - (document.clientLeft || 0) } }, position: function() { function e() { for (var e = this.offsetParent || document; e && "html" === !e.nodeType.toLowerCase && "static" === e.style.position; ) e = e.offsetParent; return e || document } var t = this[0] , e = e.apply(t) , a = this.offset() , n = /^(?:body|html)$/i.test(e.nodeName) ? { top: 0, left: 0 } : r(e).offset(); return a.top -= parseFloat(t.style.marginTop) || 0, a.left -= parseFloat(t.style.marginLeft) || 0, e.style && (n.top += parseFloat(e.style.borderTopWidth) || 0, n.left += parseFloat(e.style.borderLeftWidth) || 0), { top: a.top - n.top, left: a.left - n.left } } }; var a = {}; r.expando = "velocity" + (new Date).getTime(), r.uuid = 0; for (var n = {}, o = n.hasOwnProperty, i = n.toString, s = "Boolean Number String Function Array Date RegExp Object Error".split(" "), l = 0; l < s.length; l++) n["[object " + s[l] + "]"] = s[l].toLowerCase(); r.fn.init.prototype = r.fn, e.Velocity = { Utilities: r } } }(window), function(e) { "object" == typeof module && "object" == typeof module.exports ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : e() }(function() { return function(e, t, r, a) { function n(e) { for (var t = -1, r = e ? e.length : 0, a = []; ++t < r; ) { var n = e[t]; n && a.push(n) } return a } function o(e) { return m.isWrapped(e) ? e = [].slice.call(e) : m.isNode(e) && (e = [e]), e } function i(e) { var t = f.data(e, "velocity"); return null === t ? a : t } function s(e) { return function(t) { return Math.round(t * e) * (1 / e) } } function l(e, r, a, n) { function o(e, t) { return 1 - 3 * t + 3 * e } function i(e, t) { return 3 * t - 6 * e } function s(e) { return 3 * e } function l(e, t, r) { return ((o(t, r) * e + i(t, r)) * e + s(t)) * e } function u(e, t, r) { return 3 * o(t, r) * e * e + 2 * i(t, r) * e + s(t) } function c(t, r) { for (var n = 0; m > n; ++n) { var o = u(r, e, a); if (0 === o) return r; var i = l(r, e, a) - t; r -= i / o } return r } function p() { for (var t = 0; b > t; ++t) w[t] = l(t * x, e, a) } function f(t, r, n) { var o, i, s = 0; do i = r + (n - r) / 2, o = l(i, e, a) - t, o > 0 ? n = i : r = i; while (Math.abs(o) > h && ++s < v);return i } function d(t) { for (var r = 0, n = 1, o = b - 1; n != o && w[n] <= t; ++n) r += x; --n; var i = (t - w[n]) / (w[n + 1] - w[n]) , s = r + i * x , l = u(s, e, a); return l >= y ? c(t, s) : 0 == l ? s : f(t, r, r + x) } function g() { V = !0, (e != r || a != n) && p() } var m = 4 , y = .001 , h = 1e-7 , v = 10 , b = 11 , x = 1 / (b - 1) , S = "Float32Array"in t; if (4 !== arguments.length) return !1; for (var P = 0; 4 > P; ++P) if ("number" != typeof arguments[P] || isNaN(arguments[P]) || !isFinite(arguments[P])) return !1; e = Math.min(e, 1), a = Math.min(a, 1), e = Math.max(e, 0), a = Math.max(a, 0); var w = S ? new Float32Array(b) : new Array(b) , V = !1 , C = function(t) { return V || g(), e === r && a === n ? t : 0 === t ? 0 : 1 === t ? 1 : l(d(t), r, n) }; C.getControlPoints = function() { return [{ x: e, y: r }, { x: a, y: n }] } ; var T = "generateBezier(" + [e, r, a, n] + ")"; return C.toString = function() { return T } , C } function u(e, t) { var r = e; return m.isString(e) ? b.Easings[e] || (r = !1) : r = m.isArray(e) && 1 === e.length ? s.apply(null, e) : m.isArray(e) && 2 === e.length ? x.apply(null, e.concat([t])) : m.isArray(e) && 4 === e.length ? l.apply(null, e) : !1, r === !1 && (r = b.Easings[b.defaults.easing] ? b.defaults.easing : v), r } function c(e) { if (e) { var t = (new Date).getTime() , r = b.State.calls.length; r > 1e4 && (b.State.calls = n(b.State.calls)); for (var o = 0; r > o; o++) if (b.State.calls[o]) { var s = b.State.calls[o] , l = s[0] , u = s[2] , d = s[3] , g = !!d , y = null; d || (d = b.State.calls[o][3] = t - 16); for (var h = Math.min((t - d) / u.duration, 1), v = 0, x = l.length; x > v; v++) { var P = l[v] , V = P.element; if (i(V)) { var C = !1; if (u.display !== a && null !== u.display && "none" !== u.display) { if ("flex" === u.display) { var T = ["-webkit-box", "-moz-box", "-ms-flexbox", "-webkit-flex"]; f.each(T, function(e, t) { S.setPropertyValue(V, "display", t) }) } S.setPropertyValue(V, "display", u.display) } u.visibility !== a && "hidden" !== u.visibility && S.setPropertyValue(V, "visibility", u.visibility); for (var k in P) if ("element" !== k) { var A, F = P[k], j = m.isString(F.easing) ? b.Easings[F.easing] : F.easing; if (1 === h) A = F.endValue; else { var E = F.endValue - F.startValue; if (A = F.startValue + E * j(h, u, E), !g && A === F.currentValue) continue } if (F.currentValue = A, "tween" === k) y = A; else { if (S.Hooks.registered[k]) { var H = S.Hooks.getRoot(k) , N = i(V).rootPropertyValueCache[H]; N && (F.rootPropertyValue = N) } var L = S.setPropertyValue(V, k, F.currentValue + (0 === parseFloat(A) ? "" : F.unitType), F.rootPropertyValue, F.scrollData); S.Hooks.registered[k] && (i(V).rootPropertyValueCache[H] = S.Normalizations.registered[H] ? S.Normalizations.registered[H]("extract", null, L[1]) : L[1]), "transform" === L[0] && (C = !0) } } u.mobileHA && i(V).transformCache.translate3d === a && (i(V).transformCache.translate3d = "(0px, 0px, 0px)", C = !0), C && S.flushTransformCache(V) } } u.display !== a && "none" !== u.display && (b.State.calls[o][2].display = !1), u.visibility !== a && "hidden" !== u.visibility && (b.State.calls[o][2].visibility = !1), u.progress && u.progress.call(s[1], s[1], h, Math.max(0, d + u.duration - t), d, y), 1 === h && p(o) } } b.State.isTicking && w(c) } function p(e, t) { if (!b.State.calls[e]) return !1; for (var r = b.State.calls[e][0], n = b.State.calls[e][1], o = b.State.calls[e][2], s = b.State.calls[e][4], l = !1, u = 0, c = r.length; c > u; u++) { var p = r[u].element; if (t || o.loop || ("none" === o.display && S.setPropertyValue(p, "display", o.display), "hidden" === o.visibility && S.setPropertyValue(p, "visibility", o.visibility)), o.loop !== !0 && (f.queue(p)[1] === a || !/\.velocityQueueEntryFlag/i.test(f.queue(p)[1])) && i(p)) { i(p).isAnimating = !1, i(p).rootPropertyValueCache = {}; var d = !1; f.each(S.Lists.transforms3D, function(e, t) { var r = /^scale/.test(t) ? 1 : 0 , n = i(p).transformCache[t]; i(p).transformCache[t] !== a && new RegExp("^\\(" + r + "[^.]").test(n) && (d = !0, delete i(p).transformCache[t]) }), o.mobileHA && (d = !0, delete i(p).transformCache.translate3d), d && S.flushTransformCache(p), S.Values.removeClass(p, "velocity-animating") } if (!t && o.complete && !o.loop && u === c - 1) try { o.complete.call(n, n) } catch (g) { setTimeout(function() { throw g }, 1) } s && o.loop !== !0 && s(n), i(p) && o.loop === !0 && !t && (f.each(i(p).tweensContainer, function(e, t) { /^rotate/.test(e) && 360 === parseFloat(t.endValue) && (t.endValue = 0, t.startValue = 360), /^backgroundPosition/.test(e) && 100 === parseFloat(t.endValue) && "%" === t.unitType && (t.endValue = 0, t.startValue = 100) }), b(p, "reverse", { loop: !0, delay: o.delay })), o.queue !== !1 && f.dequeue(p, o.queue) } b.State.calls[e] = !1; for (var m = 0, y = b.State.calls.length; y > m; m++) if (b.State.calls[m] !== !1) { l = !0; break } l === !1 && (b.State.isTicking = !1, delete b.State.calls, b.State.calls = []) } var f, d = function() { if (r.documentMode) return r.documentMode; for (var e = 7; e > 4; e--) { var t = r.createElement("div"); if (t.innerHTML = "", t.getElementsByTagName("span").length) return t = null, e } return a }(), g = function() { var e = 0; return t.webkitRequestAnimationFrame || t.mozRequestAnimationFrame || function(t) { var r, a = (new Date).getTime(); return r = Math.max(0, 16 - (a - e)), e = a + r, setTimeout(function() { t(a + r) }, r) } }(), m = { isString: function(e) { return "string" == typeof e }, isArray: Array.isArray || function(e) { return "[object Array]" === Object.prototype.toString.call(e) } , isFunction: function(e) { return "[object Function]" === Object.prototype.toString.call(e) }, isNode: function(e) { return e && e.nodeType }, isNodeList: function(e) { return "object" == typeof e && /^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(e)) && e.length !== a && (0 === e.length || "object" == typeof e[0] && e[0].nodeType > 0) }, isWrapped: function(e) { return e && (e.jquery || t.Zepto && t.Zepto.zepto.isZ(e)) }, isSVG: function(e) { return t.SVGElement && e instanceof t.SVGElement }, isEmptyObject: function(e) { for (var t in e) return !1; return !0 } }, y = !1; if (e.fn && e.fn.jquery ? (f = e, y = !0) : f = t.Velocity.Utilities, 8 >= d && !y) throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity."); if (7 >= d) return void (jQuery.fn.velocity = jQuery.fn.animate); var h = 400 , v = "swing" , b = { State: { isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), isAndroid: /Android/i.test(navigator.userAgent), isGingerbread: /Android 2\.3\.[3-7]/i.test(navigator.userAgent), isChrome: t.chrome, isFirefox: /Firefox/i.test(navigator.userAgent), prefixElement: r.createElement("div"), prefixMatches: {}, scrollAnchor: null, scrollPropertyLeft: null, scrollPropertyTop: null, isTicking: !1, calls: [] }, CSS: {}, Utilities: f, Redirects: {}, Easings: {}, Promise: t.Promise, defaults: { queue: "", duration: h, easing: v, begin: a, complete: a, progress: a, display: a, visibility: a, loop: !1, delay: !1, mobileHA: !0, _cacheValues: !0 }, init: function(e) { f.data(e, "velocity", { isSVG: m.isSVG(e), isAnimating: !1, computedStyle: null, tweensContainer: null, rootPropertyValueCache: {}, transformCache: {} }) }, hook: null, mock: !1, version: { major: 1, minor: 2, patch: 2 }, debug: !1 }; t.pageYOffset !== a ? (b.State.scrollAnchor = t, b.State.scrollPropertyLeft = "pageXOffset", b.State.scrollPropertyTop = "pageYOffset") : (b.State.scrollAnchor = r.documentElement || r.body.parentNode || r.body, b.State.scrollPropertyLeft = "scrollLeft", b.State.scrollPropertyTop = "scrollTop"); var x = function() { function e(e) { return -e.tension * e.x - e.friction * e.v } function t(t, r, a) { var n = { x: t.x + a.dx * r, v: t.v + a.dv * r, tension: t.tension, friction: t.friction }; return { dx: n.v, dv: e(n) } } function r(r, a) { var n = { dx: r.v, dv: e(r) } , o = t(r, .5 * a, n) , i = t(r, .5 * a, o) , s = t(r, a, i) , l = 1 / 6 * (n.dx + 2 * (o.dx + i.dx) + s.dx) , u = 1 / 6 * (n.dv + 2 * (o.dv + i.dv) + s.dv); return r.x = r.x + l * a, r.v = r.v + u * a, r } return function a(e, t, n) { var o, i, s, l = { x: -1, v: 0, tension: null, friction: null }, u = [0], c = 0, p = 1e-4, f = .016; for (e = parseFloat(e) || 500, t = parseFloat(t) || 20, n = n || null, l.tension = e, l.friction = t, o = null !== n, o ? (c = a(e, t), i = c / n * f) : i = f; s = r(s || l, i), u.push(1 + s.x), c += 16, Math.abs(s.x) > p && Math.abs(s.v) > p; ) ; return o ? function(e) { return u[e * (u.length - 1) | 0] } : c } }(); b.Easings = { linear: function(e) { return e }, swing: function(e) { return .5 - Math.cos(e * Math.PI) / 2 }, spring: function(e) { return 1 - Math.cos(4.5 * e * Math.PI) * Math.exp(6 * -e) } }, f.each([["ease", [.25, .1, .25, 1]], ["ease-in", [.42, 0, 1, 1]], ["ease-out", [0, 0, .58, 1]], ["ease-in-out", [.42, 0, .58, 1]], ["easeInSine", [.47, 0, .745, .715]], ["easeOutSine", [.39, .575, .565, 1]], ["easeInOutSine", [.445, .05, .55, .95]], ["easeInQuad", [.55, .085, .68, .53]], ["easeOutQuad", [.25, .46, .45, .94]], ["easeInOutQuad", [.455, .03, .515, .955]], ["easeInCubic", [.55, .055, .675, .19]], ["easeOutCubic", [.215, .61, .355, 1]], ["easeInOutCubic", [.645, .045, .355, 1]], ["easeInQuart", [.895, .03, .685, .22]], ["easeOutQuart", [.165, .84, .44, 1]], ["easeInOutQuart", [.77, 0, .175, 1]], ["easeInQuint", [.755, .05, .855, .06]], ["easeOutQuint", [.23, 1, .32, 1]], ["easeInOutQuint", [.86, 0, .07, 1]], ["easeInExpo", [.95, .05, .795, .035]], ["easeOutExpo", [.19, 1, .22, 1]], ["easeInOutExpo", [1, 0, 0, 1]], ["easeInCirc", [.6, .04, .98, .335]], ["easeOutCirc", [.075, .82, .165, 1]], ["easeInOutCirc", [.785, .135, .15, .86]]], function(e, t) { b.Easings[t[0]] = l.apply(null, t[1]) }); var S = b.CSS = { RegEx: { isHex: /^#([A-f\d]{3}){1,2}$/i, valueUnwrap: /^[A-z]+\((.*)\)$/i, wrappedValueAlreadyExtracted: /[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/, valueSplit: /([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi }, Lists: { colors: ["fill", "stroke", "stopColor", "color", "backgroundColor", "borderColor", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor", "outlineColor"], transformsBase: ["translateX", "translateY", "scale", "scaleX", "scaleY", "skewX", "skewY", "rotateZ"], transforms3D: ["transformPerspective", "translateZ", "scaleZ", "rotateX", "rotateY"] }, Hooks: { templates: { textShadow: ["Color X Y Blur", "black 0px 0px 0px"], boxShadow: ["Color X Y Blur Spread", "black 0px 0px 0px 0px"], clip: ["Top Right Bottom Left", "0px 0px 0px 0px"], backgroundPosition: ["X Y", "0% 0%"], transformOrigin: ["X Y Z", "50% 50% 0px"], perspectiveOrigin: ["X Y", "50% 50%"] }, registered: {}, register: function() { for (var e = 0; e < S.Lists.colors.length; e++) { var t = "color" === S.Lists.colors[e] ? "0 0 0 1" : "255 255 255 1"; S.Hooks.templates[S.Lists.colors[e]] = ["Red Green Blue Alpha", t] } var r, a, n; if (d) for (r in S.Hooks.templates) { a = S.Hooks.templates[r], n = a[0].split(" "); var o = a[1].match(S.RegEx.valueSplit); "Color" === n[0] && (n.push(n.shift()), o.push(o.shift()), S.Hooks.templates[r] = [n.join(" "), o.join(" ")]) } for (r in S.Hooks.templates) { a = S.Hooks.templates[r], n = a[0].split(" "); for (var e in n) { var i = r + n[e] , s = e; S.Hooks.registered[i] = [r, s] } } }, getRoot: function(e) { var t = S.Hooks.registered[e]; return t ? t[0] : e }, cleanRootPropertyValue: function(e, t) { return S.RegEx.valueUnwrap.test(t) && (t = t.match(S.RegEx.valueUnwrap)[1]), S.Values.isCSSNullValue(t) && (t = S.Hooks.templates[e][1]), t }, extractValue: function(e, t) { var r = S.Hooks.registered[e]; if (r) { var a = r[0] , n = r[1]; return t = S.Hooks.cleanRootPropertyValue(a, t), t.toString().match(S.RegEx.valueSplit)[n] } return t }, injectValue: function(e, t, r) { var a = S.Hooks.registered[e]; if (a) { var n, o, i = a[0], s = a[1]; return r = S.Hooks.cleanRootPropertyValue(i, r), n = r.toString().match(S.RegEx.valueSplit), n[s] = t, o = n.join(" ") } return r } }, Normalizations: { registered: { clip: function(e, t, r) { switch (e) { case "name": return "clip"; case "extract": var a; return S.RegEx.wrappedValueAlreadyExtracted.test(r) ? a = r : (a = r.toString().match(S.RegEx.valueUnwrap), a = a ? a[1].replace(/,(\s+)?/g, " ") : r), a; case "inject": return "rect(" + r + ")" } }, blur: function(e, t, r) { switch (e) { case "name": return b.State.isFirefox ? "filter" : "-webkit-filter"; case "extract": var a = parseFloat(r); if (!a && 0 !== a) { var n = r.toString().match(/blur\(([0-9]+[A-z]+)\)/i); a = n ? n[1] : 0 } return a; case "inject": return parseFloat(r) ? "blur(" + r + ")" : "none" } }, opacity: function(e, t, r) { if (8 >= d) switch (e) { case "name": return "filter"; case "extract": var a = r.toString().match(/alpha\(opacity=(.*)\)/i); return r = a ? a[1] / 100 : 1; case "inject": return t.style.zoom = 1, parseFloat(r) >= 1 ? "" : "alpha(opacity=" + parseInt(100 * parseFloat(r), 10) + ")" } else switch (e) { case "name": return "opacity"; case "extract": return r; case "inject": return r } } }, register: function() { 9 >= d || b.State.isGingerbread || (S.Lists.transformsBase = S.Lists.transformsBase.concat(S.Lists.transforms3D)); for (var e = 0; e < S.Lists.transformsBase.length; e++) !function() { var t = S.Lists.transformsBase[e]; S.Normalizations.registered[t] = function(e, r, n) { switch (e) { case "name": return "transform"; case "extract": return i(r) === a || i(r).transformCache[t] === a ? /^scale/i.test(t) ? 1 : 0 : i(r).transformCache[t].replace(/[()]/g, ""); case "inject": var o = !1; switch (t.substr(0, t.length - 1)) { case "translate": o = !/(%|px|em|rem|vw|vh|\d)$/i.test(n); break; case "scal": case "scale": b.State.isAndroid && i(r).transformCache[t] === a && 1 > n && (n = 1), o = !/(\d)$/i.test(n); break; case "skew": o = !/(deg|\d)$/i.test(n); break; case "rotate": o = !/(deg|\d)$/i.test(n) } return o || (i(r).transformCache[t] = "(" + n + ")"), i(r).transformCache[t] } } }(); for (var e = 0; e < S.Lists.colors.length; e++) !function() { var t = S.Lists.colors[e]; S.Normalizations.registered[t] = function(e, r, n) { switch (e) { case "name": return t; case "extract": var o; if (S.RegEx.wrappedValueAlreadyExtracted.test(n)) o = n; else { var i, s = { black: "rgb(0, 0, 0)", blue: "rgb(0, 0, 255)", gray: "rgb(128, 128, 128)", green: "rgb(0, 128, 0)", red: "rgb(255, 0, 0)", white: "rgb(255, 255, 255)" }; /^[A-z]+$/i.test(n) ? i = s[n] !== a ? s[n] : s.black : S.RegEx.isHex.test(n) ? i = "rgb(" + S.Values.hexToRgb(n).join(" ") + ")" : /^rgba?\(/i.test(n) || (i = s.black), o = (i || n).toString().match(S.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g, " ") } return 8 >= d || 3 !== o.split(" ").length || (o += " 1"), o; case "inject": return 8 >= d ? 4 === n.split(" ").length && (n = n.split(/\s+/).slice(0, 3).join(" ")) : 3 === n.split(" ").length && (n += " 1"), (8 >= d ? "rgb" : "rgba") + "(" + n.replace(/\s+/g, ",").replace(/\.(\d)+(?=,)/g, "") + ")" } } }() } }, Names: { camelCase: function(e) { return e.replace(/-(\w)/g, function(e, t) { return t.toUpperCase() }) }, SVGAttribute: function(e) { var t = "width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2"; return (d || b.State.isAndroid && !b.State.isChrome) && (t += "|transform"), new RegExp("^(" + t + ")$","i").test(e) }, prefixCheck: function(e) { if (b.State.prefixMatches[e]) return [b.State.prefixMatches[e], !0]; for (var t = ["", "Webkit", "Moz", "ms", "O"], r = 0, a = t.length; a > r; r++) { var n; if (n = 0 === r ? e : t[r] + e.replace(/^\w/, function(e) { return e.toUpperCase() }), m.isString(b.State.prefixElement.style[n])) return b.State.prefixMatches[e] = n, [n, !0] } return [e, !1] } }, Values: { hexToRgb: function(e) { var t, r = /^#?([a-f\d])([a-f\d])([a-f\d])$/i, a = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; return e = e.replace(r, function(e, t, r, a) { return t + t + r + r + a + a }), t = a.exec(e), t ? [parseInt(t[1], 16), parseInt(t[2], 16), parseInt(t[3], 16)] : [0, 0, 0] }, isCSSNullValue: function(e) { return 0 == e || /^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(e) }, getUnitType: function(e) { return /^(rotate|skew)/i.test(e) ? "deg" : /(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(e) ? "" : "px" }, getDisplayType: function(e) { var t = e && e.tagName.toString().toLowerCase(); return /^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(t) ? "inline" : /^(li)$/i.test(t) ? "list-item" : /^(tr)$/i.test(t) ? "table-row" : /^(table)$/i.test(t) ? "table" : /^(tbody)$/i.test(t) ? "table-row-group" : "block" }, addClass: function(e, t) { e.classList ? e.classList.add(t) : e.className += (e.className.length ? " " : "") + t }, removeClass: function(e, t) { e.classList ? e.classList.remove(t) : e.className = e.className.toString().replace(new RegExp("(^|\\s)" + t.split(" ").join("|") + "(\\s|$)","gi"), " ") } }, getPropertyValue: function(e, r, n, o) { function s(e, r) { function n() { u && S.setPropertyValue(e, "display", "none") } var l = 0; if (8 >= d) l = f.css(e, r); else { var u = !1; if (/^(width|height)$/.test(r) && 0 === S.getPropertyValue(e, "display") && (u = !0, S.setPropertyValue(e, "display", S.Values.getDisplayType(e))), !o) { if ("height" === r && "border-box" !== S.getPropertyValue(e, "boxSizing").toString().toLowerCase()) { var c = e.offsetHeight - (parseFloat(S.getPropertyValue(e, "borderTopWidth")) || 0) - (parseFloat(S.getPropertyValue(e, "borderBottomWidth")) || 0) - (parseFloat(S.getPropertyValue(e, "paddingTop")) || 0) - (parseFloat(S.getPropertyValue(e, "paddingBottom")) || 0); return n(), c } if ("width" === r && "border-box" !== S.getPropertyValue(e, "boxSizing").toString().toLowerCase()) { var p = e.offsetWidth - (parseFloat(S.getPropertyValue(e, "borderLeftWidth")) || 0) - (parseFloat(S.getPropertyValue(e, "borderRightWidth")) || 0) - (parseFloat(S.getPropertyValue(e, "paddingLeft")) || 0) - (parseFloat(S.getPropertyValue(e, "paddingRight")) || 0); return n(), p } } var g; g = i(e) === a ? t.getComputedStyle(e, null) : i(e).computedStyle ? i(e).computedStyle : i(e).computedStyle = t.getComputedStyle(e, null), "borderColor" === r && (r = "borderTopColor"), l = 9 === d && "filter" === r ? g.getPropertyValue(r) : g[r], ("" === l || null === l) && (l = e.style[r]), n() } if ("auto" === l && /^(top|right|bottom|left)$/i.test(r)) { var m = s(e, "position"); ("fixed" === m || "absolute" === m && /top|left/i.test(r)) && (l = f(e).position()[r] + "px") } return l } var l; if (S.Hooks.registered[r]) { var u = r , c = S.Hooks.getRoot(u); n === a && (n = S.getPropertyValue(e, S.Names.prefixCheck(c)[0])), S.Normalizations.registered[c] && (n = S.Normalizations.registered[c]("extract", e, n)), l = S.Hooks.extractValue(u, n) } else if (S.Normalizations.registered[r]) { var p, g; p = S.Normalizations.registered[r]("name", e), "transform" !== p && (g = s(e, S.Names.prefixCheck(p)[0]), S.Values.isCSSNullValue(g) && S.Hooks.templates[r] && (g = S.Hooks.templates[r][1])), l = S.Normalizations.registered[r]("extract", e, g) } if (!/^[\d-]/.test(l)) if (i(e) && i(e).isSVG && S.Names.SVGAttribute(r)) if (/^(height|width)$/i.test(r)) try { l = e.getBBox()[r] } catch (m) { l = 0 } else l = e.getAttribute(r); else l = s(e, S.Names.prefixCheck(r)[0]); return S.Values.isCSSNullValue(l) && (l = 0), b.debug >= 2 && console.log("Get " + r + ": " + l), l }, setPropertyValue: function(e, r, a, n, o) { var s = r; if ("scroll" === r) o.container ? o.container["scroll" + o.direction] = a : "Left" === o.direction ? t.scrollTo(a, o.alternateValue) : t.scrollTo(o.alternateValue, a); else if (S.Normalizations.registered[r] && "transform" === S.Normalizations.registered[r]("name", e)) S.Normalizations.registered[r]("inject", e, a), s = "transform", a = i(e).transformCache[r]; else { if (S.Hooks.registered[r]) { var l = r , u = S.Hooks.getRoot(r); n = n || S.getPropertyValue(e, u), a = S.Hooks.injectValue(l, a, n), r = u } if (S.Normalizations.registered[r] && (a = S.Normalizations.registered[r]("inject", e, a), r = S.Normalizations.registered[r]("name", e)), s = S.Names.prefixCheck(r)[0], 8 >= d) try { e.style[s] = a } catch (c) { b.debug && console.log("Browser does not support [" + a + "] for [" + s + "]") } else i(e) && i(e).isSVG && S.Names.SVGAttribute(r) ? e.setAttribute(r, a) : e.style[s] = a; b.debug >= 2 && console.log("Set " + r + " (" + s + "): " + a) } return [s, a] }, flushTransformCache: function(e) { function t(t) { return parseFloat(S.getPropertyValue(e, t)) } var r = ""; if ((d || b.State.isAndroid && !b.State.isChrome) && i(e).isSVG) { var a = { translate: [t("translateX"), t("translateY")], skewX: [t("skewX")], skewY: [t("skewY")], scale: 1 !== t("scale") ? [t("scale"), t("scale")] : [t("scaleX"), t("scaleY")], rotate: [t("rotateZ"), 0, 0] }; f.each(i(e).transformCache, function(e) { /^translate/i.test(e) ? e = "translate" : /^scale/i.test(e) ? e = "scale" : /^rotate/i.test(e) && (e = "rotate"), a[e] && (r += e + "(" + a[e].join(" ") + ") ", delete a[e]) }) } else { var n, o; f.each(i(e).transformCache, function(t) { return n = i(e).transformCache[t], "transformPerspective" === t ? (o = n, !0) : (9 === d && "rotateZ" === t && (t = "rotate"), void (r += t + n + " ")) }), o && (r = "perspective" + o + " " + r) } S.setPropertyValue(e, "transform", r) } }; S.Hooks.register(), S.Normalizations.register(), b.hook = function(e, t, r) { var n = a; return e = o(e), f.each(e, function(e, o) { if (i(o) === a && b.init(o), r === a) n === a && (n = b.CSS.getPropertyValue(o, t)); else { var s = b.CSS.setPropertyValue(o, t, r); "transform" === s[0] && b.CSS.flushTransformCache(o), n = s } }), n } ; var P = function() { function e() { return s ? k.promise || null : l } function n() { function e(e) { function p(e, t) { var r = a , n = a , i = a; return m.isArray(e) ? (r = e[0], !m.isArray(e[1]) && /^[\d-]/.test(e[1]) || m.isFunction(e[1]) || S.RegEx.isHex.test(e[1]) ? i = e[1] : (m.isString(e[1]) && !S.RegEx.isHex.test(e[1]) || m.isArray(e[1])) && (n = t ? e[1] : u(e[1], s.duration), e[2] !== a && (i = e[2]))) : r = e, t || (n = n || s.easing), m.isFunction(r) && (r = r.call(o, V, w)), m.isFunction(i) && (i = i.call(o, V, w)), [r || 0, n, i] } function d(e, t) { var r, a; return a = (t || "0").toString().toLowerCase().replace(/[%A-z]+$/, function(e) { return r = e, "" }), r || (r = S.Values.getUnitType(e)), [a, r] } function h() { var e = { myParent: o.parentNode || r.body, position: S.getPropertyValue(o, "position"), fontSize: S.getPropertyValue(o, "fontSize") } , a = e.position === L.lastPosition && e.myParent === L.lastParent , n = e.fontSize === L.lastFontSize; L.lastParent = e.myParent, L.lastPosition = e.position, L.lastFontSize = e.fontSize; var s = 100 , l = {}; if (n && a) l.emToPx = L.lastEmToPx, l.percentToPxWidth = L.lastPercentToPxWidth, l.percentToPxHeight = L.lastPercentToPxHeight; else { var u = i(o).isSVG ? r.createElementNS("http://www.w3.org/2000/svg", "rect") : r.createElement("div"); b.init(u), e.myParent.appendChild(u), f.each(["overflow", "overflowX", "overflowY"], function(e, t) { b.CSS.setPropertyValue(u, t, "hidden") }), b.CSS.setPropertyValue(u, "position", e.position), b.CSS.setPropertyValue(u, "fontSize", e.fontSize), b.CSS.setPropertyValue(u, "boxSizing", "content-box"), f.each(["minWidth", "maxWidth", "width", "minHeight", "maxHeight", "height"], function(e, t) { b.CSS.setPropertyValue(u, t, s + "%") }), b.CSS.setPropertyValue(u, "paddingLeft", s + "em"), l.percentToPxWidth = L.lastPercentToPxWidth = (parseFloat(S.getPropertyValue(u, "width", null, !0)) || 1) / s, l.percentToPxHeight = L.lastPercentToPxHeight = (parseFloat(S.getPropertyValue(u, "height", null, !0)) || 1) / s, l.emToPx = L.lastEmToPx = (parseFloat(S.getPropertyValue(u, "paddingLeft")) || 1) / s, e.myParent.removeChild(u) } return null === L.remToPx && (L.remToPx = parseFloat(S.getPropertyValue(r.body, "fontSize")) || 16), null === L.vwToPx && (L.vwToPx = parseFloat(t.innerWidth) / 100, L.vhToPx = parseFloat(t.innerHeight) / 100), l.remToPx = L.remToPx, l.vwToPx = L.vwToPx, l.vhToPx = L.vhToPx, b.debug >= 1 && console.log("Unit ratios: " + JSON.stringify(l), o), l } if (s.begin && 0 === V) try { s.begin.call(g, g) } catch (x) { setTimeout(function() { throw x }, 1) } if ("scroll" === A) { var P, C, T, F = /^x$/i.test(s.axis) ? "Left" : "Top", j = parseFloat(s.offset) || 0; s.container ? m.isWrapped(s.container) || m.isNode(s.container) ? (s.container = s.container[0] || s.container, P = s.container["scroll" + F], T = P + f(o).position()[F.toLowerCase()] + j) : s.container = null : (P = b.State.scrollAnchor[b.State["scrollProperty" + F]], C = b.State.scrollAnchor[b.State["scrollProperty" + ("Left" === F ? "Top" : "Left")]], T = f(o).offset()[F.toLowerCase()] + j), l = { scroll: { rootPropertyValue: !1, startValue: P, currentValue: P, endValue: T, unitType: "", easing: s.easing, scrollData: { container: s.container, direction: F, alternateValue: C } }, element: o }, b.debug && console.log("tweensContainer (scroll): ", l.scroll, o) } else if ("reverse" === A) { if (!i(o).tweensContainer) return void f.dequeue(o, s.queue); "none" === i(o).opts.display && (i(o).opts.display = "auto"), "hidden" === i(o).opts.visibility && (i(o).opts.visibility = "visible"), i(o).opts.loop = !1, i(o).opts.begin = null, i(o).opts.complete = null, v.easing || delete s.easing, v.duration || delete s.duration, s = f.extend({}, i(o).opts, s); var E = f.extend(!0, {}, i(o).tweensContainer); for (var H in E) if ("element" !== H) { var N = E[H].startValue; E[H].startValue = E[H].currentValue = E[H].endValue, E[H].endValue = N, m.isEmptyObject(v) || (E[H].easing = s.easing), b.debug && console.log("reverse tweensContainer (" + H + "): " + JSON.stringify(E[H]), o) } l = E } else if ("start" === A) { var E; i(o).tweensContainer && i(o).isAnimating === !0 && (E = i(o).tweensContainer), f.each(y, function(e, t) { if (RegExp("^" + S.Lists.colors.join("$|^") + "$").test(e)) { var r = p(t, !0) , n = r[0] , o = r[1] , i = r[2]; if (S.RegEx.isHex.test(n)) { for (var s = ["Red", "Green", "Blue"], l = S.Values.hexToRgb(n), u = i ? S.Values.hexToRgb(i) : a, c = 0; c < s.length; c++) { var f = [l[c]]; o && f.push(o), u !== a && f.push(u[c]), y[e + s[c]] = f } delete y[e] } } }); for (var z in y) { var O = p(y[z]) , q = O[0] , $ = O[1] , M = O[2]; z = S.Names.camelCase(z); var I = S.Hooks.getRoot(z) , B = !1; if (i(o).isSVG || "tween" === I || S.Names.prefixCheck(I)[1] !== !1 || S.Normalizations.registered[I] !== a) { (s.display !== a && null !== s.display && "none" !== s.display || s.visibility !== a && "hidden" !== s.visibility) && /opacity|filter/.test(z) && !M && 0 !== q && (M = 0), s._cacheValues && E && E[z] ? (M === a && (M = E[z].endValue + E[z].unitType), B = i(o).rootPropertyValueCache[I]) : S.Hooks.registered[z] ? M === a ? (B = S.getPropertyValue(o, I), M = S.getPropertyValue(o, z, B)) : B = S.Hooks.templates[I][1] : M === a && (M = S.getPropertyValue(o, z)); var W, G, Y, D = !1; if (W = d(z, M), M = W[0], Y = W[1], W = d(z, q), q = W[0].replace(/^([+-\/*])=/, function(e, t) { return D = t, "" }), G = W[1], M = parseFloat(M) || 0, q = parseFloat(q) || 0, "%" === G && (/^(fontSize|lineHeight)$/.test(z) ? (q /= 100, G = "em") : /^scale/.test(z) ? (q /= 100, G = "") : /(Red|Green|Blue)$/i.test(z) && (q = q / 100 * 255, G = "")), /[\/*]/.test(D)) G = Y; else if (Y !== G && 0 !== M) if (0 === q) G = Y; else { n = n || h(); var Q = /margin|padding|left|right|width|text|word|letter/i.test(z) || /X$/.test(z) || "x" === z ? "x" : "y"; switch (Y) { case "%": M *= "x" === Q ? n.percentToPxWidth : n.percentToPxHeight; break; case "px": break; default: M *= n[Y + "ToPx"] } switch (G) { case "%": M *= 1 / ("x" === Q ? n.percentToPxWidth : n.percentToPxHeight); break; case "px": break; default: M *= 1 / n[G + "ToPx"] } } switch (D) { case "+": q = M + q; break; case "-": q = M - q; break; case "*": q = M * q; break; case "/": q = M / q } l[z] = { rootPropertyValue: B, startValue: M, currentValue: M, endValue: q, unitType: G, easing: $ }, b.debug && console.log("tweensContainer (" + z + "): " + JSON.stringify(l[z]), o) } else b.debug && console.log("Skipping [" + I + "] due to a lack of browser support.") } l.element = o } l.element && (S.Values.addClass(o, "velocity-animating"), R.push(l), "" === s.queue && (i(o).tweensContainer = l, i(o).opts = s), i(o).isAnimating = !0, V === w - 1 ? (b.State.calls.push([R, g, s, null, k.resolver]), b.State.isTicking === !1 && (b.State.isTicking = !0, c())) : V++) } var n, o = this, s = f.extend({}, b.defaults, v), l = {}; switch (i(o) === a && b.init(o), parseFloat(s.delay) && s.queue !== !1 && f.queue(o, s.queue, function(e) { b.velocityQueueEntryFlag = !0, i(o).delayTimer = { setTimeout: setTimeout(e, parseFloat(s.delay)), next: e } }), s.duration.toString().toLowerCase()) { case "fast": s.duration = 200; break; case "normal": s.duration = h; break; case "slow": s.duration = 600; break; default: s.duration = parseFloat(s.duration) || 1 } b.mock !== !1 && (b.mock === !0 ? s.duration = s.delay = 1 : (s.duration *= parseFloat(b.mock) || 1, s.delay *= parseFloat(b.mock) || 1)), s.easing = u(s.easing, s.duration), s.begin && !m.isFunction(s.begin) && (s.begin = null), s.progress && !m.isFunction(s.progress) && (s.progress = null), s.complete && !m.isFunction(s.complete) && (s.complete = null), s.display !== a && null !== s.display && (s.display = s.display.toString().toLowerCase(), "auto" === s.display && (s.display = b.CSS.Values.getDisplayType(o))), s.visibility !== a && null !== s.visibility && (s.visibility = s.visibility.toString().toLowerCase()), s.mobileHA = s.mobileHA && b.State.isMobile && !b.State.isGingerbread, s.queue === !1 ? s.delay ? setTimeout(e, s.delay) : e() : f.queue(o, s.queue, function(t, r) { return r === !0 ? (k.promise && k.resolver(g), !0) : (b.velocityQueueEntryFlag = !0, void e(t)) }), "" !== s.queue && "fx" !== s.queue || "inprogress" === f.queue(o)[0] || f.dequeue(o) } var s, l, d, g, y, v, x = arguments[0] && (arguments[0].p || f.isPlainObject(arguments[0].properties) && !arguments[0].properties.names || m.isString(arguments[0].properties)); if (m.isWrapped(this) ? (s = !1, d = 0, g = this, l = this) : (s = !0, d = 1, g = x ? arguments[0].elements || arguments[0].e : arguments[0]), g = o(g)) { x ? (y = arguments[0].properties || arguments[0].p, v = arguments[0].options || arguments[0].o) : (y = arguments[d], v = arguments[d + 1]); var w = g.length , V = 0; if (!/^(stop|finish)$/i.test(y) && !f.isPlainObject(v)) { var C = d + 1; v = {}; for (var T = C; T < arguments.length; T++) m.isArray(arguments[T]) || !/^(fast|normal|slow)$/i.test(arguments[T]) && !/^\d/.test(arguments[T]) ? m.isString(arguments[T]) || m.isArray(arguments[T]) ? v.easing = arguments[T] : m.isFunction(arguments[T]) && (v.complete = arguments[T]) : v.duration = arguments[T] } var k = { promise: null, resolver: null, rejecter: null }; s && b.Promise && (k.promise = new b.Promise(function(e, t) { k.resolver = e, k.rejecter = t } )); var A; switch (y) { case "scroll": A = "scroll"; break; case "reverse": A = "reverse"; break; case "finish": case "stop": f.each(g, function(e, t) { i(t) && i(t).delayTimer && (clearTimeout(i(t).delayTimer.setTimeout), i(t).delayTimer.next && i(t).delayTimer.next(), delete i(t).delayTimer) }); var F = []; return f.each(b.State.calls, function(e, t) { t && f.each(t[1], function(r, n) { var o = v === a ? "" : v; return o === !0 || t[2].queue === o || v === a && t[2].queue === !1 ? void f.each(g, function(r, a) { a === n && ((v === !0 || m.isString(v)) && (f.each(f.queue(a, m.isString(v) ? v : ""), function(e, t) { m.isFunction(t) && t(null, !0) }), f.queue(a, m.isString(v) ? v : "", [])), "stop" === y ? (i(a) && i(a).tweensContainer && o !== !1 && f.each(i(a).tweensContainer, function(e, t) { t.endValue = t.currentValue }), F.push(e)) : "finish" === y && (t[2].duration = 1)) }) : !0 }) }), "stop" === y && (f.each(F, function(e, t) { p(t, !0) }), k.promise && k.resolver(g)), e(); default: if (!f.isPlainObject(y) || m.isEmptyObject(y)) { if (m.isString(y) && b.Redirects[y]) { var j = f.extend({}, v) , E = j.duration , H = j.delay || 0; return j.backwards === !0 && (g = f.extend(!0, [], g).reverse()), f.each(g, function(e, t) { parseFloat(j.stagger) ? j.delay = H + parseFloat(j.stagger) * e : m.isFunction(j.stagger) && (j.delay = H + j.stagger.call(t, e, w)), j.drag && (j.duration = parseFloat(E) || (/^(callout|transition)/.test(y) ? 1e3 : h), j.duration = Math.max(j.duration * (j.backwards ? 1 - e / w : (e + 1) / w), .75 * j.duration, 200)), b.Redirects[y].call(t, t, j || {}, e, w, g, k.promise ? k : a) }), e() } var N = "Velocity: First argument (" + y + ") was not a property map, a known action, or a registered redirect. Aborting."; return k.promise ? k.rejecter(new Error(N)) : console.log(N), e() } A = "start" } var L = { lastParent: null, lastPosition: null, lastFontSize: null, lastPercentToPxWidth: null, lastPercentToPxHeight: null, lastEmToPx: null, remToPx: null, vwToPx: null, vhToPx: null } , R = []; f.each(g, function(e, t) { m.isNode(t) && n.call(t) }); var z, j = f.extend({}, b.defaults, v); if (j.loop = parseInt(j.loop), z = 2 * j.loop - 1, j.loop) for (var O = 0; z > O; O++) { var q = { delay: j.delay, progress: j.progress }; O === z - 1 && (q.display = j.display, q.visibility = j.visibility, q.complete = j.complete), P(g, "reverse", q) } return e() } }; b = f.extend(P, b), b.animate = P; var w = t.requestAnimationFrame || g; return b.State.isMobile || r.hidden === a || r.addEventListener("visibilitychange", function() { r.hidden ? (w = function(e) { return setTimeout(function() { e(!0) }, 16) } , c()) : w = t.requestAnimationFrame || g }), e.Velocity = b, e !== t && (e.fn.velocity = P, e.fn.velocity.defaults = b.defaults), f.each(["Down", "Up"], function(e, t) { b.Redirects["slide" + t] = function(e, r, n, o, i, s) { var l = f.extend({}, r) , u = l.begin , c = l.complete , p = { height: "", marginTop: "", marginBottom: "", paddingTop: "", paddingBottom: "" } , d = {}; l.display === a && (l.display = "Down" === t ? "inline" === b.CSS.Values.getDisplayType(e) ? "inline-block" : "block" : "none"), l.begin = function() { u && u.call(i, i); for (var r in p) { d[r] = e.style[r]; var a = b.CSS.getPropertyValue(e, r); p[r] = "Down" === t ? [a, 0] : [0, a] } d.overflow = e.style.overflow, e.style.overflow = "hidden" } , l.complete = function() { for (var t in d) e.style[t] = d[t]; c && c.call(i, i), s && s.resolver(i) } , b(e, p, l) } }), f.each(["In", "Out"], function(e, t) { b.Redirects["fade" + t] = function(e, r, n, o, i, s) { var l = f.extend({}, r) , u = { opacity: "In" === t ? 1 : 0 } , c = l.complete; l.complete = n !== o - 1 ? l.begin = null : function() { c && c.call(i, i), s && s.resolver(i) } , l.display === a && (l.display = "In" === t ? "auto" : "none"), b(this, u, l) } }), b }(window.jQuery || window.Zepto || window, window, document) })); /*! * Chart.js * http://chartjs.org/ * Version: 1.0.2 * * Copyright 2015 Nick Downie * Released under the MIT license * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md */ (function() { "use strict"; //Declare root variable - window in the browser, global on the server var root = this , previous = root.Chart; //Occupy the global variable of Chart, and create a simple base class var Chart = function(context) { var chart = this; this.canvas = context.canvas; this.ctx = context; //Variables global to the chart var computeDimension = function(element, dimension) { if (element['offset' + dimension]) { return element['offset' + dimension]; } else { return document.defaultView.getComputedStyle(element).getPropertyValue(dimension); } }; var width = this.width = computeDimension(context.canvas, 'Width') || context.canvas.width; var height = this.height = computeDimension(context.canvas, 'Height') || context.canvas.height; width = this.width = context.canvas.width; height = this.height = context.canvas.height; this.aspectRatio = this.width / this.height; //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. helpers.retinaScale(this); return this; }; //Globally expose the defaults to allow for user updating/changing Chart.defaults = { global: { // Boolean - Whether to animate the chart animation: true, // Number - Number of animation steps animationSteps: 60, // String - Animation easing effect animationEasing: "easeOutQuart", // Boolean - If we should show the scale at all showScale: true, // Boolean - If we want to override with a hard coded scale scaleOverride: false, // ** Required if scaleOverride is true ** // Number - The number of steps in a hard coded scale scaleSteps: null, // Number - The value jump in the hard coded scale scaleStepWidth: null, // Number - The scale starting value scaleStartValue: null, // String - Colour of the scale line scaleLineColor: "rgba(0,0,0,.1)", // Number - Pixel width of the scale line scaleLineWidth: 1, // Boolean - Whether to show labels on the scale scaleShowLabels: true, // Interpolated JS string - can access value scaleLabel: "<%=value%>", // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there scaleIntegersOnly: true, // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value scaleBeginAtZero: false, // String - Scale label font declaration for the scale label scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", // Number - Scale label font size in pixels scaleFontSize: 12, // String - Scale label font weight style scaleFontStyle: "normal", // String - Scale label font colour scaleFontColor: "#666", // Boolean - whether or not the chart should be responsive and resize when the browser does. responsive: false, // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container maintainAspectRatio: true, // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove showTooltips: true, // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function customTooltips: false, // Array - Array of string names to attach tooltip events tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"], // String - Tooltip background colour tooltipFillColor: "rgba(0,0,0,0.8)", // String - Tooltip label font declaration for the scale label tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", // Number - Tooltip label font size in pixels tooltipFontSize: 14, // String - Tooltip font weight style tooltipFontStyle: "normal", // String - Tooltip label font colour tooltipFontColor: "#fff", // String - Tooltip title font declaration for the scale label tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", // Number - Tooltip title font size in pixels tooltipTitleFontSize: 14, // String - Tooltip title font weight style tooltipTitleFontStyle: "bold", // String - Tooltip title font colour tooltipTitleFontColor: "#fff", // String - Tooltip title template tooltipTitleTemplate: "<%= label%>", // Number - pixel width of padding around tooltip text tooltipYPadding: 6, // Number - pixel width of padding around tooltip text tooltipXPadding: 6, // Number - Size of the caret on the tooltip tooltipCaretSize: 8, // Number - Pixel radius of the tooltip border tooltipCornerRadius: 6, // Number - Pixel offset from point x to tooltip edge tooltipXOffset: 10, // String - Template string for single tooltips tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>", // String - Template string for single tooltips multiTooltipTemplate: "<%= value %>", // String - Colour behind the legend colour block multiTooltipKeyBackground: '#fff', // Array - A list of colors to use as the defaults segmentColorDefault: ["#A6CEE3", "#1F78B4", "#B2DF8A", "#33A02C", "#FB9A99", "#E31A1C", "#FDBF6F", "#FF7F00", "#CAB2D6", "#6A3D9A", "#B4B482", "#B15928"], // Array - A list of highlight colors to use as the defaults segmentHighlightColorDefaults: ["#CEF6FF", "#47A0DC", "#DAFFB2", "#5BC854", "#FFC2C1", "#FF4244", "#FFE797", "#FFA728", "#F2DAFE", "#9265C2", "#DCDCAA", "#D98150"], // Function - Will fire on animation progression. onAnimationProgress: function() {}, // Function - Will fire on animation completion. onAnimationComplete: function() {} } }; //Create a dictionary of chart types, to allow for extension of existing types Chart.types = {}; //Global Chart helpers object for utility methods and classes var helpers = Chart.helpers = {}; //-- Basic js utility methods var each = helpers.each = function(loopable, callback, self) { var additionalArgs = Array.prototype.slice.call(arguments, 3); // Check to see if null or undefined firstly. if (loopable) { if (loopable.length === +loopable.length) { var i; for (i = 0; i < loopable.length; i++) { callback.apply(self, [loopable[i], i].concat(additionalArgs)); } } else { for (var item in loopable) { callback.apply(self, [loopable[item], item].concat(additionalArgs)); } } } } , clone = helpers.clone = function(obj) { var objClone = {}; each(obj, function(value, key) { if (obj.hasOwnProperty(key)) { objClone[key] = value; } }); return objClone; } , extend = helpers.extend = function(base) { each(Array.prototype.slice.call(arguments, 1), function(extensionObject) { each(extensionObject, function(value, key) { if (extensionObject.hasOwnProperty(key)) { base[key] = value; } }); }); return base; } , merge = helpers.merge = function(base, master) { //Merge properties in left object over to a shallow clone of object right. var args = Array.prototype.slice.call(arguments, 0); args.unshift({}); return extend.apply(null, args); } , indexOf = helpers.indexOf = function(arrayToSearch, item) { if (Array.prototype.indexOf) { return arrayToSearch.indexOf(item); } else { for (var i = 0; i < arrayToSearch.length; i++) { if (arrayToSearch[i] === item) return i; } return -1; } } , where = helpers.where = function(collection, filterCallback) { var filtered = []; helpers.each(collection, function(item) { if (filterCallback(item)) { filtered.push(item); } }); return filtered; } , findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { // Default to start of the array if (!startIndex) { startIndex = -1; } for (var i = startIndex + 1; i < arrayToSearch.length; i++) { var currentItem = arrayToSearch[i]; if (filterCallback(currentItem)) { return currentItem; } } } , findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { // Default to end of the array if (!startIndex) { startIndex = arrayToSearch.length; } for (var i = startIndex - 1; i >= 0; i--) { var currentItem = arrayToSearch[i]; if (filterCallback(currentItem)) { return currentItem; } } } , inherits = helpers.inherits = function(extensions) { //Basic javascript inheritance based on the model created in Backbone.js var parent = this; var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() { return parent.apply(this, arguments); } ; var Surrogate = function() { this.constructor = ChartElement; }; Surrogate.prototype = parent.prototype; ChartElement.prototype = new Surrogate(); ChartElement.extend = inherits; if (extensions) extend(ChartElement.prototype, extensions); ChartElement.__super__ = parent.prototype; return ChartElement; } , noop = helpers.noop = function() {} , uid = helpers.uid = (function() { var id = 0; return function() { return "chart-" + id++; } ; })() , warn = helpers.warn = function(str) { //Method for warning of errors if (window.console && typeof window.console.warn === "function") console.warn(str); } , amd = helpers.amd = (typeof define === 'function' && define.amd) , //-- Math methods isNumber = helpers.isNumber = function(n) { return !isNaN(parseFloat(n)) && isFinite(n); } , max = helpers.max = function(array) { return Math.max.apply(Math, array); } , min = helpers.min = function(array) { return Math.min.apply(Math, array); } , cap = helpers.cap = function(valueToCap, maxValue, minValue) { if (isNumber(maxValue)) { if (valueToCap > maxValue) { return maxValue; } } else if (isNumber(minValue)) { if (valueToCap < minValue) { return minValue; } } return valueToCap; } , getDecimalPlaces = helpers.getDecimalPlaces = function(num) { if (num % 1 !== 0 && isNumber(num)) { var s = num.toString(); if (s.indexOf("e-") < 0) { // no exponent, e.g. 0.01 return s.split(".")[1].length; } else if (s.indexOf(".") < 0) { // no decimal point, e.g. 1e-9 return parseInt(s.split("e-")[1]); } else { // exponent and decimal point, e.g. 1.23e-9 var parts = s.split(".")[1].split("e-"); return parts[0].length + parseInt(parts[1]); } } else { return 0; } } , toRadians = helpers.radians = function(degrees) { return degrees * (Math.PI / 180); } , // Gets the angle from vertical upright to the point about a centre. getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) { var distanceFromXCenter = anglePoint.x - centrePoint.x , distanceFromYCenter = anglePoint.y - centrePoint.y , radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter); //If the segment is in the top left quadrant, we need to add another rotation to the angle if (distanceFromXCenter < 0 && distanceFromYCenter < 0) { angle += Math.PI * 2; } return { angle: angle, distance: radialDistanceFromCenter }; } , aliasPixel = helpers.aliasPixel = function(pixelWidth) { return (pixelWidth % 2 === 0) ? 0 : 0.5; } , splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t) { //Props to Rob Spencer at scaled innovation for his post on splining between points //http://scaledinnovation.com/analytics/splines/aboutSplines.html var d01 = Math.sqrt(Math.pow(MiddlePoint.x - FirstPoint.x, 2) + Math.pow(MiddlePoint.y - FirstPoint.y, 2)) , d12 = Math.sqrt(Math.pow(AfterPoint.x - MiddlePoint.x, 2) + Math.pow(AfterPoint.y - MiddlePoint.y, 2)) , fa = t * d01 / (d01 + d12) , // scaling factor for triangle Ta fb = t * d12 / (d01 + d12); return { inner: { x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x), y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y) }, outer: { x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x), y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y) } }; } , calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) { return Math.floor(Math.log(val) / Math.LN10); } , calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly) { //Set a minimum step of two - a point at the top of the graph, and a point at the base var minSteps = 2 , maxSteps = Math.floor(drawingSize / (textSize * 1.5)) , skipFitting = (minSteps >= maxSteps); // Filter out null values since these would min() to zero var values = []; each(valuesArray, function(v) { v == null || values.push(v); }); var minValue = min(values) , maxValue = max(values); // We need some degree of separation here to calculate the scales if all the values are the same // Adding/minusing 0.5 will give us a range of 1. if (maxValue === minValue) { maxValue += 0.5; // So we don't end up with a graph with a negative start value if we've said always start from zero if (minValue >= 0.5 && !startFromZero) { minValue -= 0.5; } else { // Make up a whole number above the values maxValue += 0.5; } } var valueRange = Math.abs(maxValue - minValue) , rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange) , graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude) , graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude) , graphRange = graphMax - graphMin , stepValue = Math.pow(10, rangeOrderOfMagnitude) , numberOfSteps = Math.round(graphRange / stepValue); //If we have more space on the graph we'll use it to give more definition to the data while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) { if (numberOfSteps > maxSteps) { stepValue *= 2; numberOfSteps = Math.round(graphRange / stepValue); // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps. if (numberOfSteps % 1 !== 0) { skipFitting = true; } }//We can fit in double the amount of scale points on the scale else { //If user has declared ints only, and the step value isn't a decimal if (integersOnly && rangeOrderOfMagnitude >= 0) { //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float if (stepValue / 2 % 1 === 0) { stepValue /= 2; numberOfSteps = Math.round(graphRange / stepValue); }//If it would make it a float break out of the loop else { break; } }//If the scale doesn't have to be an int, make the scale more granular anyway. else { stepValue /= 2; numberOfSteps = Math.round(graphRange / stepValue); } } } if (skipFitting) { numberOfSteps = minSteps; stepValue = graphRange / numberOfSteps; } return { steps: numberOfSteps, stepValue: stepValue, min: graphMin, max: graphMin + (numberOfSteps * stepValue) }; } , /* jshint ignore:start */ // Blows up jshint errors based on the new Function constructor //Templating methods //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ template = helpers.template = function(templateString, valuesObject) { // If templateString is function rather than string-template - call the function for valuesObject if (templateString instanceof Function) { return templateString(valuesObject); } var cache = {}; function tmpl(str, data) { // Figure out if we're getting a template, or if we need to // load the template - and be sure to cache the result. var fn = !/\W/.test(str) ? cache[str] = cache[str] : // Generate a reusable function that will serve as a template // generator (and which will be cached). new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};" + // Introduce the data as local variables using with(){} "with(obj){p.push('" + // Convert the template into pure JavaScript str.replace(/[\r\t\n]/g, " ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g, "$1\r").replace(/\t=(.*?)%>/g, "',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'") + "');}return p.join('');"); // Provide some basic currying to the user return data ? fn(data) : fn; } return tmpl(templateString, valuesObject); } , /* jshint ignore:end */ generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue) { var labelsArray = new Array(numberOfSteps); if (templateString) { each(labelsArray, function(val, index) { labelsArray[index] = template(templateString, { value: (graphMin + (stepValue * (index + 1))) }); }); } return labelsArray; } , //--Animation methods //Easing functions adapted from Robert Penner's easing equations //http://www.robertpenner.com/easing/ easingEffects = helpers.easingEffects = { linear: function(t) { return t; }, easeInQuad: function(t) { return t * t; }, easeOutQuad: function(t) { return -1 * t * (t - 2); }, easeInOutQuad: function(t) { if ((t /= 1 / 2) < 1) { return 1 / 2 * t * t; } return -1 / 2 * ((--t) * (t - 2) - 1); }, easeInCubic: function(t) { return t * t * t; }, easeOutCubic: function(t) { return 1 * ((t = t / 1 - 1) * t * t + 1); }, easeInOutCubic: function(t) { if ((t /= 1 / 2) < 1) { return 1 / 2 * t * t * t; } return 1 / 2 * ((t -= 2) * t * t + 2); }, easeInQuart: function(t) { return t * t * t * t; }, easeOutQuart: function(t) { return -1 * ((t = t / 1 - 1) * t * t * t - 1); }, easeInOutQuart: function(t) { if ((t /= 1 / 2) < 1) { return 1 / 2 * t * t * t * t; } return -1 / 2 * ((t -= 2) * t * t * t - 2); }, easeInQuint: function(t) { return 1 * (t /= 1) * t * t * t * t; }, easeOutQuint: function(t) { return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); }, easeInOutQuint: function(t) { if ((t /= 1 / 2) < 1) { return 1 / 2 * t * t * t * t * t; } return 1 / 2 * ((t -= 2) * t * t * t * t + 2); }, easeInSine: function(t) { return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; }, easeOutSine: function(t) { return 1 * Math.sin(t / 1 * (Math.PI / 2)); }, easeInOutSine: function(t) { return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); }, easeInExpo: function(t) { return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); }, easeOutExpo: function(t) { return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); }, easeInOutExpo: function(t) { if (t === 0) { return 0; } if (t === 1) { return 1; } if ((t /= 1 / 2) < 1) { return 1 / 2 * Math.pow(2, 10 * (t - 1)); } return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); }, easeInCirc: function(t) { if (t >= 1) { return t; } return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); }, easeOutCirc: function(t) { return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); }, easeInOutCirc: function(t) { if ((t /= 1 / 2) < 1) { return -1 / 2 * (Math.sqrt(1 - t * t) - 1); } return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); }, easeInElastic: function(t) { var s = 1.70158; var p = 0; var a = 1; if (t === 0) { return 0; } if ((t /= 1) == 1) { return 1; } if (!p) { p = 1 * 0.3; } if (a < Math.abs(1)) { a = 1; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(1 / a); } return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); }, easeOutElastic: function(t) { var s = 1.70158; var p = 0; var a = 1; if (t === 0) { return 0; } if ((t /= 1) == 1) { return 1; } if (!p) { p = 1 * 0.3; } if (a < Math.abs(1)) { a = 1; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(1 / a); } return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; }, easeInOutElastic: function(t) { var s = 1.70158; var p = 0; var a = 1; if (t === 0) { return 0; } if ((t /= 1 / 2) == 2) { return 1; } if (!p) { p = 1 * (0.3 * 1.5); } if (a < Math.abs(1)) { a = 1; s = p / 4; } else { s = p / (2 * Math.PI) * Math.asin(1 / a); } if (t < 1) { return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); } return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; }, easeInBack: function(t) { var s = 1.70158; return 1 * (t /= 1) * t * ((s + 1) * t - s); }, easeOutBack: function(t) { var s = 1.70158; return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); }, easeInOutBack: function(t) { var s = 1.70158; if ((t /= 1 / 2) < 1) { return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); } return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); }, easeInBounce: function(t) { return 1 - easingEffects.easeOutBounce(1 - t); }, easeOutBounce: function(t) { if ((t /= 1) < (1 / 2.75)) { return 1 * (7.5625 * t * t); } else if (t < (2 / 2.75)) { return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); } else if (t < (2.5 / 2.75)) { return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); } else { return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); } }, easeInOutBounce: function(t) { if (t < 1 / 2) { return easingEffects.easeInBounce(t * 2) * 0.5; } return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; } } , //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ requestAnimFrame = helpers.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return window.setTimeout(callback, 1000 / 60); } ; })() , cancelAnimFrame = helpers.cancelAnimFrame = (function() { return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || function(callback) { return window.clearTimeout(callback, 1000 / 60); } ; })() , animationLoop = helpers.animationLoop = function(callback, totalSteps, easingString, onProgress, onComplete, chartInstance) { var currentStep = 0 , easingFunction = easingEffects[easingString] || easingEffects.linear; var animationFrame = function() { currentStep++; var stepDecimal = currentStep / totalSteps; var easeDecimal = easingFunction(stepDecimal); callback.call(chartInstance, easeDecimal, stepDecimal, currentStep); onProgress.call(chartInstance, easeDecimal, stepDecimal); if (currentStep < totalSteps) { chartInstance.animationFrame = requestAnimFrame(animationFrame); } else { onComplete.apply(chartInstance); } }; requestAnimFrame(animationFrame); } , //-- DOM methods getRelativePosition = helpers.getRelativePosition = function(evt) { var mouseX, mouseY; var e = evt.originalEvent || evt , canvas = evt.currentTarget || evt.srcElement , boundingRect = canvas.getBoundingClientRect(); if (e.touches) { mouseX = e.touches[0].clientX - boundingRect.left; mouseY = e.touches[0].clientY - boundingRect.top; } else { mouseX = e.clientX - boundingRect.left; mouseY = e.clientY - boundingRect.top; } return { x: mouseX, y: mouseY }; } , addEvent = helpers.addEvent = function(node, eventType, method) { if (node.addEventListener) { node.addEventListener(eventType, method); } else if (node.attachEvent) { node.attachEvent("on" + eventType, method); } else { node["on" + eventType] = method; } } , removeEvent = helpers.removeEvent = function(node, eventType, handler) { if (node.removeEventListener) { node.removeEventListener(eventType, handler, false); } else if (node.detachEvent) { node.detachEvent("on" + eventType, handler); } else { node["on" + eventType] = noop; } } , bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { // Create the events object if it's not already present if (!chartInstance.events) chartInstance.events = {}; each(arrayOfEvents, function(eventName) { chartInstance.events[eventName] = function() { handler.apply(chartInstance, arguments); } ; addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]); }); } , unbindEvents = helpers.unbindEvents = function(chartInstance, arrayOfEvents) { each(arrayOfEvents, function(handler, eventName) { removeEvent(chartInstance.chart.canvas, eventName, handler); }); } , getMaximumWidth = helpers.getMaximumWidth = function(domNode) { var container = domNode.parentNode , padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right')); // TODO = check cross browser stuff with this. return container.clientWidth - padding; } , getMaximumHeight = helpers.getMaximumHeight = function(domNode) { var container = domNode.parentNode , padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top')); // TODO = check cross browser stuff with this. return container.clientHeight - padding; } , getStyle = helpers.getStyle = function(el, property) { return el.currentStyle ? el.currentStyle[property] : document.defaultView.getComputedStyle(el, null).getPropertyValue(property); } , getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth , // legacy support retinaScale = helpers.retinaScale = function(chart) { var ctx = chart.ctx , width = chart.canvas.width , height = chart.canvas.height; if (window.devicePixelRatio) { ctx.canvas.style.width = width + "px"; ctx.canvas.style.height = height + "px"; ctx.canvas.height = height * window.devicePixelRatio; ctx.canvas.width = width * window.devicePixelRatio; ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } } , //-- Canvas methods clear = helpers.clear = function(chart) { chart.ctx.clearRect(0, 0, chart.width, chart.height); } , fontString = helpers.fontString = function(pixelSize, fontStyle, fontFamily) { return fontStyle + " " + pixelSize + "px " + fontFamily; } , longestText = helpers.longestText = function(ctx, font, arrayOfStrings) { ctx.font = font; var longest = 0; each(arrayOfStrings, function(string) { var textWidth = ctx.measureText(string).width; longest = (textWidth > longest) ? textWidth : longest; }); return longest; } , drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); } ; //Store a reference to each instance - allowing us to globally resize chart instances on window resize. //Destroy method on the chart will remove the instance of the chart from this reference. Chart.instances = {}; Chart.Type = function(data, options, chart) { this.options = options; this.chart = chart; this.id = uid(); //Add the chart instance to the global namespace Chart.instances[this.id] = this; // Initialize is always called when a chart type is created // By default it is a no op, but it should be extended if (options.responsive) { this.resize(); } this.initialize.call(this, data); } ; //Core methods that'll be a part of every chart type extend(Chart.Type.prototype, { initialize: function() { return this; }, clear: function() { clear(this.chart); return this; }, stop: function() { // Stops any current animation loop occuring Chart.animationService.cancelAnimation(this); return this; }, resize: function(callback) { this.stop(); var canvas = this.chart.canvas , newWidth = getMaximumWidth(this.chart.canvas) , newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas); canvas.width = this.chart.width = newWidth; canvas.height = this.chart.height = newHeight; retinaScale(this.chart); if (typeof callback === "function") { callback.apply(this, Array.prototype.slice.call(arguments, 1)); } return this; }, reflow: noop, render: function(reflow) { if (reflow) { this.reflow(); } if (this.options.animation && !reflow) { var animation = new Chart.Animation(); animation.numSteps = this.options.animationSteps; animation.easing = this.options.animationEasing; // render function animation.render = function(chartInstance, animationObject) { var easingFunction = helpers.easingEffects[animationObject.easing]; var stepDecimal = animationObject.currentStep / animationObject.numSteps; var easeDecimal = easingFunction(stepDecimal); chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); } ; // user events animation.onAnimationProgress = this.options.onAnimationProgress; animation.onAnimationComplete = this.options.onAnimationComplete; Chart.animationService.addAnimation(this, animation); } else { this.draw(); this.options.onAnimationComplete.call(this); } return this; }, generateLegend: function() { return template(this.options.legendTemplate, this); }, destroy: function() { this.clear(); unbindEvents(this, this.events); var canvas = this.chart.canvas; // Reset canvas height/width attributes starts a fresh with the canvas context canvas.width = this.chart.width; canvas.height = this.chart.height; // < IE9 doesn't support removeProperty if (canvas.style.removeProperty) { canvas.style.removeProperty('width'); canvas.style.removeProperty('height'); } else { canvas.style.removeAttribute('width'); canvas.style.removeAttribute('height'); } delete Chart.instances[this.id]; }, showTooltip: function(ChartElements, forceRedraw) { // Only redraw the chart if we've actually changed what we're hovering on. if (typeof this.activeElements === 'undefined') this.activeElements = []; var isChanged = (function(Elements) { var changed = false; if (Elements.length !== this.activeElements.length) { changed = true; return changed; } each(Elements, function(element, index) { if (element !== this.activeElements[index]) { changed = true; } }, this); return changed; } ).call(this, ChartElements); if (!isChanged && !forceRedraw) { return; } else { this.activeElements = ChartElements; } this.draw(); if (this.options.customTooltips) { this.options.customTooltips(false); } if (ChartElements.length > 0) { // If we have multiple datasets, show a MultiTooltip for all of the data points at that index if (this.datasets && this.datasets.length > 1) { var dataArray, dataIndex; for (var i = this.datasets.length - 1; i >= 0; i--) { dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments; dataIndex = indexOf(dataArray, ChartElements[0]); if (dataIndex !== -1) { break; } } var tooltipLabels = [] , tooltipColors = [] , medianPosition = (function(index) { // Get all the points at that particular index var Elements = [], dataCollection, xPositions = [], yPositions = [], xMax, yMax, xMin, yMin; helpers.each(this.datasets, function(dataset) { dataCollection = dataset.points || dataset.bars || dataset.segments; if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) { Elements.push(dataCollection[dataIndex]); } }); helpers.each(Elements, function(element) { xPositions.push(element.x); yPositions.push(element.y); //Include any colour information about the element tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element)); tooltipColors.push({ fill: element._saved.fillColor || element.fillColor, stroke: element._saved.strokeColor || element.strokeColor }); }, this); yMin = min(yPositions); yMax = max(yPositions); xMin = min(xPositions); xMax = max(xPositions); return { x: (xMin > this.chart.width / 2) ? xMin : xMax, y: (yMin + yMax) / 2 }; } ).call(this, dataIndex); new Chart.MultiTooltip({ x: medianPosition.x, y: medianPosition.y, xPadding: this.options.tooltipXPadding, yPadding: this.options.tooltipYPadding, xOffset: this.options.tooltipXOffset, fillColor: this.options.tooltipFillColor, textColor: this.options.tooltipFontColor, fontFamily: this.options.tooltipFontFamily, fontStyle: this.options.tooltipFontStyle, fontSize: this.options.tooltipFontSize, titleTextColor: this.options.tooltipTitleFontColor, titleFontFamily: this.options.tooltipTitleFontFamily, titleFontStyle: this.options.tooltipTitleFontStyle, titleFontSize: this.options.tooltipTitleFontSize, cornerRadius: this.options.tooltipCornerRadius, labels: tooltipLabels, legendColors: tooltipColors, legendColorBackground: this.options.multiTooltipKeyBackground, title: template(this.options.tooltipTitleTemplate, ChartElements[0]), chart: this.chart, ctx: this.chart.ctx, custom: this.options.customTooltips }).draw(); } else { each(ChartElements, function(Element) { var tooltipPosition = Element.tooltipPosition(); new Chart.Tooltip({ x: Math.round(tooltipPosition.x), y: Math.round(tooltipPosition.y), xPadding: this.options.tooltipXPadding, yPadding: this.options.tooltipYPadding, fillColor: this.options.tooltipFillColor, textColor: this.options.tooltipFontColor, fontFamily: this.options.tooltipFontFamily, fontStyle: this.options.tooltipFontStyle, fontSize: this.options.tooltipFontSize, caretHeight: this.options.tooltipCaretSize, cornerRadius: this.options.tooltipCornerRadius, text: template(this.options.tooltipTemplate, Element), chart: this.chart, custom: this.options.customTooltips }).draw(); }, this); } } return this; }, toBase64Image: function() { return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); } }); Chart.Type.extend = function(extensions) { var parent = this; var ChartType = function() { return parent.apply(this, arguments); }; //Copy the prototype object of the this class ChartType.prototype = clone(parent.prototype); //Now overwrite some of the properties in the base class with the new extensions extend(ChartType.prototype, extensions); ChartType.extend = Chart.Type.extend; if (extensions.name || parent.prototype.name) { var chartName = extensions.name || parent.prototype.name; //Assign any potential default values of the new chart type //If none are defined, we'll use a clone of the chart type this is being extended from. //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart //doesn't define some defaults of their own. var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; Chart.defaults[chartName] = extend(baseDefaults, extensions.defaults); Chart.types[chartName] = ChartType; //Register this new chart type in the Chart prototype Chart.prototype[chartName] = function(data, options) { var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {}); return new ChartType(data,config,this); } ; } else { warn("Name not provided for this chart, so it hasn't been registered"); } return parent; } ; Chart.Element = function(configuration) { extend(this, configuration); this.initialize.apply(this, arguments); this.save(); } ; extend(Chart.Element.prototype, { initialize: function() {}, restore: function(props) { if (!props) { extend(this, this._saved); } else { each(props, function(key) { this[key] = this._saved[key]; }, this); } return this; }, save: function() { this._saved = clone(this); delete this._saved._saved; return this; }, update: function(newProps) { each(newProps, function(value, key) { this._saved[key] = this[key]; this[key] = value; }, this); return this; }, transition: function(props, ease) { each(props, function(value, key) { this[key] = ((value - this._saved[key]) * ease) + this._saved[key]; }, this); return this; }, tooltipPosition: function() { return { x: this.x, y: this.y }; }, hasValue: function() { return isNumber(this.value); } }); Chart.Element.extend = inherits; Chart.Point = Chart.Element.extend({ display: true, inRange: function(chartX, chartY) { var hitDetectionRange = this.hitDetectionRadius + this.radius; return ( (Math.pow(chartX - this.x, 2) + Math.pow(chartY - this.y, 2)) < Math.pow(hitDetectionRange, 2)) ; }, draw: function() { if (this.display) { var ctx = this.ctx; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.closePath(); ctx.strokeStyle = this.strokeColor; ctx.lineWidth = this.strokeWidth; ctx.fillStyle = this.fillColor; ctx.fill(); ctx.stroke(); } //Quick debug for bezier curve splining //Highlights control points and the line between them. //Handy for dev - stripped in the min version. // ctx.save(); // ctx.fillStyle = "black"; // ctx.strokeStyle = "black" // ctx.beginPath(); // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2); // ctx.fill(); // ctx.beginPath(); // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2); // ctx.fill(); // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y); // ctx.lineTo(this.x, this.y); // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y); // ctx.stroke(); // ctx.restore(); } }); Chart.Arc = Chart.Element.extend({ inRange: function(chartX, chartY) { var pointRelativePosition = helpers.getAngleFromPoint(this, { x: chartX, y: chartY }); // Normalize all angles to 0 - 2*PI (0 - 360°) var pointRelativeAngle = pointRelativePosition.angle % (Math.PI * 2) , startAngle = (Math.PI * 2 + this.startAngle) % (Math.PI * 2) , endAngle = (Math.PI * 2 + this.endAngle) % (Math.PI * 2) || 360; // Calculate wether the pointRelativeAngle is between the start and the end angle var betweenAngles = (endAngle < startAngle) ? pointRelativeAngle <= endAngle || pointRelativeAngle >= startAngle : pointRelativeAngle >= startAngle && pointRelativeAngle <= endAngle; //Check if within the range of the open/close angle var withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius); return ( betweenAngles && withinRadius) ; //Ensure within the outside of the arc centre, but inside arc outer }, tooltipPosition: function() { var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2) , rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius; return { x: this.x + (Math.cos(centreAngle) * rangeFromCentre), y: this.y + (Math.sin(centreAngle) * rangeFromCentre) }; }, draw: function(animationPercent) { var easingDecimal = animationPercent || 1; var ctx = this.ctx; ctx.beginPath(); ctx.arc(this.x, this.y, this.outerRadius < 0 ? 0 : this.outerRadius, this.startAngle, this.endAngle); ctx.arc(this.x, this.y, this.innerRadius < 0 ? 0 : this.innerRadius, this.endAngle, this.startAngle, true); ctx.closePath(); ctx.strokeStyle = this.strokeColor; ctx.lineWidth = this.strokeWidth; ctx.fillStyle = this.fillColor; ctx.fill(); ctx.lineJoin = 'bevel'; if (this.showStroke) { ctx.stroke(); } } }); Chart.Rectangle = Chart.Element.extend({ draw: function() { var ctx = this.ctx , halfWidth = this.width / 2 , leftX = this.x - halfWidth , rightX = this.x + halfWidth , top = this.base - (this.base - this.y) , halfStroke = this.strokeWidth / 2; // Canvas doesn't allow us to stroke inside the width so we can // adjust the sizes to fit if we're setting a stroke on the line if (this.showStroke) { leftX += halfStroke; rightX -= halfStroke; top += halfStroke; } ctx.beginPath(); ctx.fillStyle = this.fillColor; ctx.strokeStyle = this.strokeColor; ctx.lineWidth = this.strokeWidth; // It'd be nice to keep this class totally generic to any rectangle // and simply specify which border to miss out. ctx.moveTo(leftX, this.base); ctx.lineTo(leftX, top); ctx.lineTo(rightX, top); ctx.lineTo(rightX, this.base); ctx.fill(); if (this.showStroke) { ctx.stroke(); } }, height: function() { return this.base - this.y; }, inRange: function(chartX, chartY) { return (chartX >= this.x - this.width / 2 && chartX <= this.x + this.width / 2) && (chartY >= this.y && chartY <= this.base); } }); Chart.Animation = Chart.Element.extend({ currentStep: null, // the current animation step numSteps: 60, // default number of steps easing: "", // the easing to use for this animation render: null, // render function used by the animation service onAnimationProgress: null, // user specified callback to fire on each step of the animation onAnimationComplete: null, // user specified callback to fire when the animation finishes }); Chart.Tooltip = Chart.Element.extend({ draw: function() { var ctx = this.chart.ctx; ctx.font = fontString(this.fontSize, this.fontStyle, this.fontFamily); this.xAlign = "center"; this.yAlign = "above"; //Distance between the actual element.y position and the start of the tooltip caret var caretPadding = this.caretPadding = 2; var tooltipWidth = ctx.measureText(this.text).width + 2 * this.xPadding , tooltipRectHeight = this.fontSize + 2 * this.yPadding , tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding; if (this.x + tooltipWidth / 2 > this.chart.width) { this.xAlign = "left"; } else if (this.x - tooltipWidth / 2 < 0) { this.xAlign = "right"; } if (this.y - tooltipHeight < 0) { this.yAlign = "below"; } var tooltipX = this.x - tooltipWidth / 2 , tooltipY = this.y - tooltipHeight; ctx.fillStyle = this.fillColor; // Custom Tooltips if (this.custom) { this.custom(this); } else { switch (this.yAlign) { case "above": //Draw a caret above the x/y ctx.beginPath(); ctx.moveTo(this.x, this.y - caretPadding); ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight)); ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight)); ctx.closePath(); ctx.fill(); break; case "below": tooltipY = this.y + caretPadding + this.caretHeight; //Draw a caret below the x/y ctx.beginPath(); ctx.moveTo(this.x, this.y + caretPadding); ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight); ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight); ctx.closePath(); ctx.fill(); break; } switch (this.xAlign) { case "left": tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight); break; case "right": tooltipX = this.x - (this.cornerRadius + this.caretHeight); break; } drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, this.cornerRadius); ctx.fill(); ctx.fillStyle = this.textColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(this.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2); } } }); Chart.MultiTooltip = Chart.Element.extend({ initialize: function() { this.font = fontString(this.fontSize, this.fontStyle, this.fontFamily); this.titleFont = fontString(this.titleFontSize, this.titleFontStyle, this.titleFontFamily); this.titleHeight = this.title ? this.titleFontSize * 1.5 : 0; this.height = (this.labels.length * this.fontSize) + ((this.labels.length - 1) * (this.fontSize / 2)) + (this.yPadding * 2) + this.titleHeight; this.ctx.font = this.titleFont; var titleWidth = this.ctx.measureText(this.title).width , //Label has a legend square as well so account for this. labelWidth = longestText(this.ctx, this.font, this.labels) + this.fontSize + 3 , longestTextWidth = max([labelWidth, titleWidth]); this.width = longestTextWidth + (this.xPadding * 2); var halfHeight = this.height / 2; //Check to ensure the height will fit on the canvas if (this.y - halfHeight < 0) { this.y = halfHeight; } else if (this.y + halfHeight > this.chart.height) { this.y = this.chart.height - halfHeight; } //Decide whether to align left or right based on position on canvas if (this.x > this.chart.width / 2) { this.x -= this.xOffset + this.width; } else { this.x += this.xOffset; } }, getLineHeight: function(index) { var baseLineHeight = this.y - (this.height / 2) + this.yPadding , afterTitleIndex = index - 1; //If the index is zero, we're getting the title if (index === 0) { return baseLineHeight + this.titleHeight / 3; } else { return baseLineHeight + ((this.fontSize * 1.5 * afterTitleIndex) + this.fontSize / 2) + this.titleHeight; } }, draw: function() { // Custom Tooltips if (this.custom) { this.custom(this); } else { drawRoundedRectangle(this.ctx, this.x, this.y - this.height / 2, this.width, this.height, this.cornerRadius); var ctx = this.ctx; ctx.fillStyle = this.fillColor; ctx.fill(); ctx.closePath(); ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.fillStyle = this.titleTextColor; ctx.font = this.titleFont; ctx.fillText(this.title, this.x + this.xPadding, this.getLineHeight(0)); ctx.font = this.font; helpers.each(this.labels, function(label, index) { ctx.fillStyle = this.textColor; ctx.fillText(label, this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1)); //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); //Instead we'll make a white filled block to put the legendColour palette over. ctx.fillStyle = this.legendColorBackground; ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize / 2, this.fontSize, this.fontSize); ctx.fillStyle = this.legendColors[index].fill; ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize / 2, this.fontSize, this.fontSize); }, this); } } }); Chart.Scale = Chart.Element.extend({ initialize: function() { this.fit(); }, buildYLabels: function() { this.yLabels = []; var stepDecimalPlaces = getDecimalPlaces(this.stepValue); for (var i = 0; i <= this.steps; i++) { this.yLabels.push(template(this.templateString, { value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces) })); } this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx, this.font, this.yLabels) + 10 : 0; }, addXLabel: function(label) { this.xLabels.push(label); this.valuesCount++; this.fit(); }, removeXLabel: function() { this.xLabels.shift(); this.valuesCount--; this.fit(); }, // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use fit: function() { // First we need the width of the yLabels, assuming the xLabels aren't rotated // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation this.startPoint = (this.display) ? this.fontSize : 0; this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels // Apply padding settings to the start and end point. this.startPoint += this.padding; this.endPoint -= this.padding; // Cache the starting endpoint, excluding the space for x labels var cachedEndPoint = this.endPoint; // Cache the starting height, so can determine if we need to recalculate the scale yAxis var cachedHeight = this.endPoint - this.startPoint, cachedYLabelWidth; // Build the current yLabels so we have an idea of what size they'll be to start /* * This sets what is returned from calculateScaleRange as static properties of this class: * this.steps; this.stepValue; this.min; this.max; * */ this.calculateYRange(cachedHeight); // With these properties set we can now build the array of yLabels // and also the width of the largest yLabel this.buildYLabels(); this.calculateXLabelRotation(); while ((cachedHeight > this.endPoint - this.startPoint) ) { cachedHeight = this.endPoint - this.startPoint; cachedYLabelWidth = this.yLabelWidth; this.calculateYRange(cachedHeight); this.buildYLabels(); // Only go through the xLabel loop again if the yLabel width has changed if (cachedYLabelWidth < this.yLabelWidth) { this.endPoint = cachedEndPoint; this.calculateXLabelRotation(); } } }, calculateXLabelRotation: function() { //Get the width of each grid by calculating the difference //between x offsets between 0 and 1. this.ctx.font = this.font; var firstWidth = this.ctx.measureText(this.xLabels[0]).width, lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width, firstRotated, lastRotated; this.xScalePaddingRight = lastWidth / 2 + 3; this.xScalePaddingLeft = (firstWidth / 2 > this.yLabelWidth) ? firstWidth / 2 : this.yLabelWidth; this.xLabelRotation = 0; if (this.display) { var originalLabelWidth = longestText(this.ctx, this.font, this.xLabels), cosRotation, firstRotatedWidth; this.xLabelWidth = originalLabelWidth; //Allow 3 pixels x2 padding either side for label readability var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6; //Max label rotate should be 90 - also act as a loop counter while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)) { cosRotation = Math.cos(toRadians(this.xLabelRotation)); firstRotated = cosRotation * firstWidth; lastRotated = cosRotation * lastWidth; // We're right aligning the text now. if (firstRotated + this.fontSize / 2 > this.yLabelWidth) { this.xScalePaddingLeft = firstRotated + this.fontSize / 2; } this.xScalePaddingRight = this.fontSize / 2; this.xLabelRotation++; this.xLabelWidth = cosRotation * originalLabelWidth; } if (this.xLabelRotation > 0) { this.endPoint -= Math.sin(toRadians(this.xLabelRotation)) * originalLabelWidth + 3; } } else { this.xLabelWidth = 0; this.xScalePaddingRight = this.padding; this.xScalePaddingLeft = this.padding; } }, // Needs to be overidden in each Chart type // Otherwise we need to pass all the data into the scale class calculateYRange: noop, drawingArea: function() { return this.startPoint - this.endPoint; }, calculateY: function(value) { var scalingFactor = this.drawingArea() / (this.min - this.max); return this.endPoint - (scalingFactor * (value - this.min)); }, calculateX: function(index) { var isRotated = (this.xLabelRotation > 0) , // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding, innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight) , valueWidth = innerWidth / Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1) , valueOffset = (valueWidth * index) + this.xScalePaddingLeft; if (this.offsetGridLines) { valueOffset += (valueWidth / 2); } return Math.round(valueOffset); }, update: function(newProps) { helpers.extend(this, newProps); this.fit(); }, draw: function() { var ctx = this.ctx , yLabelGap = (this.endPoint - this.startPoint) / this.steps , xStart = Math.round(this.xScalePaddingLeft); if (this.display) { ctx.fillStyle = this.textColor; ctx.font = this.font; each(this.yLabels, function(labelString, index) { var yLabelCenter = this.endPoint - (yLabelGap * index) , linePositionY = Math.round(yLabelCenter) , drawHorizontalLine = this.showHorizontalLines; ctx.textAlign = "right"; ctx.textBaseline = "middle"; if (this.showLabels) { ctx.fillText(labelString, xStart - 10, yLabelCenter); } // This is X axis, so draw it if (index === 0 && !drawHorizontalLine) { drawHorizontalLine = true; } if (drawHorizontalLine) { ctx.beginPath(); } if (index > 0) { // This is a grid line in the centre, so drop that ctx.lineWidth = this.gridLineWidth; ctx.strokeStyle = this.gridLineColor; } else { // This is the first line on the scale ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; } linePositionY += helpers.aliasPixel(ctx.lineWidth); if (drawHorizontalLine) { ctx.moveTo(xStart, linePositionY); ctx.lineTo(this.width, linePositionY); ctx.stroke(); ctx.closePath(); } ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; ctx.beginPath(); ctx.moveTo(xStart - 5, linePositionY); ctx.lineTo(xStart, linePositionY); ctx.stroke(); ctx.closePath(); }, this); each(this.xLabels, function(label, index) { var xPos = this.calculateX(index) + aliasPixel(this.lineWidth) , // Check to see if line/bar here and decide where to place the line linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth) , isRotated = (this.xLabelRotation > 0) , drawVerticalLine = this.showVerticalLines; // This is Y axis, so draw it if (index === 0 && !drawVerticalLine) { drawVerticalLine = true; } if (drawVerticalLine) { ctx.beginPath(); } if (index > 0) { // This is a grid line in the centre, so drop that ctx.lineWidth = this.gridLineWidth; ctx.strokeStyle = this.gridLineColor; } else { // This is the first line on the scale ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; } if (drawVerticalLine) { ctx.moveTo(linePos, this.endPoint); ctx.lineTo(linePos, this.startPoint - 3); ctx.stroke(); ctx.closePath(); } ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; // Small lines at the bottom of the base grid line ctx.beginPath(); ctx.moveTo(linePos, this.endPoint); ctx.lineTo(linePos, this.endPoint + 5); ctx.stroke(); ctx.closePath(); ctx.save(); ctx.translate(xPos, (isRotated) ? this.endPoint + 12 : this.endPoint + 8); ctx.rotate(toRadians(this.xLabelRotation) * -1); ctx.font = this.font; ctx.textAlign = (isRotated) ? "right" : "center"; ctx.textBaseline = (isRotated) ? "middle" : "top"; ctx.fillText(label, 0, 0); ctx.restore(); }, this); } } }); Chart.RadialScale = Chart.Element.extend({ initialize: function() { this.size = min([this.height, this.width]); this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); }, calculateCenterOffset: function(value) { // Take into account half font size + the yPadding of the top value var scalingFactor = this.drawingArea / (this.max - this.min); return (value - this.min) * scalingFactor; }, update: function() { if (!this.lineArc) { this.setScaleSize(); } else { this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); } this.buildYLabels(); }, buildYLabels: function() { this.yLabels = []; var stepDecimalPlaces = getDecimalPlaces(this.stepValue); for (var i = 0; i <= this.steps; i++) { this.yLabels.push(template(this.templateString, { value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces) })); } }, getCircumference: function() { return ( (Math.PI * 2) / this.valuesCount) ; }, setScaleSize: function() { /* * Right, this is really confusing and there is a lot of maths going on here * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 * * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif * * Solution: * * We assume the radius of the polygon is half the size of the canvas at first * at each index we check if the text overlaps. * * Where it does, we store that angle and that index. * * After finding the largest index and angle we calculate how much we need to remove * from the shape radius to move the point inwards by that x. * * We average the left and right distances to get the maximum shape radius that can fit in the box * along with labels. * * Once we have that, we can find the centre point for the chart, by taking the x text protrusion * on each side, removing that from the size, halving it and adding the left x protrusion width. * * This will mean we have a shape fitted to the canvas, as large as it can be with the labels * and position it in the most space efficient manner * * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif */ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points var largestPossibleRadius = min([(this.height / 2 - this.pointLabelFontSize - 5), this.width / 2]), pointPosition, i, textWidth, halfTextWidth, furthestRight = this.width, furthestRightIndex, furthestRightAngle, furthestLeft = 0, furthestLeftIndex, furthestLeftAngle, xProtrusionLeft, xProtrusionRight, radiusReductionRight, radiusReductionLeft, maxWidthRadius; this.ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily); for (i = 0; i < this.valuesCount; 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(template(this.templateString, { value: this.labels[i] })).width + 5; if (i === 0 || i === this.valuesCount / 2) { // If we're at index zero, or exactly the middle, 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 (i < this.valuesCount / 2) { // Less than half the values means we'll left align the text if (pointPosition.x + textWidth > furthestRight) { furthestRight = pointPosition.x + textWidth; furthestRightIndex = i; } } else if (i > this.valuesCount / 2) { // More than half the values means we'll right align the text if (pointPosition.x - textWidth < furthestLeft) { furthestLeft = pointPosition.x - textWidth; furthestLeftIndex = i; } } } xProtrusionLeft = furthestLeft; xProtrusionRight = Math.ceil(furthestRight - this.width); furthestRightAngle = this.getIndexAngle(furthestRightIndex); furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); // Ensure we actually need to reduce the size of the chart radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0; radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2; //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) this.setCenterPoint(radiusReductionLeft, radiusReductionRight); }, setCenterPoint: function(leftMovement, rightMovement) { var maxRight = this.width - rightMovement - this.drawingArea , maxLeft = leftMovement + this.drawingArea; this.xCenter = (maxLeft + maxRight) / 2; // Always vertically in the centre as the text height doesn't change this.yCenter = (this.height / 2); }, getIndexAngle: function(index) { var angleMultiplier = (Math.PI * 2) / this.valuesCount; // Start from the top instead of right, so remove a quarter of the circle return index * angleMultiplier - (Math.PI / 2); }, getPointPosition: function(index, distanceFromCenter) { var thisAngle = this.getIndexAngle(index); return { x: (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, y: (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter }; }, draw: function() { if (this.display) { var ctx = this.ctx; each(this.yLabels, function(label, index) { // Don't draw a centre value if (index > 0) { var yCenterOffset = index * (this.drawingArea / this.steps), yHeight = this.yCenter - yCenterOffset, pointPosition; // Draw circular lines around the scale if (this.lineWidth > 0) { ctx.strokeStyle = this.lineColor; ctx.lineWidth = this.lineWidth; if (this.lineArc) { ctx.beginPath(); ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2); ctx.closePath(); ctx.stroke(); } else { ctx.beginPath(); for (var i = 0; i < this.valuesCount; i++) { pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue))); if (i === 0) { ctx.moveTo(pointPosition.x, pointPosition.y); } else { ctx.lineTo(pointPosition.x, pointPosition.y); } } ctx.closePath(); ctx.stroke(); } } if (this.showLabels) { ctx.font = fontString(this.fontSize, this.fontStyle, this.fontFamily); if (this.showLabelBackdrop) { var labelWidth = ctx.measureText(label).width; ctx.fillStyle = this.backdropColor; ctx.fillRect(this.xCenter - labelWidth / 2 - this.backdropPaddingX, yHeight - this.fontSize / 2 - this.backdropPaddingY, labelWidth + this.backdropPaddingX * 2, this.fontSize + this.backdropPaddingY * 2); } ctx.textAlign = 'center'; ctx.textBaseline = "middle"; ctx.fillStyle = this.fontColor; ctx.fillText(label, this.xCenter, yHeight); } } }, this); if (!this.lineArc) { ctx.lineWidth = this.angleLineWidth; ctx.strokeStyle = this.angleLineColor; for (var i = this.valuesCount - 1; i >= 0; i--) { var centerOffset = null , outerPosition = null; if (this.angleLineWidth > 0) { centerOffset = this.calculateCenterOffset(this.max); outerPosition = this.getPointPosition(i, centerOffset); ctx.beginPath(); ctx.moveTo(this.xCenter, this.yCenter); ctx.lineTo(outerPosition.x, outerPosition.y); ctx.stroke(); ctx.closePath(); } if (this.backgroundColors && this.backgroundColors.length == this.valuesCount) { if (centerOffset == null) centerOffset = this.calculateCenterOffset(this.max); if (outerPosition == null) outerPosition = this.getPointPosition(i, centerOffset); var previousOuterPosition = this.getPointPosition(i === 0 ? this.valuesCount - 1 : i - 1, centerOffset); var nextOuterPosition = this.getPointPosition(i === this.valuesCount - 1 ? 0 : i + 1, centerOffset); var previousOuterHalfway = { x: (previousOuterPosition.x + outerPosition.x) / 2, y: (previousOuterPosition.y + outerPosition.y) / 2 }; var nextOuterHalfway = { x: (outerPosition.x + nextOuterPosition.x) / 2, y: (outerPosition.y + nextOuterPosition.y) / 2 }; ctx.beginPath(); ctx.moveTo(this.xCenter, this.yCenter); ctx.lineTo(previousOuterHalfway.x, previousOuterHalfway.y); ctx.lineTo(outerPosition.x, outerPosition.y); ctx.lineTo(nextOuterHalfway.x, nextOuterHalfway.y); ctx.fillStyle = this.backgroundColors[i]; ctx.fill(); ctx.closePath(); } // Extra 3px out for some label spacing var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5); ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily); ctx.fillStyle = this.pointLabelFontColor; var labelsCount = this.labels.length , halfLabelsCount = this.labels.length / 2 , quarterLabelsCount = halfLabelsCount / 2 , upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount) , exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); if (i === 0) { ctx.textAlign = 'center'; } else if (i === halfLabelsCount) { ctx.textAlign = 'center'; } else if (i < halfLabelsCount) { ctx.textAlign = 'left'; } else { ctx.textAlign = 'right'; } // Set the correct text baseline based on outer positioning if (exactQuarter) { ctx.textBaseline = 'middle'; } else if (upperHalf) { ctx.textBaseline = 'bottom'; } else { ctx.textBaseline = 'top'; } ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); } } } } }); Chart.animationService = { frameDuration: 17, animations: [], dropFrames: 0, addAnimation: function(chartInstance, animationObject) { for (var index = 0; index < this.animations.length; ++index) { if (this.animations[index].chartInstance === chartInstance) { // replacing an in progress animation this.animations[index].animationObject = animationObject; return; } } this.animations.push({ chartInstance: chartInstance, animationObject: animationObject }); // If there are no animations queued, manually kickstart a digest, for lack of a better word if (this.animations.length == 1) { helpers.requestAnimFrame.call(window, this.digestWrapper); } }, // Cancel the animation for a given chart instance cancelAnimation: function(chartInstance) { var index = helpers.findNextWhere(this.animations, function(animationWrapper) { return animationWrapper.chartInstance === chartInstance; }); if (index) { this.animations.splice(index, 1); } }, // calls startDigest with the proper context digestWrapper: function() { Chart.animationService.startDigest.call(Chart.animationService); }, startDigest: function() { var startTime = Date.now(); var framesToDrop = 0; if (this.dropFrames > 1) { framesToDrop = Math.floor(this.dropFrames); this.dropFrames -= framesToDrop; } for (var i = 0; i < this.animations.length; i++) { if (this.animations[i].animationObject.currentStep === null) { this.animations[i].animationObject.currentStep = 0; } this.animations[i].animationObject.currentStep += 1 + framesToDrop; if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; } this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); // Check if executed the last frame. if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) { // Call onAnimationComplete this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance); // Remove the animation. this.animations.splice(i, 1); // Keep the index in place to offset the splice i--; } } var endTime = Date.now(); var delay = endTime - startTime - this.frameDuration; var frameDelay = delay / this.frameDuration; if (frameDelay > 1) { this.dropFrames += frameDelay; } // Do we have more stuff to animate? if (this.animations.length > 0) { helpers.requestAnimFrame.call(window, this.digestWrapper); } } }; // Attach global event to resize each chart instance when the browser resizes helpers.addEvent(window, "resize", (function() { // Basic debounce of resize function so it doesn't hurt performance when resizing browser. var timeout; return function() { clearTimeout(timeout); timeout = setTimeout(function() { each(Chart.instances, function(instance) { // If the responsive flag is set in the chart instance config // Cascade the resize event down to the chart. if (instance.options.responsive) { instance.resize(instance.render, true); } }); }, 50); } ; })()); if (amd) { define(function() { return Chart; }); } else if (typeof module === 'object' && module.exports) { module.exports = Chart; } root.Chart = Chart; Chart.noConflict = function() { root.Chart = previous; return Chart; } ; } ).call(this); (function() { "use strict"; var root = this , Chart = root.Chart , helpers = Chart.helpers; var defaultConfig = { //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value scaleBeginAtZero: true, //Boolean - Whether grid lines are shown across the chart scaleShowGridLines: true, //String - Colour of the grid lines scaleGridLineColor: "rgba(0,0,0,.05)", //Number - Width of the grid lines scaleGridLineWidth: 1, //Boolean - Whether to show horizontal lines (except X axis) scaleShowHorizontalLines: true, //Boolean - Whether to show vertical lines (except Y axis) scaleShowVerticalLines: true, //Boolean - If there is a stroke on each bar barShowStroke: true, //Number - Pixel width of the bar stroke barStrokeWidth: 2, //Number - Spacing between each of the X value sets barValueSpacing: 5, //Number - Spacing between data sets within X values barDatasetSpacing: 1, //String - A legend template legendTemplate: "