/*! * 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;ln;++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=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=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;en&&(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=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;cO;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= 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 furthestRight) { furthestRight = pointPosition.x + halfTextWidth; furthestRightIndex = i; } if (pointPosition.x - halfTextWidth < furthestLeft) { furthestLeft = pointPosition.x - halfTextWidth; furthestLeftIndex = i; } } else if (i < this.valuesCount/2) { // Less than half the values means we'll left align the text if (pointPosition.x + textWidth > furthestRight) { furthestRight = pointPosition.x + textWidth; furthestRightIndex = i; } } else if (i > this.valuesCount/2){ // More than half the values means we'll right align the text if (pointPosition.x - textWidth < furthestLeft) { furthestLeft = pointPosition.x - textWidth; furthestLeftIndex = i; } } } xProtrusionLeft = furthestLeft; xProtrusionRight = Math.ceil(furthestRight - this.width); furthestRightAngle = this.getIndexAngle(furthestRightIndex); furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2); radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2); // Ensure we actually need to reduce the size of the chart radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0; radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2; //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) this.setCenterPoint(radiusReductionLeft, radiusReductionRight); }, setCenterPoint: function(leftMovement, rightMovement){ var maxRight = this.width - rightMovement - this.drawingArea, maxLeft = leftMovement + this.drawingArea; this.xCenter = (maxLeft + maxRight)/2; // Always vertically in the centre as the text height doesn't change this.yCenter = (this.height/2); }, getIndexAngle : function(index){ var angleMultiplier = (Math.PI * 2) / this.valuesCount; // Start from the top instead of right, so remove a quarter of the circle return index * angleMultiplier - (Math.PI/2); }, getPointPosition : function(index, distanceFromCenter){ var thisAngle = this.getIndexAngle(index); return { x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter }; }, draw: function(){ if (this.display){ var ctx = this.ctx; each(this.yLabels, function(label, index){ // Don't draw a centre value if (index > 0){ var yCenterOffset = index * (this.drawingArea/this.steps), yHeight = this.yCenter - yCenterOffset, pointPosition; // Draw circular lines around the scale if (this.lineWidth > 0){ ctx.strokeStyle = this.lineColor; ctx.lineWidth = this.lineWidth; if(this.lineArc){ ctx.beginPath(); ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2); ctx.closePath(); ctx.stroke(); } else{ ctx.beginPath(); for (var i=0;i= 0; i--) { 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 : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" }; Chart.Type.extend({ name: "Bar", defaults : defaultConfig, initialize: function(data){ //Expose options as a scope variable here so we can access it in the ScaleClass var options = this.options; this.ScaleClass = Chart.Scale.extend({ offsetGridLines : true, calculateBarX : function(datasetCount, datasetIndex, barIndex){ //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar var xWidth = this.calculateBaseWidth(), xAbsolute = this.calculateX(barIndex) - (xWidth/2), barWidth = this.calculateBarWidth(datasetCount); return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2; }, calculateBaseWidth : function(){ return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing); }, calculateBarWidth : function(datasetCount){ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing); return (baseWidth / datasetCount); } }); this.datasets = []; //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : []; this.eachBars(function(bar){ bar.restore(['fillColor', 'strokeColor']); }); helpers.each(activeBars, function(activeBar){ activeBar.fillColor = activeBar.highlightFill; activeBar.strokeColor = activeBar.highlightStroke; }); this.showTooltip(activeBars); }); } //Declare the extension of the default point, to cater for the options passed in to the constructor this.BarClass = Chart.Rectangle.extend({ strokeWidth : this.options.barStrokeWidth, showStroke : this.options.barShowStroke, ctx : this.chart.ctx }); //Iterate through each of the datasets, and build this into a property of the chart helpers.each(data.datasets,function(dataset,datasetIndex){ var datasetObject = { label : dataset.label || null, fillColor : dataset.fillColor, strokeColor : dataset.strokeColor, bars : [] }; this.datasets.push(datasetObject); helpers.each(dataset.data,function(dataPoint,index){ //Add a new point for each piece of data, passing any required data to draw. datasetObject.bars.push(new this.BarClass({ value : dataPoint, label : data.labels[index], datasetLabel: dataset.label, strokeColor : dataset.strokeColor, fillColor : dataset.fillColor, highlightFill : dataset.highlightFill || dataset.fillColor, highlightStroke : dataset.highlightStroke || dataset.strokeColor })); },this); },this); this.buildScale(data.labels); this.BarClass.prototype.base = this.scale.endPoint; this.eachBars(function(bar, index, datasetIndex){ helpers.extend(bar, { width : this.scale.calculateBarWidth(this.datasets.length), x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index), y: this.scale.endPoint }); bar.save(); }, this); this.render(); }, update : function(){ this.scale.update(); // Reset any highlight colours before updating. helpers.each(this.activeElements, function(activeElement){ activeElement.restore(['fillColor', 'strokeColor']); }); this.eachBars(function(bar){ bar.save(); }); this.render(); }, eachBars : function(callback){ helpers.each(this.datasets,function(dataset, datasetIndex){ helpers.each(dataset.bars, callback, this, datasetIndex); },this); }, getBarsAtEvent : function(e){ var barsArray = [], eventPosition = helpers.getRelativePosition(e), datasetIterator = function(dataset){ barsArray.push(dataset.bars[barIndex]); }, barIndex; for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) { for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) { if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){ helpers.each(this.datasets, datasetIterator); return barsArray; } } } return barsArray; }, buildScale : function(labels){ var self = this; var dataTotal = function(){ var values = []; self.eachBars(function(bar){ values.push(bar.value); }); return values; }; var scaleOptions = { templateString : this.options.scaleLabel, height : this.chart.height, width : this.chart.width, ctx : this.chart.ctx, textColor : this.options.scaleFontColor, fontSize : this.options.scaleFontSize, fontStyle : this.options.scaleFontStyle, fontFamily : this.options.scaleFontFamily, valuesCount : labels.length, beginAtZero : this.options.scaleBeginAtZero, integersOnly : this.options.scaleIntegersOnly, calculateYRange: function(currentHeight){ var updatedRanges = helpers.calculateScaleRange( dataTotal(), currentHeight, this.fontSize, this.beginAtZero, this.integersOnly ); helpers.extend(this, updatedRanges); }, xLabels : labels, font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), lineWidth : this.options.scaleLineWidth, lineColor : this.options.scaleLineColor, showHorizontalLines : this.options.scaleShowHorizontalLines, showVerticalLines : this.options.scaleShowVerticalLines, gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0, showLabels : this.options.scaleShowLabels, display : this.options.showScale }; if (this.options.scaleOverride){ helpers.extend(scaleOptions, { calculateYRange: helpers.noop, steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) }); } this.scale = new this.ScaleClass(scaleOptions); }, addData : function(valuesArray,label){ //Map the values array for each of the datasets helpers.each(valuesArray,function(value,datasetIndex){ //Add a new point for each piece of data, passing any required data to draw. this.datasets[datasetIndex].bars.push(new this.BarClass({ value : value, label : label, datasetLabel: this.datasets[datasetIndex].label, x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1), y: this.scale.endPoint, width : this.scale.calculateBarWidth(this.datasets.length), base : this.scale.endPoint, strokeColor : this.datasets[datasetIndex].strokeColor, fillColor : this.datasets[datasetIndex].fillColor })); },this); this.scale.addXLabel(label); //Then re-render the chart. this.update(); }, removeData : function(){ this.scale.removeXLabel(); //Then re-render the chart. helpers.each(this.datasets,function(dataset){ dataset.bars.shift(); },this); this.update(); }, reflow : function(){ helpers.extend(this.BarClass.prototype,{ y: this.scale.endPoint, base : this.scale.endPoint }); var newScaleProps = helpers.extend({ height : this.chart.height, width : this.chart.width }); this.scale.update(newScaleProps); }, draw : function(ease){ var easingDecimal = ease || 1; this.clear(); var ctx = this.chart.ctx; this.scale.draw(easingDecimal); //Draw all the bars for each dataset helpers.each(this.datasets,function(dataset,datasetIndex){ helpers.each(dataset.bars,function(bar,index){ if (bar.hasValue()){ bar.base = this.scale.endPoint; //Transition then draw bar.transition({ x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index), y : this.scale.calculateY(bar.value), width : this.scale.calculateBarWidth(this.datasets.length) }, easingDecimal).draw(); } },this); },this); } }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, //Cache a local reference to Chart.helpers helpers = Chart.helpers; var defaultConfig = { //Boolean - Whether we should show a stroke on each segment segmentShowStroke : true, //String - The colour of each segment stroke segmentStrokeColor : "#fff", //Number - The width of each segment stroke segmentStrokeWidth : 2, //The percentage of the chart that we cut out of the middle. percentageInnerCutout : 50, //Number - Amount of animation steps animationSteps : 100, //String - Animation easing effect animationEasing : "easeOutBounce", //Boolean - Whether we animate the rotation of the Doughnut animateRotate : true, //Boolean - Whether we animate scaling the Doughnut from the centre animateScale : false, //String - A legend template legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" }; Chart.Type.extend({ //Passing in a name registers this chart in the Chart namespace name: "Doughnut", //Providing a defaults will also register the deafults in the chart namespace defaults : defaultConfig, //Initialize is fired when the chart is initialized - Data is passed in as a parameter //Config is automatically merged by the core of Chart.js, and is available at this.options initialize: function(data){ //Declare segments as a static property to prevent inheriting across the Chart type prototype this.segments = []; this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; this.SegmentArc = Chart.Arc.extend({ ctx : this.chart.ctx, x : this.chart.width/2, y : this.chart.height/2 }); //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; helpers.each(this.segments,function(segment){ segment.restore(["fillColor"]); }); helpers.each(activeSegments,function(activeSegment){ activeSegment.fillColor = activeSegment.highlightColor; }); this.showTooltip(activeSegments); }); } this.calculateTotal(data); helpers.each(data,function(datapoint, index){ if (!datapoint.color) { datapoint.color = 'hsl(' + (360 * index / data.length) + ', 100%, 50%)'; } this.addData(datapoint, index, true); },this); this.render(); }, getSegmentsAtEvent : function(e){ var segmentsArray = []; var location = helpers.getRelativePosition(e); helpers.each(this.segments,function(segment){ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); },this); return segmentsArray; }, addData : function(segment, atIndex, silent){ var index = atIndex !== undefined ? atIndex : this.segments.length; if ( typeof(segment.color) === "undefined" ) { segment.color = Chart.defaults.global.segmentColorDefault[index % Chart.defaults.global.segmentColorDefault.length]; segment.highlight = Chart.defaults.global.segmentHighlightColorDefaults[index % Chart.defaults.global.segmentHighlightColorDefaults.length]; } this.segments.splice(index, 0, new this.SegmentArc({ value : segment.value, outerRadius : (this.options.animateScale) ? 0 : this.outerRadius, innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout, fillColor : segment.color, highlightColor : segment.highlight || segment.color, showStroke : this.options.segmentShowStroke, strokeWidth : this.options.segmentStrokeWidth, strokeColor : this.options.segmentStrokeColor, startAngle : Math.PI * 1.5, circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value), label : segment.label })); if (!silent){ this.reflow(); this.update(); } }, calculateCircumference : function(value) { if ( this.total > 0 ) { return (Math.PI*2)*(value / this.total); } else { return 0; } }, calculateTotal : function(data){ this.total = 0; helpers.each(data,function(segment){ this.total += Math.abs(segment.value); },this); }, update : function(){ this.calculateTotal(this.segments); // Reset any highlight colours before updating. helpers.each(this.activeElements, function(activeElement){ activeElement.restore(['fillColor']); }); helpers.each(this.segments,function(segment){ segment.save(); }); this.render(); }, removeData: function(atIndex){ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; this.segments.splice(indexToDelete, 1); this.reflow(); this.update(); }, reflow : function(){ helpers.extend(this.SegmentArc.prototype,{ x : this.chart.width/2, y : this.chart.height/2 }); this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; helpers.each(this.segments, function(segment){ segment.update({ outerRadius : this.outerRadius, innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout }); }, this); }, draw : function(easeDecimal){ var animDecimal = (easeDecimal) ? easeDecimal : 1; this.clear(); helpers.each(this.segments,function(segment,index){ segment.transition({ circumference : this.calculateCircumference(segment.value), outerRadius : this.outerRadius, innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout },animDecimal); segment.endAngle = segment.startAngle + segment.circumference; segment.draw(); if (index === 0){ segment.startAngle = Math.PI * 1.5; } //Check to see if it's the last segment, if not get the next and update the start angle if (index < this.segments.length-1){ this.segments[index+1].startAngle = segment.endAngle; } },this); } }); Chart.types.Doughnut.extend({ name : "Pie", defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0}) }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, helpers = Chart.helpers; var defaultConfig = { ///Boolean - Whether grid lines are shown across the chart scaleShowGridLines : true, //String - Colour of the grid lines scaleGridLineColor : "rgba(0,0,0,.05)", //Number - Width of the grid lines scaleGridLineWidth : 1, //Boolean - Whether to show horizontal lines (except X axis) scaleShowHorizontalLines: true, //Boolean - Whether to show vertical lines (except Y axis) scaleShowVerticalLines: true, //Boolean - Whether the line is curved between points bezierCurve : true, //Number - Tension of the bezier curve between points bezierCurveTension : 0.4, //Boolean - Whether to show a dot for each point pointDot : true, //Number - Radius of each point dot in pixels pointDotRadius : 4, //Number - Pixel width of point dot stroke pointDotStrokeWidth : 1, //Number - amount extra to add to the radius to cater for hit detection outside the drawn point pointHitDetectionRadius : 20, //Boolean - Whether to show a stroke for datasets datasetStroke : true, //Number - Pixel width of dataset stroke datasetStrokeWidth : 2, //Boolean - Whether to fill the dataset with a colour datasetFill : true, //String - A legend template legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
", //Boolean - Whether to horizontally center the label and point dot inside the grid offsetGridLines : false }; Chart.Type.extend({ name: "Line", defaults : defaultConfig, initialize: function(data){ //Declare the extension of the default point, to cater for the options passed in to the constructor this.PointClass = Chart.Point.extend({ offsetGridLines : this.options.offsetGridLines, strokeWidth : this.options.pointDotStrokeWidth, radius : this.options.pointDotRadius, display: this.options.pointDot, hitDetectionRadius : this.options.pointHitDetectionRadius, ctx : this.chart.ctx, inRange : function(mouseX){ return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2)); } }); this.datasets = []; //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; this.eachPoints(function(point){ point.restore(['fillColor', 'strokeColor']); }); helpers.each(activePoints, function(activePoint){ activePoint.fillColor = activePoint.highlightFill; activePoint.strokeColor = activePoint.highlightStroke; }); this.showTooltip(activePoints); }); } //Iterate through each of the datasets, and build this into a property of the chart helpers.each(data.datasets,function(dataset){ var datasetObject = { label : dataset.label || null, fillColor : dataset.fillColor, strokeColor : dataset.strokeColor, pointColor : dataset.pointColor, pointStrokeColor : dataset.pointStrokeColor, points : [] }; this.datasets.push(datasetObject); helpers.each(dataset.data,function(dataPoint,index){ //Add a new point for each piece of data, passing any required data to draw. datasetObject.points.push(new this.PointClass({ value : dataPoint, label : data.labels[index], datasetLabel: dataset.label, strokeColor : dataset.pointStrokeColor, fillColor : dataset.pointColor, highlightFill : dataset.pointHighlightFill || dataset.pointColor, highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor })); },this); this.buildScale(data.labels); this.eachPoints(function(point, index){ helpers.extend(point, { x: this.scale.calculateX(index), y: this.scale.endPoint }); point.save(); }, this); },this); this.render(); }, update : function(){ this.scale.update(); // Reset any highlight colours before updating. helpers.each(this.activeElements, function(activeElement){ activeElement.restore(['fillColor', 'strokeColor']); }); this.eachPoints(function(point){ point.save(); }); this.render(); }, eachPoints : function(callback){ helpers.each(this.datasets,function(dataset){ helpers.each(dataset.points,callback,this); },this); }, getPointsAtEvent : function(e){ var pointsArray = [], eventPosition = helpers.getRelativePosition(e); helpers.each(this.datasets,function(dataset){ helpers.each(dataset.points,function(point){ if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point); }); },this); return pointsArray; }, buildScale : function(labels){ var self = this; var dataTotal = function(){ var values = []; self.eachPoints(function(point){ values.push(point.value); }); return values; }; var scaleOptions = { templateString : this.options.scaleLabel, height : this.chart.height, width : this.chart.width, ctx : this.chart.ctx, textColor : this.options.scaleFontColor, offsetGridLines : this.options.offsetGridLines, fontSize : this.options.scaleFontSize, fontStyle : this.options.scaleFontStyle, fontFamily : this.options.scaleFontFamily, valuesCount : labels.length, beginAtZero : this.options.scaleBeginAtZero, integersOnly : this.options.scaleIntegersOnly, calculateYRange : function(currentHeight){ var updatedRanges = helpers.calculateScaleRange( dataTotal(), currentHeight, this.fontSize, this.beginAtZero, this.integersOnly ); helpers.extend(this, updatedRanges); }, xLabels : labels, font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), lineWidth : this.options.scaleLineWidth, lineColor : this.options.scaleLineColor, showHorizontalLines : this.options.scaleShowHorizontalLines, showVerticalLines : this.options.scaleShowVerticalLines, gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth, showLabels : this.options.scaleShowLabels, display : this.options.showScale }; if (this.options.scaleOverride){ helpers.extend(scaleOptions, { calculateYRange: helpers.noop, steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) }); } this.scale = new Chart.Scale(scaleOptions); }, addData : function(valuesArray,label){ //Map the values array for each of the datasets helpers.each(valuesArray,function(value,datasetIndex){ //Add a new point for each piece of data, passing any required data to draw. this.datasets[datasetIndex].points.push(new this.PointClass({ value : value, label : label, datasetLabel: this.datasets[datasetIndex].label, x: this.scale.calculateX(this.scale.valuesCount+1), y: this.scale.endPoint, strokeColor : this.datasets[datasetIndex].pointStrokeColor, fillColor : this.datasets[datasetIndex].pointColor })); },this); this.scale.addXLabel(label); //Then re-render the chart. this.update(); }, removeData : function(){ this.scale.removeXLabel(); //Then re-render the chart. helpers.each(this.datasets,function(dataset){ dataset.points.shift(); },this); this.update(); }, reflow : function(){ var newScaleProps = helpers.extend({ height : this.chart.height, width : this.chart.width }); this.scale.update(newScaleProps); }, draw : function(ease){ var easingDecimal = ease || 1; this.clear(); var ctx = this.chart.ctx; // Some helper methods for getting the next/prev points var hasValue = function(item){ return item.value !== null; }, nextPoint = function(point, collection, index){ return helpers.findNextWhere(collection, hasValue, index) || point; }, previousPoint = function(point, collection, index){ return helpers.findPreviousWhere(collection, hasValue, index) || point; }; if (!this.scale) return; this.scale.draw(easingDecimal); helpers.each(this.datasets,function(dataset){ var pointsWithValues = helpers.where(dataset.points, hasValue); //Transition each point first so that the line and point drawing isn't out of sync //We can use this extra loop to calculate the control points of this dataset also in this loop helpers.each(dataset.points, function(point, index){ if (point.hasValue()){ point.transition({ y : this.scale.calculateY(point.value), x : this.scale.calculateX(index) }, easingDecimal); } },this); // Control points need to be calculated in a separate loop, because we need to know the current x/y of the point // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed if (this.options.bezierCurve){ helpers.each(pointsWithValues, function(point, index){ var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0; point.controlPoints = helpers.splineCurve( previousPoint(point, pointsWithValues, index), point, nextPoint(point, pointsWithValues, index), tension ); // Prevent the bezier going outside of the bounds of the graph // Cap puter bezier handles to the upper/lower scale bounds if (point.controlPoints.outer.y > this.scale.endPoint){ point.controlPoints.outer.y = this.scale.endPoint; } else if (point.controlPoints.outer.y < this.scale.startPoint){ point.controlPoints.outer.y = this.scale.startPoint; } // Cap inner bezier handles to the upper/lower scale bounds if (point.controlPoints.inner.y > this.scale.endPoint){ point.controlPoints.inner.y = this.scale.endPoint; } else if (point.controlPoints.inner.y < this.scale.startPoint){ point.controlPoints.inner.y = this.scale.startPoint; } },this); } //Draw the line between all the points ctx.lineWidth = this.options.datasetStrokeWidth; ctx.strokeStyle = dataset.strokeColor; ctx.beginPath(); helpers.each(pointsWithValues, function(point, index){ if (index === 0){ ctx.moveTo(point.x, point.y); } else{ if(this.options.bezierCurve){ var previous = previousPoint(point, pointsWithValues, index); ctx.bezierCurveTo( previous.controlPoints.outer.x, previous.controlPoints.outer.y, point.controlPoints.inner.x, point.controlPoints.inner.y, point.x, point.y ); } else{ ctx.lineTo(point.x,point.y); } } }, this); if (this.options.datasetStroke) { ctx.stroke(); } if (this.options.datasetFill && pointsWithValues.length > 0){ //Round off the line by going to the base of the chart, back to the start, then fill. ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint); ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint); ctx.fillStyle = dataset.fillColor; ctx.closePath(); ctx.fill(); } //Now draw the points over the line //A little inefficient double looping, but better than the line //lagging behind the point positions helpers.each(pointsWithValues,function(point){ point.draw(); }); },this); } }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, //Cache a local reference to Chart.helpers helpers = Chart.helpers; var defaultConfig = { //Boolean - Show a backdrop to the scale label scaleShowLabelBackdrop : true, //String - The colour of the label backdrop scaleBackdropColor : "rgba(255,255,255,0.75)", // Boolean - Whether the scale should begin at zero scaleBeginAtZero : true, //Number - The backdrop padding above & below the label in pixels scaleBackdropPaddingY : 2, //Number - The backdrop padding to the side of the label in pixels scaleBackdropPaddingX : 2, //Boolean - Show line for each value in the scale scaleShowLine : true, //Boolean - Stroke a line around each segment in the chart segmentShowStroke : true, //String - The colour of the stroke on each segment. segmentStrokeColor : "#fff", //Number - The width of the stroke value in pixels segmentStrokeWidth : 2, //Number - Amount of animation steps animationSteps : 100, //String - Animation easing effect. animationEasing : "easeOutBounce", //Boolean - Whether to animate the rotation of the chart animateRotate : true, //Boolean - Whether to animate scaling the chart from the centre animateScale : false, //String - A legend template legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" }; Chart.Type.extend({ //Passing in a name registers this chart in the Chart namespace name: "PolarArea", //Providing a defaults will also register the deafults in the chart namespace defaults : defaultConfig, //Initialize is fired when the chart is initialized - Data is passed in as a parameter //Config is automatically merged by the core of Chart.js, and is available at this.options initialize: function(data){ this.segments = []; //Declare segment class as a chart instance specific class, so it can share props for this instance this.SegmentArc = Chart.Arc.extend({ showStroke : this.options.segmentShowStroke, strokeWidth : this.options.segmentStrokeWidth, strokeColor : this.options.segmentStrokeColor, ctx : this.chart.ctx, innerRadius : 0, x : this.chart.width/2, y : this.chart.height/2 }); this.scale = new Chart.RadialScale({ display: this.options.showScale, fontStyle: this.options.scaleFontStyle, fontSize: this.options.scaleFontSize, fontFamily: this.options.scaleFontFamily, fontColor: this.options.scaleFontColor, showLabels: this.options.scaleShowLabels, showLabelBackdrop: this.options.scaleShowLabelBackdrop, backdropColor: this.options.scaleBackdropColor, backdropPaddingY : this.options.scaleBackdropPaddingY, backdropPaddingX: this.options.scaleBackdropPaddingX, lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, lineColor: this.options.scaleLineColor, lineArc: true, width: this.chart.width, height: this.chart.height, xCenter: this.chart.width/2, yCenter: this.chart.height/2, ctx : this.chart.ctx, templateString: this.options.scaleLabel, valuesCount: data.length }); this.updateScaleRange(data); this.scale.update(); helpers.each(data,function(segment,index){ this.addData(segment,index,true); },this); //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; helpers.each(this.segments,function(segment){ segment.restore(["fillColor"]); }); helpers.each(activeSegments,function(activeSegment){ activeSegment.fillColor = activeSegment.highlightColor; }); this.showTooltip(activeSegments); }); } this.render(); }, getSegmentsAtEvent : function(e){ var segmentsArray = []; var location = helpers.getRelativePosition(e); helpers.each(this.segments,function(segment){ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); },this); return segmentsArray; }, addData : function(segment, atIndex, silent){ var index = atIndex || this.segments.length; this.segments.splice(index, 0, new this.SegmentArc({ fillColor: segment.color, highlightColor: segment.highlight || segment.color, label: segment.label, value: segment.value, outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value), circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(), startAngle: Math.PI * 1.5 })); if (!silent){ this.reflow(); this.update(); } }, removeData: function(atIndex){ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; this.segments.splice(indexToDelete, 1); this.reflow(); this.update(); }, calculateTotal: function(data){ this.total = 0; helpers.each(data,function(segment){ this.total += segment.value; },this); this.scale.valuesCount = this.segments.length; }, updateScaleRange: function(datapoints){ var valuesArray = []; helpers.each(datapoints,function(segment){ valuesArray.push(segment.value); }); var scaleSizes = (this.options.scaleOverride) ? { steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) } : helpers.calculateScaleRange( valuesArray, helpers.min([this.chart.width, this.chart.height])/2, this.options.scaleFontSize, this.options.scaleBeginAtZero, this.options.scaleIntegersOnly ); helpers.extend( this.scale, scaleSizes, { size: helpers.min([this.chart.width, this.chart.height]), xCenter: this.chart.width/2, yCenter: this.chart.height/2 } ); }, update : function(){ this.calculateTotal(this.segments); helpers.each(this.segments,function(segment){ segment.save(); }); this.reflow(); this.render(); }, reflow : function(){ helpers.extend(this.SegmentArc.prototype,{ x : this.chart.width/2, y : this.chart.height/2 }); this.updateScaleRange(this.segments); this.scale.update(); helpers.extend(this.scale,{ xCenter: this.chart.width/2, yCenter: this.chart.height/2 }); helpers.each(this.segments, function(segment){ segment.update({ outerRadius : this.scale.calculateCenterOffset(segment.value) }); }, this); }, draw : function(ease){ var easingDecimal = ease || 1; //Clear & draw the canvas this.clear(); helpers.each(this.segments,function(segment, index){ segment.transition({ circumference : this.scale.getCircumference(), outerRadius : this.scale.calculateCenterOffset(segment.value) },easingDecimal); segment.endAngle = segment.startAngle + segment.circumference; // If we've removed the first segment we need to set the first one to // start at the top. if (index === 0){ segment.startAngle = Math.PI * 1.5; } //Check to see if it's the last segment, if not get the next and update the start angle if (index < this.segments.length - 1){ this.segments[index+1].startAngle = segment.endAngle; } segment.draw(); }, this); this.scale.draw(); } }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, helpers = Chart.helpers; Chart.Type.extend({ name: "Radar", defaults:{ //Boolean - Whether to show lines for each scale point scaleShowLine : true, //Boolean - Whether we show the angle lines out of the radar angleShowLineOut : true, //Boolean - Whether to show labels on the scale scaleShowLabels : false, // Boolean - Whether the scale should begin at zero scaleBeginAtZero : true, //String - Colour of the angle line angleLineColor : "rgba(0,0,0,.1)", //Number - Pixel width of the angle line angleLineWidth : 1, //String - Point label font declaration pointLabelFontFamily : "'Arial'", //String - Point label font weight pointLabelFontStyle : "normal", //Number - Point label font size in pixels pointLabelFontSize : 10, //String - Point label font colour pointLabelFontColor : "#666", //Boolean - Whether to show a dot for each point pointDot : true, //Number - Radius of each point dot in pixels pointDotRadius : 3, //Number - Pixel width of point dot stroke pointDotStrokeWidth : 1, //Number - amount extra to add to the radius to cater for hit detection outside the drawn point pointHitDetectionRadius : 20, //Boolean - Whether to show a stroke for datasets datasetStroke : true, //Number - Pixel width of dataset stroke datasetStrokeWidth : 2, //Boolean - Whether to fill the dataset with a colour datasetFill : true, //String - A legend template legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" }, initialize: function(data){ this.PointClass = Chart.Point.extend({ strokeWidth : this.options.pointDotStrokeWidth, radius : this.options.pointDotRadius, display: this.options.pointDot, hitDetectionRadius : this.options.pointHitDetectionRadius, ctx : this.chart.ctx }); this.datasets = []; this.buildScale(data); //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; this.eachPoints(function(point){ point.restore(['fillColor', 'strokeColor']); }); helpers.each(activePointsCollection, function(activePoint){ activePoint.fillColor = activePoint.highlightFill; activePoint.strokeColor = activePoint.highlightStroke; }); this.showTooltip(activePointsCollection); }); } //Iterate through each of the datasets, and build this into a property of the chart helpers.each(data.datasets,function(dataset){ var datasetObject = { label: dataset.label || null, fillColor : dataset.fillColor, strokeColor : dataset.strokeColor, pointColor : dataset.pointColor, pointStrokeColor : dataset.pointStrokeColor, points : [] }; this.datasets.push(datasetObject); helpers.each(dataset.data,function(dataPoint,index){ //Add a new point for each piece of data, passing any required data to draw. var pointPosition; if (!this.scale.animation){ pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint)); } datasetObject.points.push(new this.PointClass({ value : dataPoint, label : data.labels[index], datasetLabel: dataset.label, x: (this.options.animation) ? this.scale.xCenter : pointPosition.x, y: (this.options.animation) ? this.scale.yCenter : pointPosition.y, strokeColor : dataset.pointStrokeColor, fillColor : dataset.pointColor, highlightFill : dataset.pointHighlightFill || dataset.pointColor, highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor })); },this); },this); this.render(); }, eachPoints : function(callback){ helpers.each(this.datasets,function(dataset){ helpers.each(dataset.points,callback,this); },this); }, getPointsAtEvent : function(evt){ var mousePosition = helpers.getRelativePosition(evt), fromCenter = helpers.getAngleFromPoint({ x: this.scale.xCenter, y: this.scale.yCenter }, mousePosition); var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount, pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex), activePointsCollection = []; // If we're at the top, make the pointIndex 0 to get the first of the array. if (pointIndex >= this.scale.valuesCount || pointIndex < 0){ pointIndex = 0; } if (fromCenter.distance <= this.scale.drawingArea){ helpers.each(this.datasets, function(dataset){ activePointsCollection.push(dataset.points[pointIndex]); }); } return activePointsCollection; }, buildScale : function(data){ this.scale = new Chart.RadialScale({ display: this.options.showScale, fontStyle: this.options.scaleFontStyle, fontSize: this.options.scaleFontSize, fontFamily: this.options.scaleFontFamily, fontColor: this.options.scaleFontColor, showLabels: this.options.scaleShowLabels, showLabelBackdrop: this.options.scaleShowLabelBackdrop, backdropColor: this.options.scaleBackdropColor, backgroundColors: this.options.scaleBackgroundColors, backdropPaddingY : this.options.scaleBackdropPaddingY, backdropPaddingX: this.options.scaleBackdropPaddingX, lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, lineColor: this.options.scaleLineColor, angleLineColor : this.options.angleLineColor, angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0, // Point labels at the edge of each line pointLabelFontColor : this.options.pointLabelFontColor, pointLabelFontSize : this.options.pointLabelFontSize, pointLabelFontFamily : this.options.pointLabelFontFamily, pointLabelFontStyle : this.options.pointLabelFontStyle, height : this.chart.height, width: this.chart.width, xCenter: this.chart.width/2, yCenter: this.chart.height/2, ctx : this.chart.ctx, templateString: this.options.scaleLabel, labels: data.labels, valuesCount: data.datasets[0].data.length }); this.scale.setScaleSize(); this.updateScaleRange(data.datasets); this.scale.buildYLabels(); }, updateScaleRange: function(datasets){ var valuesArray = (function(){ var totalDataArray = []; helpers.each(datasets,function(dataset){ if (dataset.data){ totalDataArray = totalDataArray.concat(dataset.data); } else { helpers.each(dataset.points, function(point){ totalDataArray.push(point.value); }); } }); return totalDataArray; })(); var scaleSizes = (this.options.scaleOverride) ? { steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) } : helpers.calculateScaleRange( valuesArray, helpers.min([this.chart.width, this.chart.height])/2, this.options.scaleFontSize, this.options.scaleBeginAtZero, this.options.scaleIntegersOnly ); helpers.extend( this.scale, scaleSizes ); }, addData : function(valuesArray,label){ //Map the values array for each of the datasets this.scale.valuesCount++; helpers.each(valuesArray,function(value,datasetIndex){ var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value)); this.datasets[datasetIndex].points.push(new this.PointClass({ value : value, label : label, datasetLabel: this.datasets[datasetIndex].label, x: pointPosition.x, y: pointPosition.y, strokeColor : this.datasets[datasetIndex].pointStrokeColor, fillColor : this.datasets[datasetIndex].pointColor })); },this); this.scale.labels.push(label); this.reflow(); this.update(); }, removeData : function(){ this.scale.valuesCount--; this.scale.labels.shift(); helpers.each(this.datasets,function(dataset){ dataset.points.shift(); },this); this.reflow(); this.update(); }, update : function(){ this.eachPoints(function(point){ point.save(); }); this.reflow(); this.render(); }, reflow: function(){ helpers.extend(this.scale, { width : this.chart.width, height: this.chart.height, size : helpers.min([this.chart.width, this.chart.height]), xCenter: this.chart.width/2, yCenter: this.chart.height/2 }); this.updateScaleRange(this.datasets); this.scale.setScaleSize(); this.scale.buildYLabels(); }, draw : function(ease){ var easeDecimal = ease || 1, ctx = this.chart.ctx; this.clear(); this.scale.draw(); helpers.each(this.datasets,function(dataset){ //Transition each point first so that the line and point drawing isn't out of sync helpers.each(dataset.points,function(point,index){ if (point.hasValue()){ point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal); } },this); //Draw the line between all the points ctx.lineWidth = this.options.datasetStrokeWidth; ctx.strokeStyle = dataset.strokeColor; ctx.beginPath(); helpers.each(dataset.points,function(point,index){ if (index === 0){ ctx.moveTo(point.x,point.y); } else{ ctx.lineTo(point.x,point.y); } },this); ctx.closePath(); ctx.stroke(); ctx.fillStyle = dataset.fillColor; if(this.options.datasetFill){ ctx.fill(); } //Now draw the points over the line //A little inefficient double looping, but better than the line //lagging behind the point positions helpers.each(dataset.points,function(point){ if (point.hasValue()){ point.draw(); } }); },this); } }); }).call(this); //WOW SCRIPT - REVEAL ANIMATIONS WHEN SCROLLING (function() { var MutationObserver, Util, WeakMap, getComputedStyle, getComputedStyleRX, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Util = (function() { function Util() {} Util.prototype.extend = function(custom, defaults) { var key, value; for (key in defaults) { value = defaults[key]; if (custom[key] == null) { custom[key] = value; } } return custom; }; Util.prototype.isMobile = function(agent) { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(agent); }; Util.prototype.createEvent = function(event, bubble, cancel, detail) { var customEvent; if (bubble == null) { bubble = false; } if (cancel == null) { cancel = false; } if (detail == null) { detail = null; } if (document.createEvent != null) { customEvent = document.createEvent('CustomEvent'); customEvent.initCustomEvent(event, bubble, cancel, detail); } else if (document.createEventObject != null) { customEvent = document.createEventObject(); customEvent.eventType = event; } else { customEvent.eventName = event; } return customEvent; }; Util.prototype.emitEvent = function(elem, event) { if (elem.dispatchEvent != null) { return elem.dispatchEvent(event); } else if (event in (elem != null)) { return elem[event](); } else if (("on" + event) in (elem != null)) { return elem["on" + event](); } }; Util.prototype.addEvent = function(elem, event, fn) { if (elem.addEventListener != null) { return elem.addEventListener(event, fn, false); } else if (elem.attachEvent != null) { return elem.attachEvent("on" + event, fn); } else { return elem[event] = fn; } }; Util.prototype.removeEvent = function(elem, event, fn) { if (elem.removeEventListener != null) { return elem.removeEventListener(event, fn, false); } else if (elem.detachEvent != null) { return elem.detachEvent("on" + event, fn); } else { return delete elem[event]; } }; Util.prototype.innerHeight = function() { if ('innerHeight' in window) { return window.innerHeight; } else { return document.documentElement.clientHeight; } }; return Util; })(); WeakMap = this.WeakMap || this.MozWeakMap || (WeakMap = (function() { function WeakMap() { this.keys = []; this.values = []; } WeakMap.prototype.get = function(key) { var i, item, j, len, ref; ref = this.keys; for (i = j = 0, len = ref.length; j < len; i = ++j) { item = ref[i]; if (item === key) { return this.values[i]; } } }; WeakMap.prototype.set = function(key, value) { var i, item, j, len, ref; ref = this.keys; for (i = j = 0, len = ref.length; j < len; i = ++j) { item = ref[i]; if (item === key) { this.values[i] = value; return; } } this.keys.push(key); return this.values.push(value); }; return WeakMap; })()); MutationObserver = this.MutationObserver || this.WebkitMutationObserver || this.MozMutationObserver || (MutationObserver = (function() { function MutationObserver() { if (typeof console !== "undefined" && console !== null) { console.warn('MutationObserver is not supported by your browser.'); } if (typeof console !== "undefined" && console !== null) { console.warn('WOW.js cannot detect dom mutations, please call .sync() after loading new content.'); } } MutationObserver.notSupported = true; MutationObserver.prototype.observe = function() {}; return MutationObserver; })()); getComputedStyle = this.getComputedStyle || function(el, pseudo) { this.getPropertyValue = function(prop) { var ref; if (prop === 'float') { prop = 'styleFloat'; } if (getComputedStyleRX.test(prop)) { prop.replace(getComputedStyleRX, function(_, _char) { return _char.toUpperCase(); }); } return ((ref = el.currentStyle) != null ? ref[prop] : void 0) || null; }; return this; }; getComputedStyleRX = /(\-([a-z]){1})/g; this.WOW = (function() { WOW.prototype.defaults = { boxClass: 'wow', animateClass: 'animated', offset: 0, mobile: true, live: true, callback: null, scrollContainer: null }; function WOW(options) { if (options == null) { options = {}; } this.scrollCallback = bind(this.scrollCallback, this); this.scrollHandler = bind(this.scrollHandler, this); this.resetAnimation = bind(this.resetAnimation, this); this.start = bind(this.start, this); this.scrolled = true; this.config = this.util().extend(options, this.defaults); if (options.scrollContainer != null) { this.config.scrollContainer = document.querySelector(options.scrollContainer); } this.animationNameCache = new WeakMap(); this.wowEvent = this.util().createEvent(this.config.boxClass); } WOW.prototype.init = function() { var ref; this.element = window.document.documentElement; if ((ref = document.readyState) === "interactive" || ref === "complete") { this.start(); } else { this.util().addEvent(document, 'DOMContentLoaded', this.start); } return this.finished = []; }; WOW.prototype.start = function() { var box, j, len, ref; this.stopped = false; this.boxes = (function() { var j, len, ref, results; ref = this.element.querySelectorAll("." + this.config.boxClass); results = []; for (j = 0, len = ref.length; j < len; j++) { box = ref[j]; results.push(box); } return results; }).call(this); this.all = (function() { var j, len, ref, results; ref = this.boxes; results = []; for (j = 0, len = ref.length; j < len; j++) { box = ref[j]; results.push(box); } return results; }).call(this); if (this.boxes.length) { if (this.disabled()) { this.resetStyle(); } else { ref = this.boxes; for (j = 0, len = ref.length; j < len; j++) { box = ref[j]; this.applyStyle(box, true); } } } if (!this.disabled()) { this.util().addEvent(this.config.scrollContainer || window, 'scroll', this.scrollHandler); this.util().addEvent(window, 'resize', this.scrollHandler); this.interval = setInterval(this.scrollCallback, 50); } if (this.config.live) { return new MutationObserver((function(_this) { return function(records) { var k, len1, node, record, results; results = []; for (k = 0, len1 = records.length; k < len1; k++) { record = records[k]; results.push((function() { var l, len2, ref1, results1; ref1 = record.addedNodes || []; results1 = []; for (l = 0, len2 = ref1.length; l < len2; l++) { node = ref1[l]; results1.push(this.doSync(node)); } return results1; }).call(_this)); } return results; }; })(this)).observe(document.body, { childList: true, subtree: true }); } }; WOW.prototype.stop = function() { this.stopped = true; this.util().removeEvent(this.config.scrollContainer || window, 'scroll', this.scrollHandler); this.util().removeEvent(window, 'resize', this.scrollHandler); if (this.interval != null) { return clearInterval(this.interval); } }; WOW.prototype.sync = function(element) { if (MutationObserver.notSupported) { return this.doSync(this.element); } }; WOW.prototype.doSync = function(element) { var box, j, len, ref, results; if (element == null) { element = this.element; } if (element.nodeType !== 1) { return; } element = element.parentNode || element; ref = element.querySelectorAll("." + this.config.boxClass); results = []; for (j = 0, len = ref.length; j < len; j++) { box = ref[j]; if (indexOf.call(this.all, box) < 0) { this.boxes.push(box); this.all.push(box); if (this.stopped || this.disabled()) { this.resetStyle(); } else { this.applyStyle(box, true); } results.push(this.scrolled = true); } else { results.push(void 0); } } return results; }; WOW.prototype.show = function(box) { this.applyStyle(box); box.className = box.className + " " + this.config.animateClass; if (this.config.callback != null) { this.config.callback(box); } this.util().emitEvent(box, this.wowEvent); this.util().addEvent(box, 'animationend', this.resetAnimation); this.util().addEvent(box, 'oanimationend', this.resetAnimation); this.util().addEvent(box, 'webkitAnimationEnd', this.resetAnimation); this.util().addEvent(box, 'MSAnimationEnd', this.resetAnimation); return box; }; WOW.prototype.applyStyle = function(box, hidden) { var delay, duration, iteration; duration = box.getAttribute('data-wow-duration'); delay = box.getAttribute('data-wow-delay'); iteration = box.getAttribute('data-wow-iteration'); return this.animate((function(_this) { return function() { return _this.customStyle(box, hidden, duration, delay, iteration); }; })(this)); }; WOW.prototype.animate = (function() { if ('requestAnimationFrame' in window) { return function(callback) { return window.requestAnimationFrame(callback); }; } else { return function(callback) { return callback(); }; } })(); WOW.prototype.resetStyle = function() { var box, j, len, ref, results; ref = this.boxes; results = []; for (j = 0, len = ref.length; j < len; j++) { box = ref[j]; results.push(box.style.visibility = 'visible'); } return results; }; WOW.prototype.resetAnimation = function(event) { var target; if (event.type.toLowerCase().indexOf('animationend') >= 0) { target = event.target || event.srcElement; return target.className = target.className.replace(this.config.animateClass, '').trim(); } }; WOW.prototype.customStyle = function(box, hidden, duration, delay, iteration) { if (hidden) { this.cacheAnimationName(box); } box.style.visibility = hidden ? 'hidden' : 'visible'; if (duration) { this.vendorSet(box.style, { animationDuration: duration }); } if (delay) { this.vendorSet(box.style, { animationDelay: delay }); } if (iteration) { this.vendorSet(box.style, { animationIterationCount: iteration }); } this.vendorSet(box.style, { animationName: hidden ? 'none' : this.cachedAnimationName(box) }); return box; }; WOW.prototype.vendors = ["moz", "webkit"]; WOW.prototype.vendorSet = function(elem, properties) { var name, results, value, vendor; results = []; for (name in properties) { value = properties[name]; elem["" + name] = value; results.push((function() { var j, len, ref, results1; ref = this.vendors; results1 = []; for (j = 0, len = ref.length; j < len; j++) { vendor = ref[j]; results1.push(elem["" + vendor + (name.charAt(0).toUpperCase()) + (name.substr(1))] = value); } return results1; }).call(this)); } return results; }; WOW.prototype.vendorCSS = function(elem, property) { var j, len, ref, result, style, vendor; style = getComputedStyle(elem); result = style.getPropertyCSSValue(property); ref = this.vendors; for (j = 0, len = ref.length; j < len; j++) { vendor = ref[j]; result = result || style.getPropertyCSSValue("-" + vendor + "-" + property); } return result; }; WOW.prototype.animationName = function(box) { var animationName; try { animationName = this.vendorCSS(box, 'animation-name').cssText; } catch (_error) { animationName = getComputedStyle(box).getPropertyValue('animation-name'); } if (animationName === 'none') { return ''; } else { return animationName; } }; WOW.prototype.cacheAnimationName = function(box) { return this.animationNameCache.set(box, this.animationName(box)); }; WOW.prototype.cachedAnimationName = function(box) { return this.animationNameCache.get(box); }; WOW.prototype.scrollHandler = function() { return this.scrolled = true; }; WOW.prototype.scrollCallback = function() { var box; if (this.scrolled) { this.scrolled = false; this.boxes = (function() { var j, len, ref, results; ref = this.boxes; results = []; for (j = 0, len = ref.length; j < len; j++) { box = ref[j]; if (!(box)) { continue; } if (this.isVisible(box)) { this.show(box); continue; } results.push(box); } return results; }).call(this); if (!(this.boxes.length || this.config.live)) { return this.stop(); } } }; WOW.prototype.offsetTop = function(element) { var top; while (element.offsetTop === void 0) { element = element.parentNode; } top = element.offsetTop; while (element = element.offsetParent) { top += element.offsetTop; } return top; }; WOW.prototype.isVisible = function(box) { var bottom, offset, top, viewBottom, viewTop; offset = box.getAttribute('data-wow-offset') || this.config.offset; viewTop = (this.config.scrollContainer && this.config.scrollContainer.scrollTop) || window.pageYOffset; viewBottom = viewTop + Math.min(this.element.clientHeight, this.util().innerHeight()) - offset; top = this.offsetTop(box); bottom = top + box.clientHeight; return top <= viewBottom && bottom >= viewTop; }; WOW.prototype.util = function() { return this._util != null ? this._util : this._util = new Util(); }; WOW.prototype.disabled = function() { return !this.config.mobile && this.util().isMobile(navigator.userAgent); }; return WOW; })(); }).call(this);/* SCROLLING NAVBAR */ //jQuery to collapse the navbar on scroll $(window).scroll(function () { if ($(".navbar").offset()) { if ($(".navbar").offset().top > 50) { $(".scrolling-navbar").addClass("top-nav-collapse"); } else { $(".scrolling-navbar").removeClass("top-nav-collapse"); } } }); //jQuery for page scrolling feature - requires jQuery Easing plugin $(function () { $('a.page-scroll').bind('click', function (event) { var $anchor = $(this); $('html, body').stop().animate({ scrollTop: $($anchor.attr('href')).offset().top }, 1500, 'easeInOutExpo'); event.preventDefault(); }); }); /*! * Waves v0.7.5 * http://fian.my.id/Waves * * Copyright 2014-2016 Alfiana E. Sibuea and other contributors * Released under the MIT license * https://github.com/fians/Waves/blob/master/LICENSE */ ; (function (window, factory) { 'use strict'; // AMD. Register as an anonymous module. Wrap in function so we have access // to root via `this`. if (typeof define === 'function' && define.amd) { define([], function () { return factory.apply(window); }); } // Node. Does not work with strict CommonJS, but only CommonJS-like // environments that support module.exports, like Node. else if (typeof exports === 'object') { module.exports = factory.call(window); } // Browser globals. else { window.Waves = factory.call(window); } })(typeof global === 'object' ? global : this, function () { 'use strict'; var Waves = Waves || {}; var $$ = document.querySelectorAll.bind(document); var toString = Object.prototype.toString; var isTouchAvailable = 'ontouchstart' in window; // Find exact position of element function isWindow(obj) { return obj !== null && obj === obj.window; } function getWindow(elem) { return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView; } function isObject(value) { var type = typeof value; return type === 'function' || type === 'object' && !!value; } function isDOMNode(obj) { return isObject(obj) && obj.nodeType > 0; } function getWavesElements(nodes) { var stringRepr = toString.call(nodes); if (stringRepr === '[object String]') { return $$(nodes); } else if (isObject(nodes) && /^\[object (Array|HTMLCollection|NodeList|Object)\]$/.test(stringRepr) && nodes.hasOwnProperty('length')) { return nodes; } else if (isDOMNode(nodes)) { return [nodes]; } return []; } function offset(elem) { var docElem, win, box = { top: 0, left: 0 }, doc = elem && elem.ownerDocument; docElem = doc.documentElement; if (typeof elem.getBoundingClientRect !== typeof undefined) { box = elem.getBoundingClientRect(); } win = getWindow(doc); return { top: box.top + win.pageYOffset - docElem.clientTop, left: box.left + win.pageXOffset - docElem.clientLeft }; } function convertStyle(styleObj) { var style = ''; for (var prop in styleObj) { if (styleObj.hasOwnProperty(prop)) { style += (prop + ':' + styleObj[prop] + ';'); } } return style; } var Effect = { // Effect duration duration: 750, // Effect delay (check for scroll before showing effect) delay: 200, show: function (e, element, velocity) { // Disable right click if (e.button === 2) { return false; } element = element || this; // Create ripple var ripple = document.createElement('div'); ripple.className = 'waves-ripple waves-rippling'; element.appendChild(ripple); // Get click coordinate and element width var pos = offset(element); var relativeY = 0; var relativeX = 0; // Support for touch devices if ('touches' in e && e.touches.length) { relativeY = (e.touches[0].pageY - pos.top); relativeX = (e.touches[0].pageX - pos.left); } //Normal case else { relativeY = (e.pageY - pos.top); relativeX = (e.pageX - pos.left); } // Support for synthetic events relativeX = relativeX >= 0 ? relativeX : 0; relativeY = relativeY >= 0 ? relativeY : 0; var scale = 'scale(' + ((element.clientWidth / 100) * 3) + ')'; var translate = 'translate(0,0)'; if (velocity) { translate = 'translate(' + (velocity.x) + 'px, ' + (velocity.y) + 'px)'; } // Attach data to element ripple.setAttribute('data-hold', Date.now()); ripple.setAttribute('data-x', relativeX); ripple.setAttribute('data-y', relativeY); ripple.setAttribute('data-scale', scale); ripple.setAttribute('data-translate', translate); // Set ripple position var rippleStyle = { top: relativeY + 'px', left: relativeX + 'px' }; ripple.classList.add('waves-notransition'); ripple.setAttribute('style', convertStyle(rippleStyle)); ripple.classList.remove('waves-notransition'); // Scale the ripple rippleStyle['-webkit-transform'] = scale + ' ' + translate; rippleStyle['-moz-transform'] = scale + ' ' + translate; rippleStyle['-ms-transform'] = scale + ' ' + translate; rippleStyle['-o-transform'] = scale + ' ' + translate; rippleStyle.transform = scale + ' ' + translate; rippleStyle.opacity = '1'; var duration = e.type === 'mousemove' ? 2500 : Effect.duration; rippleStyle['-webkit-transition-duration'] = duration + 'ms'; rippleStyle['-moz-transition-duration'] = duration + 'ms'; rippleStyle['-o-transition-duration'] = duration + 'ms'; rippleStyle['transition-duration'] = duration + 'ms'; ripple.setAttribute('style', convertStyle(rippleStyle)); }, hide: function (e, element) { element = element || this; var ripples = element.getElementsByClassName('waves-rippling'); for (var i = 0, len = ripples.length; i < len; i++) { removeRipple(e, element, ripples[i]); } } }; /** * Collection of wrapper for HTML element that only have single tag * like and */ var TagWrapper = { // Wrap tag so it can perform the effect input: function (element) { var parent = element.parentNode; // If input already have parent just pass through if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('waves-effect')) { return; } // Put element class and style to the specified parent var wrapper = document.createElement('i'); wrapper.className = element.className + ' waves-input-wrapper'; element.className = 'waves-button-input'; // Put element as child parent.replaceChild(wrapper, element); wrapper.appendChild(element); // Apply element color and background color to wrapper var elementStyle = window.getComputedStyle(element, null); var color = elementStyle.color; var backgroundColor = elementStyle.backgroundColor; wrapper.setAttribute('style', 'color:' + color + ';background:' + backgroundColor); element.setAttribute('style', 'background-color:rgba(0,0,0,0);'); }, // Wrap tag so it can perform the effect img: function (element) { var parent = element.parentNode; // If input already have parent just pass through if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('waves-effect')) { return; } // Put element as child var wrapper = document.createElement('i'); parent.replaceChild(wrapper, element); wrapper.appendChild(element); } }; /** * Hide the effect and remove the ripple. Must be * a separate function to pass the JSLint... */ function removeRipple(e, el, ripple) { // Check if the ripple still exist if (!ripple) { return; } ripple.classList.remove('waves-rippling'); var relativeX = ripple.getAttribute('data-x'); var relativeY = ripple.getAttribute('data-y'); var scale = ripple.getAttribute('data-scale'); var translate = ripple.getAttribute('data-translate'); // Get delay beetween mousedown and mouse leave var diff = Date.now() - Number(ripple.getAttribute('data-hold')); var delay = 350 - diff; if (delay < 0) { delay = 0; } if (e.type === 'mousemove') { delay = 150; } // Fade out ripple after delay var duration = e.type === 'mousemove' ? 2500 : Effect.duration; setTimeout(function () { var style = { top: relativeY + 'px', left: relativeX + 'px', opacity: '0', // Duration '-webkit-transition-duration': duration + 'ms', '-moz-transition-duration': duration + 'ms', '-o-transition-duration': duration + 'ms', 'transition-duration': duration + 'ms', '-webkit-transform': scale + ' ' + translate, '-moz-transform': scale + ' ' + translate, '-ms-transform': scale + ' ' + translate, '-o-transform': scale + ' ' + translate, 'transform': scale + ' ' + translate }; ripple.setAttribute('style', convertStyle(style)); setTimeout(function () { try { el.removeChild(ripple); } catch (e) { return false; } }, duration); }, delay); } /** * Disable mousedown event for 500ms during and after touch */ var TouchHandler = { /* uses an integer rather than bool so there's no issues with * needing to clear timeouts if another touch event occurred * within the 500ms. Cannot mouseup between touchstart and * touchend, nor in the 500ms after touchend. */ touches: 0, allowEvent: function (e) { var allow = true; if (/^(mousedown|mousemove)$/.test(e.type) && TouchHandler.touches) { allow = false; } return allow; }, registerEvent: function (e) { var eType = e.type; if (eType === 'touchstart') { TouchHandler.touches += 1; // push } else if (/^(touchend|touchcancel)$/.test(eType)) { setTimeout(function () { if (TouchHandler.touches) { TouchHandler.touches -= 1; // pop after 500ms } }, 500); } } }; /** * Delegated click handler for .waves-effect element. * returns null when .waves-effect element not in "click tree" */ function getWavesEffectElement(e) { if (TouchHandler.allowEvent(e) === false) { return null; } var element = null; var target = e.target || e.srcElement; while (target.parentElement !== null) { if (target.classList.contains('waves-effect') && (!(target instanceof SVGElement))) { element = target; break; } target = target.parentElement; } return element; } /** * Bubble the click and show effect if .waves-effect elem was found */ function showEffect(e) { // Disable effect if element has "disabled" property on it // In some cases, the event is not triggered by the current element // if (e.target.getAttribute('disabled') !== null) { // return; // } var element = getWavesEffectElement(e); if (element !== null) { // Make it sure the element has either disabled property, disabled attribute or 'disabled' class if (element.disabled || element.getAttribute('disabled') || element.classList.contains('disabled')) { return; } TouchHandler.registerEvent(e); if (e.type === 'touchstart' && Effect.delay) { var hidden = false; var timer = setTimeout(function () { timer = null; Effect.show(e, element); }, Effect.delay); var hideEffect = function (hideEvent) { // if touch hasn't moved, and effect not yet started: start effect now if (timer) { clearTimeout(timer); timer = null; Effect.show(e, element); } if (!hidden) { hidden = true; Effect.hide(hideEvent, element); } }; var touchMove = function (moveEvent) { if (timer) { clearTimeout(timer); timer = null; } hideEffect(moveEvent); }; element.addEventListener('touchmove', touchMove, false); element.addEventListener('touchend', hideEffect, false); element.addEventListener('touchcancel', hideEffect, false); } else { Effect.show(e, element); if (isTouchAvailable) { element.addEventListener('touchend', Effect.hide, false); element.addEventListener('touchcancel', Effect.hide, false); } element.addEventListener('mouseup', Effect.hide, false); element.addEventListener('mouseleave', Effect.hide, false); } } } Waves.init = function (options) { var body = document.body; options = options || {}; if ('duration' in options) { Effect.duration = options.duration; } if ('delay' in options) { Effect.delay = options.delay; } if (isTouchAvailable) { body.addEventListener('touchstart', showEffect, false); body.addEventListener('touchcancel', TouchHandler.registerEvent, false); body.addEventListener('touchend', TouchHandler.registerEvent, false); } body.addEventListener('mousedown', showEffect, false); }; /** * Attach Waves to dynamically loaded inputs, or add .waves-effect and other * waves classes to a set of elements. Set drag to true if the ripple mouseover * or skimming effect should be applied to the elements. */ Waves.attach = function (elements, classes) { elements = getWavesElements(elements); if (toString.call(classes) === '[object Array]') { classes = classes.join(' '); } classes = classes ? ' ' + classes : ''; var element, tagName; for (var i = 0, len = elements.length; i < len; i++) { element = elements[i]; tagName = element.tagName.toLowerCase(); if (['input', 'img'].indexOf(tagName) !== -1) { TagWrapper[tagName](element); element = element.parentElement; } if (element.className.indexOf('waves-effect') === -1) { element.className += ' waves-effect' + classes; } } }; /** * Cause a ripple to appear in an element via code. */ Waves.ripple = function (elements, options) { elements = getWavesElements(elements); var elementsLen = elements.length; options = options || {}; options.wait = options.wait || 0; options.position = options.position || null; // default = centre of element if (elementsLen) { var element, pos, off, centre = {}, i = 0; var mousedown = { type: 'mousedown', button: 1 }; var hideRipple = function (mouseup, element) { return function () { Effect.hide(mouseup, element); }; }; for (; i < elementsLen; i++) { element = elements[i]; pos = options.position || { x: element.clientWidth / 2, y: element.clientHeight / 2 }; off = offset(element); centre.x = off.left + pos.x; centre.y = off.top + pos.y; mousedown.pageX = centre.x; mousedown.pageY = centre.y; Effect.show(mousedown, element); if (options.wait >= 0 && options.wait !== null) { var mouseup = { type: 'mouseup', button: 1 }; setTimeout(hideRipple(mouseup, element), options.wait); } } } }; /** * Remove all ripples from an element. */ Waves.calm = function (elements) { elements = getWavesElements(elements); var mouseup = { type: 'mouseup', button: 1 }; for (var i = 0, len = elements.length; i < len; i++) { Effect.hide(mouseup, elements[i]); } }; /** * Deprecated API fallback */ Waves.displayEffect = function (options) { console.error('Waves.displayEffect() has been deprecated and will be removed in future version. Please use Waves.init() to initialize Waves effect'); Waves.init(options); }; return Waves; }); //Initialization Waves.attach('.btn, .btn-floating', ['waves-light']); Waves.attach('.waves-light', ['waves-light']); Waves.attach('.navbar-nav a:not(.navbar-brand), .nav-icons li a, .navbar form, .nav-tabs .nav-item', ['waves-light']); Waves.attach('.pager li a', ['waves-light']); Waves.attach('.pagination .page-item .page-link', ['waves-effect']); Waves.init();/* FORMS */ (function ($) { $(document).ready(function () { // Function to update labels of text fields Materialize.updateTextFields = function () { var input_selector = 'input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search], textarea'; $(input_selector).each(function (index, element) { if ($(element).val().length > 0 || $(this).attr('placeholder') !== undefined || $(element)[0].validity.badInput === true) { $(this).siblings('label, i').addClass('active'); } else { $(this).siblings('label, i').removeClass('active'); } }); }; // Text based inputs var input_selector = 'input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search], textarea'; // Handle HTML5 autofocus $('input[autofocus]').siblings('label, i').addClass('active'); // Add active if form auto complete $(document).on('change', input_selector, function () { if ($(this).val().length !== 0 || $(this).attr('placeholder') !== undefined) { $(this).siblings('label, i').addClass('active'); } validate_field($(this)); }); // Add active if input element has been pre-populated on document ready $(document).ready(function () { Materialize.updateTextFields(); }); // HTML DOM FORM RESET handling $(document).on('reset', function (e) { var formReset = $(e.target); if (formReset.is('form')) { formReset.find(input_selector).removeClass('valid').removeClass('invalid'); formReset.find(input_selector).each(function () { if ($(this).attr('value') === '') { $(this).siblings('label, i').removeClass('active'); } }); // Reset select formReset.find('select.initialized').each(function () { var reset_text = formReset.find('option[selected]').text(); formReset.siblings('input.select-dropdown').val(reset_text); }); } }); // Add active when element has focus $(document).on('focus', input_selector, function () { $(this).siblings('label, i').addClass('active'); }); $(document).on('blur', input_selector, function () { var $inputElement = $(this); if ($inputElement.val().length === 0 && $inputElement[0].validity.badInput !== true && $inputElement.attr('placeholder') === undefined) { $inputElement.siblings('label, i').removeClass('active'); } validate_field($inputElement); }); validate_field = function (object) { var hasLength = object.attr('length') !== undefined; var lenAttr = parseInt(object.attr('length')); var len = object.val().length; if (object.val().length === 0 && object[0].validity.badInput === false) { if (object.hasClass('validate')) { object.removeClass('valid'); object.removeClass('invalid'); } } else { if (object.hasClass('validate')) { // Check for character counter attributes if ((object.is(':valid') && hasLength && (len < lenAttr)) || (object.is(':valid') && !hasLength)) { object.removeClass('invalid'); object.addClass('valid'); } else { object.removeClass('valid'); object.addClass('invalid'); } } } }; // Textarea Auto Resize var hiddenDiv = $('.hiddendiv').first(); if (!hiddenDiv.length) { hiddenDiv = $('
'); $('body').append(hiddenDiv); } var text_area_selector = '.materialize-textarea'; function textareaAutoResize($textarea) { // Set font properties of hiddenDiv var fontFamily = $textarea.css('font-family'); var fontSize = $textarea.css('font-size'); if (fontSize) { hiddenDiv.css('font-size', fontSize); } if (fontFamily) { hiddenDiv.css('font-family', fontFamily); } if ($textarea.attr('wrap') === "off") { hiddenDiv.css('overflow-wrap', "normal") .css('white-space', "pre"); } hiddenDiv.text($textarea.val() + '\n'); var content = hiddenDiv.html().replace(/\n/g, '
'); hiddenDiv.html(content); // When textarea is hidden, width goes crazy. // Approximate with half of window size if ($textarea.is(':visible')) { hiddenDiv.css('width', $textarea.width()); } else { hiddenDiv.css('width', $(window).width() / 2); } $textarea.css('height', hiddenDiv.height()); } $(text_area_selector).each(function () { var $textarea = $(this); if ($textarea.val().length) { textareaAutoResize($textarea); } }); $('body').on('keyup keydown', text_area_selector, function () { textareaAutoResize($(this)); }); }); // End of $(document).ready }(jQuery));/* Enhanced Bootstrap Modals https://mdbootstrap.com office@mdbootstrap.com */ $('body').on('shown.bs.modal', '.modal', function() { if($('.modal-backdrop').length) { } else { $modal_dialog = $(this).children('.modal-dialog') if($modal_dialog.hasClass('modal-side')) { $(this).addClass('modal-scrolling'); $('body').addClass('scrollable'); } if($modal_dialog.hasClass('modal-frame')) { $(this).addClass('modal-content-clickable'); $('body').addClass('scrollable'); } } }); $('body').on('hidden.bs.modal', '.modal', function() { $('body').removeClass('scrollable'); });