chart 0.1.3.1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8df287d1c74c758bf8f3c0fc7e07daa09845bf57
4
- data.tar.gz: 18c97d6d4b2abf8a73686affe6ab2c223d5d6376
3
+ metadata.gz: 43f956593a4aa2a7c9d00a5557249de8a2d9651f
4
+ data.tar.gz: 6e3965a9237cb6120b765f01c021ca27790bcdd5
5
5
  SHA512:
6
- metadata.gz: 152d5ce707d08038bca13b7dc5daafab07857093b2ca692980c4f29a4a1a17cb0b475cb225adb787ce6b5666f269273c7704d7874c043491e6ee15618824a8a8
7
- data.tar.gz: c8ada08d414e99ef18f08d24dd1bb6bcea6d5ecd22cabd2e2cbb2e8716a777afefe5fc89d722aaa2829b26d172fd9b94d00a3b6e69a523184cfa8ad8b66cf75e
6
+ metadata.gz: 0b5a26c959f786da4d2507d8678d2e0a244b81e8f09c111fa444328d55f3a17eca397bf36223ae024cd55447837cad286429243106368b366cd2269c3afce338
7
+ data.tar.gz: 48c7b1ca31adf34fb5ef0789d30eef310fe81ff047a4c2c1606d897608a8a87e2e4c242997da97f9f0268f703bdc70123ccedcc964a7b8876b33588d3097cc69
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 Bryan Lim
3
+ Copyright (c) 2015 Bryan Lim (@ytbryan)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
-
data/README.md CHANGED
@@ -68,7 +68,7 @@ At Application.css, write `*= require nvd3`
68
68
 
69
69
  Chart ships with the following awesome charts:
70
70
 
71
- * [ChartJS](https://github.com/nnnick/Chart.js/) (1.02)
71
+ * [ChartJS](https://github.com/nnnick/Chart.js/) (1.10)
72
72
  * [NVD3](https://github.com/novus/nvd3) (1.8.2)
73
73
  * [Google Chart](https://developers.google.com/chart/)
74
74
  * [Highchart](http://www.highcharts.com) (4.2.3)
@@ -1,3 +1,3 @@
1
1
  module Chart
2
- VERSION = "0.1.3.1"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -1,11 +1,3738 @@
1
1
  /*!
2
2
  * Chart.js
3
3
  * http://chartjs.org/
4
- * Version: 1.0.2
4
+ * Version: 1.1.0
5
5
  *
6
6
  * Copyright 2015 Nick Downie
7
7
  * Released under the MIT license
8
8
  * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
9
9
  */
10
- (function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n<t.length;n++)i.apply(e,[t[n],n].concat(s))}else for(var o in t)i.apply(e,[t[o],o].concat(s))},o=s.clone=function(t){var i={};return n(t,function(e,s){t.hasOwnProperty(s)&&(i[s]=e)}),i},a=s.extend=function(t){return n(Array.prototype.slice.call(arguments,1),function(i){n(i,function(e,s){i.hasOwnProperty(s)&&(t[s]=e)})}),t},h=s.merge=function(){var t=Array.prototype.slice.call(arguments,0);return t.unshift({}),a.apply(null,t)},l=s.indexOf=function(t,i){if(Array.prototype.indexOf)return t.indexOf(i);for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1},r=(s.where=function(t,i){var e=[];return s.each(t,function(t){i(t)&&e.push(t)}),e},s.findNextWhere=function(t,i,e){e||(e=-1);for(var s=e+1;s<t.length;s++){var n=t[s];if(i(n))return n}},s.findPreviousWhere=function(t,i,e){e||(e=t.length);for(var s=e-1;s>=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--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){return(t/=.5)<1?.5*t*t*t:.5*((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){return(t/=.5)<1?.5*t*t*t*t:-0.5*((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){return(t/=.5)<1?.5*t*t*t*t*t:.5*((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-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),-(s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)))},easeOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),s*Math.pow(2,-10*t)*Math.sin(2*(1*t-i)*Math.PI/e)+1)},easeInOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:2==(t/=.5)?1:(e||(e=.3*1.5),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),1>t?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.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:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.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:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)<Math.pow(e,2)},draw:function(){if(this.display){var t=this.ctx;t.beginPath(),t.arc(this.x,this.y,this.radius,0,2*Math.PI),t.closePath(),t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.fillStyle=this.fillColor,t.fill(),t.stroke()}}}),e.Arc=e.Element.extend({inRange:function(t,i){var e=s.getAngleFromPoint(this,{x:t,y:i}),n=e.angle>=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),t<this.yLabelWidth&&this.calculateXLabelRotation()},calculateXLabelRotation:function(){this.ctx.font=this.font;var t,i,e=this.ctx.measureText(this.xLabels[0]).width,s=this.ctx.measureText(this.xLabels[this.xLabels.length-1]).width;if(this.xScalePaddingRight=s/2+3,this.xScalePaddingLeft=e/2>this.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;i<this.valuesCount;i++)t=this.getPointPosition(i,d),e=this.ctx.measureText(C(this.templateString,{value:this.labels[i]})).width+5,0===i||i===this.valuesCount/2?(s=e/2,t.x+s>p&&(p=t.x+s,n=i),t.x-s<g&&(g=t.x-s,a=i)):i<this.valuesCount/2?t.x+e>p&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e<g&&(g=t.x-e,a=i);l=g,r=Math.ceil(p-this.width),o=this.getIndexAngle(n),h=this.getIndexAngle(a),c=r/Math.sin(o+Math.PI/2),u=l/Math.sin(h+Math.PI/2),c=f(c)?c:0,u=f(u)?u:0,this.drawingArea=d-(u+c)/2,this.setCenterPoint(u,c)},setCenterPoint:function(t,i){var e=this.width-i-this.drawingArea,s=t+this.drawingArea;this.xCenter=(s+e)/2,this.yCenter=this.height/2},getIndexAngle:function(t){var i=2*Math.PI/this.valuesCount;return t*i-Math.PI/2},getPointPosition:function(t,i){var e=this.getIndexAngle(t);return{x:Math.cos(e)*i+this.xCenter,y:Math.sin(e)*i+this.yCenter}},draw:function(){if(this.display){var t=this.ctx;if(n(this.yLabels,function(i,e){if(e>0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a<this.valuesCount;a++)s=this.getPointPosition(a,this.calculateCenterOffset(this.min+e*this.stepValue)),0===a?t.moveTo(s.x,s.y):t.lineTo(s.x,s.y);t.closePath(),t.stroke()}if(this.showLabels){if(t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.showLabelBackdrop){var h=t.measureText(i).width;t.fillStyle=this.backdropColor,t.fillRect(this.xCenter-h/2-this.backdropPaddingX,o-this.fontSize/2-this.backdropPaddingY,h+2*this.backdropPaddingX,this.fontSize+2*this.backdropPaddingY)}t.textAlign="center",t.textBaseline="middle",t.fillStyle=this.fontColor,t.fillText(i,this.xCenter,o)}}},this),!this.lineArc){t.lineWidth=this.angleLineWidth,t.strokeStyle=this.angleLineColor;for(var i=this.valuesCount-1;i>=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<this.datasets.length;a++)for(i=0;i<this.datasets[a].bars.length;i++)if(this.datasets[a].bars[i].inRange(n.x,n.y))return e.each(this.datasets,o),s;return s},buildScale:function(t){var i=this,s=function(){var t=[];return i.eachBars(function(i){t.push(i.value)}),t},n={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:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(s(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.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};this.options.scaleOverride&&e.extend(n,{calculateYRange:e.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(n)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].bars.push(new this.BarClass({value:t,label:i,x:this.scale.calculateBarX(this.datasets.length,e,this.scale.valuesCount+1),y:this.scale.endPoint,width:this.scale.calculateBarWidth(this.datasets.length),base:this.scale.endPoint,strokeColor:this.datasets[e].strokeColor,fillColor:this.datasets[e].fillColor}))
11
- },this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.bars.shift()},this),this.update()},reflow:function(){e.extend(this.BarClass.prototype,{y:this.scale.endPoint,base:this.scale.endPoint});var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();this.chart.ctx;this.scale.draw(i),e.each(this.datasets,function(t,s){e.each(t.bars,function(t,e){t.hasValue()&&(t.base=this.scale.endPoint,t.transition({x:this.scale.calculateBarX(this.datasets.length,s,e),y:this.scale.calculateY(t.value),width:this.scale.calculateBarWidth(this.datasets.length)},i).draw())},this)},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle)},this)}}),i.types.Doughnut.extend({name:"Pie",defaults:e.merge(s,{percentageInnerCutout:0})})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,bezierCurve:!0,bezierCurveTension:.4,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)<Math.pow(this.radius+this.hitDetectionRadius,2)}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this),this.buildScale(t.labels),this.eachPoints(function(t,i){e.extend(t,{x:this.scale.calculateX(i),y:this.scale.endPoint}),t.save()},this)},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachPoints(function(t){t.save()}),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.datasets,function(t){e.each(t.points,function(t){t.inRange(s.x,s.y)&&i.push(t)})},this),i},buildScale:function(t){var s=this,n=function(){var t=[];return s.eachPoints(function(i){t.push(i.value)}),t},o={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:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(n(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.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};this.options.scaleOverride&&e.extend(o,{calculateYRange:e.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 i.Scale(o)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:this.scale.calculateX(this.scale.valuesCount+1),y:this.scale.endPoint,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.points.shift()},this),this.update()},reflow:function(){var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();var s=this.chart.ctx,n=function(t){return null!==t.value},o=function(t,i,s){return e.findNextWhere(i,n,s)||t},a=function(t,i,s){return e.findPreviousWhere(i,n,s)||t};this.scale.draw(i),e.each(this.datasets,function(t){var h=e.where(t.points,n);e.each(t.points,function(t,e){t.hasValue()&&t.transition({y:this.scale.calculateY(t.value),x:this.scale.calculateX(e)},i)},this),this.options.bezierCurve&&e.each(h,function(t,i){var s=i>0&&i<h.length-1?this.options.bezierCurveTension:0;t.controlPoints=e.splineCurve(a(t,h,i),t,o(t,h,i),s),t.controlPoints.outer.y>this.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.y<this.scale.startPoint&&(t.controlPoints.outer.y=this.scale.startPoint),t.controlPoints.inner.y>this.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y<this.scale.startPoint&&(t.controlPoints.inner.y=this.scale.startPoint)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(h,function(t,i){if(0===i)s.moveTo(t.x,t.y);else if(this.options.bezierCurve){var e=a(t,h,i);s.bezierCurveTo(e.controlPoints.outer.x,e.controlPoints.outer.y,t.controlPoints.inner.x,t.controlPoints.inner.y,t.x,t.y)}else s.lineTo(t.x,t.y)},this),s.stroke(),this.options.datasetFill&&h.length>0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.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 i.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:!0,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:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=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}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle),t.draw()},this),this.scale.draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.Type.extend({name:"Radar",defaults:{scaleShowLine:!0,angleShowLineOut:!0,scaleShowLabels:!1,scaleBeginAtZero:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:10,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'},initialize:function(t){this.PointClass=i.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(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,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:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=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}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.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(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this);
10
+
11
+
12
+ (function(){
13
+
14
+ "use strict";
15
+
16
+ //Declare root variable - window in the browser, global on the server
17
+ var root = this,
18
+ previous = root.Chart;
19
+
20
+ //Occupy the global variable of Chart, and create a simple base class
21
+ var Chart = function(context){
22
+ var chart = this;
23
+ this.canvas = context.canvas;
24
+
25
+ this.ctx = context;
26
+
27
+ //Variables global to the chart
28
+ var computeDimension = function(element,dimension)
29
+ {
30
+ if (element['offset'+dimension])
31
+ {
32
+ return element['offset'+dimension];
33
+ }
34
+ else
35
+ {
36
+ return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
37
+ }
38
+ };
39
+
40
+ var width = this.width = computeDimension(context.canvas,'Width') || context.canvas.width;
41
+ var height = this.height = computeDimension(context.canvas,'Height') || context.canvas.height;
42
+
43
+ width = this.width = context.canvas.width;
44
+ height = this.height = context.canvas.height;
45
+ this.aspectRatio = this.width / this.height;
46
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
47
+ helpers.retinaScale(this);
48
+
49
+ return this;
50
+ };
51
+ //Globally expose the defaults to allow for user updating/changing
52
+ Chart.defaults = {
53
+ global: {
54
+ // Boolean - Whether to animate the chart
55
+ animation: true,
56
+
57
+ // Number - Number of animation steps
58
+ animationSteps: 60,
59
+
60
+ // String - Animation easing effect
61
+ animationEasing: "easeOutQuart",
62
+
63
+ // Boolean - If we should show the scale at all
64
+ showScale: true,
65
+
66
+ // Boolean - If we want to override with a hard coded scale
67
+ scaleOverride: false,
68
+
69
+ // ** Required if scaleOverride is true **
70
+ // Number - The number of steps in a hard coded scale
71
+ scaleSteps: null,
72
+ // Number - The value jump in the hard coded scale
73
+ scaleStepWidth: null,
74
+ // Number - The scale starting value
75
+ scaleStartValue: null,
76
+
77
+ // String - Colour of the scale line
78
+ scaleLineColor: "rgba(0,0,0,.1)",
79
+
80
+ // Number - Pixel width of the scale line
81
+ scaleLineWidth: 1,
82
+
83
+ // Boolean - Whether to show labels on the scale
84
+ scaleShowLabels: true,
85
+
86
+ // Interpolated JS string - can access value
87
+ scaleLabel: "<%=value%>",
88
+
89
+ // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
90
+ scaleIntegersOnly: true,
91
+
92
+ // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
93
+ scaleBeginAtZero: false,
94
+
95
+ // String - Scale label font declaration for the scale label
96
+ scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
97
+
98
+ // Number - Scale label font size in pixels
99
+ scaleFontSize: 12,
100
+
101
+ // String - Scale label font weight style
102
+ scaleFontStyle: "normal",
103
+
104
+ // String - Scale label font colour
105
+ scaleFontColor: "#666",
106
+
107
+ // Boolean - whether or not the chart should be responsive and resize when the browser does.
108
+ responsive: false,
109
+
110
+ // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
111
+ maintainAspectRatio: true,
112
+
113
+ // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
114
+ showTooltips: true,
115
+
116
+ // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
117
+ customTooltips: false,
118
+
119
+ // Array - Array of string names to attach tooltip events
120
+ tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
121
+
122
+ // String - Tooltip background colour
123
+ tooltipFillColor: "rgba(0,0,0,0.8)",
124
+
125
+ // String - Tooltip label font declaration for the scale label
126
+ tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
127
+
128
+ // Number - Tooltip label font size in pixels
129
+ tooltipFontSize: 14,
130
+
131
+ // String - Tooltip font weight style
132
+ tooltipFontStyle: "normal",
133
+
134
+ // String - Tooltip label font colour
135
+ tooltipFontColor: "#fff",
136
+
137
+ // String - Tooltip title font declaration for the scale label
138
+ tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
139
+
140
+ // Number - Tooltip title font size in pixels
141
+ tooltipTitleFontSize: 14,
142
+
143
+ // String - Tooltip title font weight style
144
+ tooltipTitleFontStyle: "bold",
145
+
146
+ // String - Tooltip title font colour
147
+ tooltipTitleFontColor: "#fff",
148
+
149
+ // String - Tooltip title template
150
+ tooltipTitleTemplate: "<%= label%>",
151
+
152
+ // Number - pixel width of padding around tooltip text
153
+ tooltipYPadding: 6,
154
+
155
+ // Number - pixel width of padding around tooltip text
156
+ tooltipXPadding: 6,
157
+
158
+ // Number - Size of the caret on the tooltip
159
+ tooltipCaretSize: 8,
160
+
161
+ // Number - Pixel radius of the tooltip border
162
+ tooltipCornerRadius: 6,
163
+
164
+ // Number - Pixel offset from point x to tooltip edge
165
+ tooltipXOffset: 10,
166
+
167
+ // String - Template string for single tooltips
168
+ tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
169
+
170
+ // String - Template string for single tooltips
171
+ multiTooltipTemplate: "<%= datasetLabel %>: <%= value %>",
172
+
173
+ // String - Colour behind the legend colour block
174
+ multiTooltipKeyBackground: '#fff',
175
+
176
+ // Array - A list of colors to use as the defaults
177
+ segmentColorDefault: ["#A6CEE3", "#1F78B4", "#B2DF8A", "#33A02C", "#FB9A99", "#E31A1C", "#FDBF6F", "#FF7F00", "#CAB2D6", "#6A3D9A", "#B4B482", "#B15928" ],
178
+
179
+ // Array - A list of highlight colors to use as the defaults
180
+ segmentHighlightColorDefaults: [ "#CEF6FF", "#47A0DC", "#DAFFB2", "#5BC854", "#FFC2C1", "#FF4244", "#FFE797", "#FFA728", "#F2DAFE", "#9265C2", "#DCDCAA", "#D98150" ],
181
+
182
+ // Function - Will fire on animation progression.
183
+ onAnimationProgress: function(){},
184
+
185
+ // Function - Will fire on animation completion.
186
+ onAnimationComplete: function(){}
187
+
188
+ }
189
+ };
190
+
191
+ //Create a dictionary of chart types, to allow for extension of existing types
192
+ Chart.types = {};
193
+
194
+ //Global Chart helpers object for utility methods and classes
195
+ var helpers = Chart.helpers = {};
196
+
197
+ //-- Basic js utility methods
198
+ var each = helpers.each = function(loopable,callback,self){
199
+ var additionalArgs = Array.prototype.slice.call(arguments, 3);
200
+ // Check to see if null or undefined firstly.
201
+ if (loopable){
202
+ if (loopable.length === +loopable.length){
203
+ var i;
204
+ for (i=0; i<loopable.length; i++){
205
+ callback.apply(self,[loopable[i], i].concat(additionalArgs));
206
+ }
207
+ }
208
+ else{
209
+ for (var item in loopable){
210
+ callback.apply(self,[loopable[item],item].concat(additionalArgs));
211
+ }
212
+ }
213
+ }
214
+ },
215
+ clone = helpers.clone = function(obj){
216
+ var objClone = {};
217
+ each(obj,function(value,key){
218
+ if (obj.hasOwnProperty(key)){
219
+ objClone[key] = value;
220
+ }
221
+ });
222
+ return objClone;
223
+ },
224
+ extend = helpers.extend = function(base){
225
+ each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
226
+ each(extensionObject,function(value,key){
227
+ if (extensionObject.hasOwnProperty(key)){
228
+ base[key] = value;
229
+ }
230
+ });
231
+ });
232
+ return base;
233
+ },
234
+ merge = helpers.merge = function(base,master){
235
+ //Merge properties in left object over to a shallow clone of object right.
236
+ var args = Array.prototype.slice.call(arguments,0);
237
+ args.unshift({});
238
+ return extend.apply(null, args);
239
+ },
240
+ indexOf = helpers.indexOf = function(arrayToSearch, item){
241
+ if (Array.prototype.indexOf) {
242
+ return arrayToSearch.indexOf(item);
243
+ }
244
+ else{
245
+ for (var i = 0; i < arrayToSearch.length; i++) {
246
+ if (arrayToSearch[i] === item) return i;
247
+ }
248
+ return -1;
249
+ }
250
+ },
251
+ where = helpers.where = function(collection, filterCallback){
252
+ var filtered = [];
253
+
254
+ helpers.each(collection, function(item){
255
+ if (filterCallback(item)){
256
+ filtered.push(item);
257
+ }
258
+ });
259
+
260
+ return filtered;
261
+ },
262
+ findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
263
+ // Default to start of the array
264
+ if (!startIndex){
265
+ startIndex = -1;
266
+ }
267
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
268
+ var currentItem = arrayToSearch[i];
269
+ if (filterCallback(currentItem)){
270
+ return currentItem;
271
+ }
272
+ }
273
+ },
274
+ findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
275
+ // Default to end of the array
276
+ if (!startIndex){
277
+ startIndex = arrayToSearch.length;
278
+ }
279
+ for (var i = startIndex - 1; i >= 0; i--) {
280
+ var currentItem = arrayToSearch[i];
281
+ if (filterCallback(currentItem)){
282
+ return currentItem;
283
+ }
284
+ }
285
+ },
286
+ inherits = helpers.inherits = function(extensions){
287
+ //Basic javascript inheritance based on the model created in Backbone.js
288
+ var parent = this;
289
+ var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
290
+
291
+ var Surrogate = function(){ this.constructor = ChartElement;};
292
+ Surrogate.prototype = parent.prototype;
293
+ ChartElement.prototype = new Surrogate();
294
+
295
+ ChartElement.extend = inherits;
296
+
297
+ if (extensions) extend(ChartElement.prototype, extensions);
298
+
299
+ ChartElement.__super__ = parent.prototype;
300
+
301
+ return ChartElement;
302
+ },
303
+ noop = helpers.noop = function(){},
304
+ uid = helpers.uid = (function(){
305
+ var id=0;
306
+ return function(){
307
+ return "chart-" + id++;
308
+ };
309
+ })(),
310
+ warn = helpers.warn = function(str){
311
+ //Method for warning of errors
312
+ if (window.console && typeof window.console.warn === "function") console.warn(str);
313
+ },
314
+ amd = helpers.amd = (typeof define === 'function' && define.amd),
315
+ //-- Math methods
316
+ isNumber = helpers.isNumber = function(n){
317
+ return !isNaN(parseFloat(n)) && isFinite(n);
318
+ },
319
+ max = helpers.max = function(array){
320
+ return Math.max.apply( Math, array );
321
+ },
322
+ min = helpers.min = function(array){
323
+ return Math.min.apply( Math, array );
324
+ },
325
+ cap = helpers.cap = function(valueToCap,maxValue,minValue){
326
+ if(isNumber(maxValue)) {
327
+ if( valueToCap > maxValue ) {
328
+ return maxValue;
329
+ }
330
+ }
331
+ else if(isNumber(minValue)){
332
+ if ( valueToCap < minValue ){
333
+ return minValue;
334
+ }
335
+ }
336
+ return valueToCap;
337
+ },
338
+ getDecimalPlaces = helpers.getDecimalPlaces = function(num){
339
+ if (num%1!==0 && isNumber(num)){
340
+ var s = num.toString();
341
+ if(s.indexOf("e-") < 0){
342
+ // no exponent, e.g. 0.01
343
+ return s.split(".")[1].length;
344
+ }
345
+ else if(s.indexOf(".") < 0) {
346
+ // no decimal point, e.g. 1e-9
347
+ return parseInt(s.split("e-")[1]);
348
+ }
349
+ else {
350
+ // exponent and decimal point, e.g. 1.23e-9
351
+ var parts = s.split(".")[1].split("e-");
352
+ return parts[0].length + parseInt(parts[1]);
353
+ }
354
+ }
355
+ else {
356
+ return 0;
357
+ }
358
+ },
359
+ toRadians = helpers.radians = function(degrees){
360
+ return degrees * (Math.PI/180);
361
+ },
362
+ // Gets the angle from vertical upright to the point about a centre.
363
+ getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
364
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
365
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
366
+ radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
367
+
368
+
369
+ var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
370
+
371
+ //If the segment is in the top left quadrant, we need to add another rotation to the angle
372
+ if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
373
+ angle += Math.PI*2;
374
+ }
375
+
376
+ return {
377
+ angle: angle,
378
+ distance: radialDistanceFromCenter
379
+ };
380
+ },
381
+ aliasPixel = helpers.aliasPixel = function(pixelWidth){
382
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
383
+ },
384
+ splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
385
+ //Props to Rob Spencer at scaled innovation for his post on splining between points
386
+ //http://scaledinnovation.com/analytics/splines/aboutSplines.html
387
+ var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
388
+ d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
389
+ fa=t*d01/(d01+d12),// scaling factor for triangle Ta
390
+ fb=t*d12/(d01+d12);
391
+ return {
392
+ inner : {
393
+ x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
394
+ y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
395
+ },
396
+ outer : {
397
+ x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
398
+ y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
399
+ }
400
+ };
401
+ },
402
+ calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
403
+ return Math.floor(Math.log(val) / Math.LN10);
404
+ },
405
+ calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
406
+
407
+ //Set a minimum step of two - a point at the top of the graph, and a point at the base
408
+ var minSteps = 2,
409
+ maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
410
+ skipFitting = (minSteps >= maxSteps);
411
+
412
+ // Filter out null values since these would min() to zero
413
+ var values = [];
414
+ each(valuesArray, function( v ){
415
+ v == null || values.push( v );
416
+ });
417
+ var minValue = min(values),
418
+ maxValue = max(values);
419
+
420
+ // We need some degree of separation here to calculate the scales if all the values are the same
421
+ // Adding/minusing 0.5 will give us a range of 1.
422
+ if (maxValue === minValue){
423
+ maxValue += 0.5;
424
+ // So we don't end up with a graph with a negative start value if we've said always start from zero
425
+ if (minValue >= 0.5 && !startFromZero){
426
+ minValue -= 0.5;
427
+ }
428
+ else{
429
+ // Make up a whole number above the values
430
+ maxValue += 0.5;
431
+ }
432
+ }
433
+
434
+ var valueRange = Math.abs(maxValue - minValue),
435
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
436
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
437
+ graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
438
+ graphRange = graphMax - graphMin,
439
+ stepValue = Math.pow(10, rangeOrderOfMagnitude),
440
+ numberOfSteps = Math.round(graphRange / stepValue);
441
+
442
+ //If we have more space on the graph we'll use it to give more definition to the data
443
+ while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
444
+ if(numberOfSteps > maxSteps){
445
+ stepValue *=2;
446
+ numberOfSteps = Math.round(graphRange/stepValue);
447
+ // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
448
+ if (numberOfSteps % 1 !== 0){
449
+ skipFitting = true;
450
+ }
451
+ }
452
+ //We can fit in double the amount of scale points on the scale
453
+ else{
454
+ //If user has declared ints only, and the step value isn't a decimal
455
+ if (integersOnly && rangeOrderOfMagnitude >= 0){
456
+ //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
457
+ if(stepValue/2 % 1 === 0){
458
+ stepValue /=2;
459
+ numberOfSteps = Math.round(graphRange/stepValue);
460
+ }
461
+ //If it would make it a float break out of the loop
462
+ else{
463
+ break;
464
+ }
465
+ }
466
+ //If the scale doesn't have to be an int, make the scale more granular anyway.
467
+ else{
468
+ stepValue /=2;
469
+ numberOfSteps = Math.round(graphRange/stepValue);
470
+ }
471
+
472
+ }
473
+ }
474
+
475
+ if (skipFitting){
476
+ numberOfSteps = minSteps;
477
+ stepValue = graphRange / numberOfSteps;
478
+ }
479
+
480
+ return {
481
+ steps : numberOfSteps,
482
+ stepValue : stepValue,
483
+ min : graphMin,
484
+ max : graphMin + (numberOfSteps * stepValue)
485
+ };
486
+
487
+ },
488
+ /* jshint ignore:start */
489
+ // Blows up jshint errors based on the new Function constructor
490
+ //Templating methods
491
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
492
+ template = helpers.template = function(templateString, valuesObject){
493
+
494
+ // If templateString is function rather than string-template - call the function for valuesObject
495
+
496
+ if(templateString instanceof Function){
497
+ return templateString(valuesObject);
498
+ }
499
+
500
+ var cache = {};
501
+ function tmpl(str, data){
502
+ // Figure out if we're getting a template, or if we need to
503
+ // load the template - and be sure to cache the result.
504
+ var fn = !/\W/.test(str) ?
505
+ cache[str] = cache[str] :
506
+
507
+ // Generate a reusable function that will serve as a template
508
+ // generator (and which will be cached).
509
+ new Function("obj",
510
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
511
+
512
+ // Introduce the data as local variables using with(){}
513
+ "with(obj){p.push('" +
514
+
515
+ // Convert the template into pure JavaScript
516
+ str
517
+ .replace(/[\r\t\n]/g, " ")
518
+ .split("<%").join("\t")
519
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
520
+ .replace(/\t=(.*?)%>/g, "',$1,'")
521
+ .split("\t").join("');")
522
+ .split("%>").join("p.push('")
523
+ .split("\r").join("\\'") +
524
+ "');}return p.join('');"
525
+ );
526
+
527
+ // Provide some basic currying to the user
528
+ return data ? fn( data ) : fn;
529
+ }
530
+ return tmpl(templateString,valuesObject);
531
+ },
532
+ /* jshint ignore:end */
533
+ generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
534
+ var labelsArray = new Array(numberOfSteps);
535
+ if (templateString){
536
+ each(labelsArray,function(val,index){
537
+ labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
538
+ });
539
+ }
540
+ return labelsArray;
541
+ },
542
+ //--Animation methods
543
+ //Easing functions adapted from Robert Penner's easing equations
544
+ //http://www.robertpenner.com/easing/
545
+ easingEffects = helpers.easingEffects = {
546
+ linear: function (t) {
547
+ return t;
548
+ },
549
+ easeInQuad: function (t) {
550
+ return t * t;
551
+ },
552
+ easeOutQuad: function (t) {
553
+ return -1 * t * (t - 2);
554
+ },
555
+ easeInOutQuad: function (t) {
556
+ if ((t /= 1 / 2) < 1){
557
+ return 1 / 2 * t * t;
558
+ }
559
+ return -1 / 2 * ((--t) * (t - 2) - 1);
560
+ },
561
+ easeInCubic: function (t) {
562
+ return t * t * t;
563
+ },
564
+ easeOutCubic: function (t) {
565
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
566
+ },
567
+ easeInOutCubic: function (t) {
568
+ if ((t /= 1 / 2) < 1){
569
+ return 1 / 2 * t * t * t;
570
+ }
571
+ return 1 / 2 * ((t -= 2) * t * t + 2);
572
+ },
573
+ easeInQuart: function (t) {
574
+ return t * t * t * t;
575
+ },
576
+ easeOutQuart: function (t) {
577
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
578
+ },
579
+ easeInOutQuart: function (t) {
580
+ if ((t /= 1 / 2) < 1){
581
+ return 1 / 2 * t * t * t * t;
582
+ }
583
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
584
+ },
585
+ easeInQuint: function (t) {
586
+ return 1 * (t /= 1) * t * t * t * t;
587
+ },
588
+ easeOutQuint: function (t) {
589
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
590
+ },
591
+ easeInOutQuint: function (t) {
592
+ if ((t /= 1 / 2) < 1){
593
+ return 1 / 2 * t * t * t * t * t;
594
+ }
595
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
596
+ },
597
+ easeInSine: function (t) {
598
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
599
+ },
600
+ easeOutSine: function (t) {
601
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
602
+ },
603
+ easeInOutSine: function (t) {
604
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
605
+ },
606
+ easeInExpo: function (t) {
607
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
608
+ },
609
+ easeOutExpo: function (t) {
610
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
611
+ },
612
+ easeInOutExpo: function (t) {
613
+ if (t === 0){
614
+ return 0;
615
+ }
616
+ if (t === 1){
617
+ return 1;
618
+ }
619
+ if ((t /= 1 / 2) < 1){
620
+ return 1 / 2 * Math.pow(2, 10 * (t - 1));
621
+ }
622
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
623
+ },
624
+ easeInCirc: function (t) {
625
+ if (t >= 1){
626
+ return t;
627
+ }
628
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
629
+ },
630
+ easeOutCirc: function (t) {
631
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
632
+ },
633
+ easeInOutCirc: function (t) {
634
+ if ((t /= 1 / 2) < 1){
635
+ return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
636
+ }
637
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
638
+ },
639
+ easeInElastic: function (t) {
640
+ var s = 1.70158;
641
+ var p = 0;
642
+ var a = 1;
643
+ if (t === 0){
644
+ return 0;
645
+ }
646
+ if ((t /= 1) == 1){
647
+ return 1;
648
+ }
649
+ if (!p){
650
+ p = 1 * 0.3;
651
+ }
652
+ if (a < Math.abs(1)) {
653
+ a = 1;
654
+ s = p / 4;
655
+ } else{
656
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
657
+ }
658
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
659
+ },
660
+ easeOutElastic: function (t) {
661
+ var s = 1.70158;
662
+ var p = 0;
663
+ var a = 1;
664
+ if (t === 0){
665
+ return 0;
666
+ }
667
+ if ((t /= 1) == 1){
668
+ return 1;
669
+ }
670
+ if (!p){
671
+ p = 1 * 0.3;
672
+ }
673
+ if (a < Math.abs(1)) {
674
+ a = 1;
675
+ s = p / 4;
676
+ } else{
677
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
678
+ }
679
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
680
+ },
681
+ easeInOutElastic: function (t) {
682
+ var s = 1.70158;
683
+ var p = 0;
684
+ var a = 1;
685
+ if (t === 0){
686
+ return 0;
687
+ }
688
+ if ((t /= 1 / 2) == 2){
689
+ return 1;
690
+ }
691
+ if (!p){
692
+ p = 1 * (0.3 * 1.5);
693
+ }
694
+ if (a < Math.abs(1)) {
695
+ a = 1;
696
+ s = p / 4;
697
+ } else {
698
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
699
+ }
700
+ if (t < 1){
701
+ return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));}
702
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
703
+ },
704
+ easeInBack: function (t) {
705
+ var s = 1.70158;
706
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
707
+ },
708
+ easeOutBack: function (t) {
709
+ var s = 1.70158;
710
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
711
+ },
712
+ easeInOutBack: function (t) {
713
+ var s = 1.70158;
714
+ if ((t /= 1 / 2) < 1){
715
+ return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
716
+ }
717
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
718
+ },
719
+ easeInBounce: function (t) {
720
+ return 1 - easingEffects.easeOutBounce(1 - t);
721
+ },
722
+ easeOutBounce: function (t) {
723
+ if ((t /= 1) < (1 / 2.75)) {
724
+ return 1 * (7.5625 * t * t);
725
+ } else if (t < (2 / 2.75)) {
726
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
727
+ } else if (t < (2.5 / 2.75)) {
728
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
729
+ } else {
730
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
731
+ }
732
+ },
733
+ easeInOutBounce: function (t) {
734
+ if (t < 1 / 2){
735
+ return easingEffects.easeInBounce(t * 2) * 0.5;
736
+ }
737
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
738
+ }
739
+ },
740
+ //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
741
+ requestAnimFrame = helpers.requestAnimFrame = (function(){
742
+ return window.requestAnimationFrame ||
743
+ window.webkitRequestAnimationFrame ||
744
+ window.mozRequestAnimationFrame ||
745
+ window.oRequestAnimationFrame ||
746
+ window.msRequestAnimationFrame ||
747
+ function(callback) {
748
+ return window.setTimeout(callback, 1000 / 60);
749
+ };
750
+ })(),
751
+ cancelAnimFrame = helpers.cancelAnimFrame = (function(){
752
+ return window.cancelAnimationFrame ||
753
+ window.webkitCancelAnimationFrame ||
754
+ window.mozCancelAnimationFrame ||
755
+ window.oCancelAnimationFrame ||
756
+ window.msCancelAnimationFrame ||
757
+ function(callback) {
758
+ return window.clearTimeout(callback, 1000 / 60);
759
+ };
760
+ })(),
761
+ animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
762
+
763
+ var currentStep = 0,
764
+ easingFunction = easingEffects[easingString] || easingEffects.linear;
765
+
766
+ var animationFrame = function(){
767
+ currentStep++;
768
+ var stepDecimal = currentStep/totalSteps;
769
+ var easeDecimal = easingFunction(stepDecimal);
770
+
771
+ callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
772
+ onProgress.call(chartInstance,easeDecimal,stepDecimal);
773
+ if (currentStep < totalSteps){
774
+ chartInstance.animationFrame = requestAnimFrame(animationFrame);
775
+ } else{
776
+ onComplete.apply(chartInstance);
777
+ }
778
+ };
779
+ requestAnimFrame(animationFrame);
780
+ },
781
+ //-- DOM methods
782
+ getRelativePosition = helpers.getRelativePosition = function(evt){
783
+ var mouseX, mouseY;
784
+ var e = evt.originalEvent || evt,
785
+ canvas = evt.currentTarget || evt.srcElement,
786
+ boundingRect = canvas.getBoundingClientRect();
787
+
788
+ if (e.touches){
789
+ mouseX = e.touches[0].clientX - boundingRect.left;
790
+ mouseY = e.touches[0].clientY - boundingRect.top;
791
+
792
+ }
793
+ else{
794
+ mouseX = e.clientX - boundingRect.left;
795
+ mouseY = e.clientY - boundingRect.top;
796
+ }
797
+
798
+ return {
799
+ x : mouseX,
800
+ y : mouseY
801
+ };
802
+
803
+ },
804
+ addEvent = helpers.addEvent = function(node,eventType,method){
805
+ if (node.addEventListener){
806
+ node.addEventListener(eventType,method);
807
+ } else if (node.attachEvent){
808
+ node.attachEvent("on"+eventType, method);
809
+ } else {
810
+ node["on"+eventType] = method;
811
+ }
812
+ },
813
+ removeEvent = helpers.removeEvent = function(node, eventType, handler){
814
+ if (node.removeEventListener){
815
+ node.removeEventListener(eventType, handler, false);
816
+ } else if (node.detachEvent){
817
+ node.detachEvent("on"+eventType,handler);
818
+ } else{
819
+ node["on" + eventType] = noop;
820
+ }
821
+ },
822
+ bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
823
+ // Create the events object if it's not already present
824
+ if (!chartInstance.events) chartInstance.events = {};
825
+
826
+ each(arrayOfEvents,function(eventName){
827
+ chartInstance.events[eventName] = function(){
828
+ handler.apply(chartInstance, arguments);
829
+ };
830
+ addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
831
+ });
832
+ },
833
+ unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
834
+ each(arrayOfEvents, function(handler,eventName){
835
+ removeEvent(chartInstance.chart.canvas, eventName, handler);
836
+ });
837
+ },
838
+ getMaximumWidth = helpers.getMaximumWidth = function(domNode){
839
+ var container = domNode.parentNode,
840
+ padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right'));
841
+ // TODO = check cross browser stuff with this.
842
+ return container ? container.clientWidth - padding : 0;
843
+ },
844
+ getMaximumHeight = helpers.getMaximumHeight = function(domNode){
845
+ var container = domNode.parentNode,
846
+ padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top'));
847
+ // TODO = check cross browser stuff with this.
848
+ return container ? container.clientHeight - padding : 0;
849
+ },
850
+ getStyle = helpers.getStyle = function (el, property) {
851
+ return el.currentStyle ?
852
+ el.currentStyle[property] :
853
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
854
+ },
855
+ getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
856
+ retinaScale = helpers.retinaScale = function(chart){
857
+ var ctx = chart.ctx,
858
+ width = chart.canvas.width,
859
+ height = chart.canvas.height;
860
+
861
+ if (window.devicePixelRatio) {
862
+ ctx.canvas.style.width = width + "px";
863
+ ctx.canvas.style.height = height + "px";
864
+ ctx.canvas.height = height * window.devicePixelRatio;
865
+ ctx.canvas.width = width * window.devicePixelRatio;
866
+ ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
867
+ }
868
+ },
869
+ //-- Canvas methods
870
+ clear = helpers.clear = function(chart){
871
+ chart.ctx.clearRect(0,0,chart.width,chart.height);
872
+ },
873
+ fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
874
+ return fontStyle + " " + pixelSize+"px " + fontFamily;
875
+ },
876
+ longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
877
+ ctx.font = font;
878
+ var longest = 0;
879
+ each(arrayOfStrings,function(string){
880
+ var textWidth = ctx.measureText(string).width;
881
+ longest = (textWidth > longest) ? textWidth : longest;
882
+ });
883
+ return longest;
884
+ },
885
+ drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
886
+ ctx.beginPath();
887
+ ctx.moveTo(x + radius, y);
888
+ ctx.lineTo(x + width - radius, y);
889
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
890
+ ctx.lineTo(x + width, y + height - radius);
891
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
892
+ ctx.lineTo(x + radius, y + height);
893
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
894
+ ctx.lineTo(x, y + radius);
895
+ ctx.quadraticCurveTo(x, y, x + radius, y);
896
+ ctx.closePath();
897
+ };
898
+
899
+
900
+ //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
901
+ //Destroy method on the chart will remove the instance of the chart from this reference.
902
+ Chart.instances = {};
903
+
904
+ Chart.Type = function(data,options,chart){
905
+ this.options = options;
906
+ this.chart = chart;
907
+ this.id = uid();
908
+ //Add the chart instance to the global namespace
909
+ Chart.instances[this.id] = this;
910
+
911
+ // Initialize is always called when a chart type is created
912
+ // By default it is a no op, but it should be extended
913
+ if (options.responsive){
914
+ this.resize();
915
+ }
916
+ this.initialize.call(this,data);
917
+ };
918
+
919
+ //Core methods that'll be a part of every chart type
920
+ extend(Chart.Type.prototype,{
921
+ initialize : function(){return this;},
922
+ clear : function(){
923
+ clear(this.chart);
924
+ return this;
925
+ },
926
+ stop : function(){
927
+ // Stops any current animation loop occuring
928
+ Chart.animationService.cancelAnimation(this);
929
+ return this;
930
+ },
931
+ resize : function(callback){
932
+ this.stop();
933
+ var canvas = this.chart.canvas,
934
+ newWidth = getMaximumWidth(this.chart.canvas),
935
+ newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
936
+
937
+ canvas.width = this.chart.width = newWidth;
938
+ canvas.height = this.chart.height = newHeight;
939
+
940
+ retinaScale(this.chart);
941
+
942
+ if (typeof callback === "function"){
943
+ callback.apply(this, Array.prototype.slice.call(arguments, 1));
944
+ }
945
+ return this;
946
+ },
947
+ reflow : noop,
948
+ render : function(reflow){
949
+ if (reflow){
950
+ this.reflow();
951
+ }
952
+
953
+ if (this.options.animation && !reflow){
954
+ var animation = new Chart.Animation();
955
+ animation.numSteps = this.options.animationSteps;
956
+ animation.easing = this.options.animationEasing;
957
+
958
+ // render function
959
+ animation.render = function(chartInstance, animationObject) {
960
+ var easingFunction = helpers.easingEffects[animationObject.easing];
961
+ var stepDecimal = animationObject.currentStep / animationObject.numSteps;
962
+ var easeDecimal = easingFunction(stepDecimal);
963
+
964
+ chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
965
+ };
966
+
967
+ // user events
968
+ animation.onAnimationProgress = this.options.onAnimationProgress;
969
+ animation.onAnimationComplete = this.options.onAnimationComplete;
970
+
971
+ Chart.animationService.addAnimation(this, animation);
972
+ }
973
+ else{
974
+ this.draw();
975
+ this.options.onAnimationComplete.call(this);
976
+ }
977
+ return this;
978
+ },
979
+ generateLegend : function(){
980
+ return helpers.template(this.options.legendTemplate, this);
981
+ },
982
+ destroy : function(){
983
+ this.stop();
984
+ this.clear();
985
+ unbindEvents(this, this.events);
986
+ var canvas = this.chart.canvas;
987
+
988
+ // Reset canvas height/width attributes starts a fresh with the canvas context
989
+ canvas.width = this.chart.width;
990
+ canvas.height = this.chart.height;
991
+
992
+ // < IE9 doesn't support removeProperty
993
+ if (canvas.style.removeProperty) {
994
+ canvas.style.removeProperty('width');
995
+ canvas.style.removeProperty('height');
996
+ } else {
997
+ canvas.style.removeAttribute('width');
998
+ canvas.style.removeAttribute('height');
999
+ }
1000
+
1001
+ delete Chart.instances[this.id];
1002
+ },
1003
+ showTooltip : function(ChartElements, forceRedraw){
1004
+ // Only redraw the chart if we've actually changed what we're hovering on.
1005
+ if (typeof this.activeElements === 'undefined') this.activeElements = [];
1006
+
1007
+ var isChanged = (function(Elements){
1008
+ var changed = false;
1009
+
1010
+ if (Elements.length !== this.activeElements.length){
1011
+ changed = true;
1012
+ return changed;
1013
+ }
1014
+
1015
+ each(Elements, function(element, index){
1016
+ if (element !== this.activeElements[index]){
1017
+ changed = true;
1018
+ }
1019
+ }, this);
1020
+ return changed;
1021
+ }).call(this, ChartElements);
1022
+
1023
+ if (!isChanged && !forceRedraw){
1024
+ return;
1025
+ }
1026
+ else{
1027
+ this.activeElements = ChartElements;
1028
+ }
1029
+ this.draw();
1030
+ if(this.options.customTooltips){
1031
+ this.options.customTooltips(false);
1032
+ }
1033
+ if (ChartElements.length > 0){
1034
+ // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
1035
+ if (this.datasets && this.datasets.length > 1) {
1036
+ var dataArray,
1037
+ dataIndex;
1038
+
1039
+ for (var i = this.datasets.length - 1; i >= 0; i--) {
1040
+ dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
1041
+ dataIndex = indexOf(dataArray, ChartElements[0]);
1042
+ if (dataIndex !== -1){
1043
+ break;
1044
+ }
1045
+ }
1046
+ var tooltipLabels = [],
1047
+ tooltipColors = [],
1048
+ medianPosition = (function(index) {
1049
+
1050
+ // Get all the points at that particular index
1051
+ var Elements = [],
1052
+ dataCollection,
1053
+ xPositions = [],
1054
+ yPositions = [],
1055
+ xMax,
1056
+ yMax,
1057
+ xMin,
1058
+ yMin;
1059
+ helpers.each(this.datasets, function(dataset){
1060
+ dataCollection = dataset.points || dataset.bars || dataset.segments;
1061
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
1062
+ Elements.push(dataCollection[dataIndex]);
1063
+ }
1064
+ });
1065
+
1066
+ helpers.each(Elements, function(element) {
1067
+ xPositions.push(element.x);
1068
+ yPositions.push(element.y);
1069
+
1070
+
1071
+ //Include any colour information about the element
1072
+ tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
1073
+ tooltipColors.push({
1074
+ fill: element._saved.fillColor || element.fillColor,
1075
+ stroke: element._saved.strokeColor || element.strokeColor
1076
+ });
1077
+
1078
+ }, this);
1079
+
1080
+ yMin = min(yPositions);
1081
+ yMax = max(yPositions);
1082
+
1083
+ xMin = min(xPositions);
1084
+ xMax = max(xPositions);
1085
+
1086
+ return {
1087
+ x: (xMin > this.chart.width/2) ? xMin : xMax,
1088
+ y: (yMin + yMax)/2
1089
+ };
1090
+ }).call(this, dataIndex);
1091
+
1092
+ new Chart.MultiTooltip({
1093
+ x: medianPosition.x,
1094
+ y: medianPosition.y,
1095
+ xPadding: this.options.tooltipXPadding,
1096
+ yPadding: this.options.tooltipYPadding,
1097
+ xOffset: this.options.tooltipXOffset,
1098
+ fillColor: this.options.tooltipFillColor,
1099
+ textColor: this.options.tooltipFontColor,
1100
+ fontFamily: this.options.tooltipFontFamily,
1101
+ fontStyle: this.options.tooltipFontStyle,
1102
+ fontSize: this.options.tooltipFontSize,
1103
+ titleTextColor: this.options.tooltipTitleFontColor,
1104
+ titleFontFamily: this.options.tooltipTitleFontFamily,
1105
+ titleFontStyle: this.options.tooltipTitleFontStyle,
1106
+ titleFontSize: this.options.tooltipTitleFontSize,
1107
+ cornerRadius: this.options.tooltipCornerRadius,
1108
+ labels: tooltipLabels,
1109
+ legendColors: tooltipColors,
1110
+ legendColorBackground : this.options.multiTooltipKeyBackground,
1111
+ title: template(this.options.tooltipTitleTemplate,ChartElements[0]),
1112
+ chart: this.chart,
1113
+ ctx: this.chart.ctx,
1114
+ custom: this.options.customTooltips
1115
+ }).draw();
1116
+
1117
+ } else {
1118
+ each(ChartElements, function(Element) {
1119
+ var tooltipPosition = Element.tooltipPosition();
1120
+ new Chart.Tooltip({
1121
+ x: Math.round(tooltipPosition.x),
1122
+ y: Math.round(tooltipPosition.y),
1123
+ xPadding: this.options.tooltipXPadding,
1124
+ yPadding: this.options.tooltipYPadding,
1125
+ fillColor: this.options.tooltipFillColor,
1126
+ textColor: this.options.tooltipFontColor,
1127
+ fontFamily: this.options.tooltipFontFamily,
1128
+ fontStyle: this.options.tooltipFontStyle,
1129
+ fontSize: this.options.tooltipFontSize,
1130
+ caretHeight: this.options.tooltipCaretSize,
1131
+ cornerRadius: this.options.tooltipCornerRadius,
1132
+ text: template(this.options.tooltipTemplate, Element),
1133
+ chart: this.chart,
1134
+ custom: this.options.customTooltips
1135
+ }).draw();
1136
+ }, this);
1137
+ }
1138
+ }
1139
+ return this;
1140
+ },
1141
+ toBase64Image : function(){
1142
+ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
1143
+ }
1144
+ });
1145
+
1146
+ Chart.Type.extend = function(extensions){
1147
+
1148
+ var parent = this;
1149
+
1150
+ var ChartType = function(){
1151
+ return parent.apply(this,arguments);
1152
+ };
1153
+
1154
+ //Copy the prototype object of the this class
1155
+ ChartType.prototype = clone(parent.prototype);
1156
+ //Now overwrite some of the properties in the base class with the new extensions
1157
+ extend(ChartType.prototype, extensions);
1158
+
1159
+ ChartType.extend = Chart.Type.extend;
1160
+
1161
+ if (extensions.name || parent.prototype.name){
1162
+
1163
+ var chartName = extensions.name || parent.prototype.name;
1164
+ //Assign any potential default values of the new chart type
1165
+
1166
+ //If none are defined, we'll use a clone of the chart type this is being extended from.
1167
+ //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
1168
+ //doesn't define some defaults of their own.
1169
+
1170
+ var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
1171
+
1172
+ Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
1173
+
1174
+ Chart.types[chartName] = ChartType;
1175
+
1176
+ //Register this new chart type in the Chart prototype
1177
+ Chart.prototype[chartName] = function(data,options){
1178
+ var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
1179
+ return new ChartType(data,config,this);
1180
+ };
1181
+ } else{
1182
+ warn("Name not provided for this chart, so it hasn't been registered");
1183
+ }
1184
+ return parent;
1185
+ };
1186
+
1187
+ Chart.Element = function(configuration){
1188
+ extend(this,configuration);
1189
+ this.initialize.apply(this,arguments);
1190
+ this.save();
1191
+ };
1192
+ extend(Chart.Element.prototype,{
1193
+ initialize : function(){},
1194
+ restore : function(props){
1195
+ if (!props){
1196
+ extend(this,this._saved);
1197
+ } else {
1198
+ each(props,function(key){
1199
+ this[key] = this._saved[key];
1200
+ },this);
1201
+ }
1202
+ return this;
1203
+ },
1204
+ save : function(){
1205
+ this._saved = clone(this);
1206
+ delete this._saved._saved;
1207
+ return this;
1208
+ },
1209
+ update : function(newProps){
1210
+ each(newProps,function(value,key){
1211
+ this._saved[key] = this[key];
1212
+ this[key] = value;
1213
+ },this);
1214
+ return this;
1215
+ },
1216
+ transition : function(props,ease){
1217
+ each(props,function(value,key){
1218
+ this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
1219
+ },this);
1220
+ return this;
1221
+ },
1222
+ tooltipPosition : function(){
1223
+ return {
1224
+ x : this.x,
1225
+ y : this.y
1226
+ };
1227
+ },
1228
+ hasValue: function(){
1229
+ return isNumber(this.value);
1230
+ }
1231
+ });
1232
+
1233
+ Chart.Element.extend = inherits;
1234
+
1235
+
1236
+ Chart.Point = Chart.Element.extend({
1237
+ display: true,
1238
+ inRange: function(chartX,chartY){
1239
+ var hitDetectionRange = this.hitDetectionRadius + this.radius;
1240
+ return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
1241
+ },
1242
+ draw : function(){
1243
+ if (this.display){
1244
+ var ctx = this.ctx;
1245
+ ctx.beginPath();
1246
+
1247
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
1248
+ ctx.closePath();
1249
+
1250
+ ctx.strokeStyle = this.strokeColor;
1251
+ ctx.lineWidth = this.strokeWidth;
1252
+
1253
+ ctx.fillStyle = this.fillColor;
1254
+
1255
+ ctx.fill();
1256
+ ctx.stroke();
1257
+ }
1258
+
1259
+
1260
+ //Quick debug for bezier curve splining
1261
+ //Highlights control points and the line between them.
1262
+ //Handy for dev - stripped in the min version.
1263
+
1264
+ // ctx.save();
1265
+ // ctx.fillStyle = "black";
1266
+ // ctx.strokeStyle = "black"
1267
+ // ctx.beginPath();
1268
+ // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
1269
+ // ctx.fill();
1270
+
1271
+ // ctx.beginPath();
1272
+ // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
1273
+ // ctx.fill();
1274
+
1275
+ // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
1276
+ // ctx.lineTo(this.x, this.y);
1277
+ // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
1278
+ // ctx.stroke();
1279
+
1280
+ // ctx.restore();
1281
+
1282
+
1283
+
1284
+ }
1285
+ });
1286
+
1287
+ Chart.Arc = Chart.Element.extend({
1288
+ inRange : function(chartX,chartY){
1289
+
1290
+ var pointRelativePosition = helpers.getAngleFromPoint(this, {
1291
+ x: chartX,
1292
+ y: chartY
1293
+ });
1294
+
1295
+ // Normalize all angles to 0 - 2*PI (0 - 360°)
1296
+ var pointRelativeAngle = pointRelativePosition.angle % (Math.PI * 2),
1297
+ startAngle = (Math.PI * 2 + this.startAngle) % (Math.PI * 2),
1298
+ endAngle = (Math.PI * 2 + this.endAngle) % (Math.PI * 2) || 360;
1299
+
1300
+ // Calculate wether the pointRelativeAngle is between the start and the end angle
1301
+ var betweenAngles = (endAngle < startAngle) ?
1302
+ pointRelativeAngle <= endAngle || pointRelativeAngle >= startAngle:
1303
+ pointRelativeAngle >= startAngle && pointRelativeAngle <= endAngle;
1304
+
1305
+ //Check if within the range of the open/close angle
1306
+ var withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
1307
+
1308
+ return (betweenAngles && withinRadius);
1309
+ //Ensure within the outside of the arc centre, but inside arc outer
1310
+ },
1311
+ tooltipPosition : function(){
1312
+ var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
1313
+ rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
1314
+ return {
1315
+ x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
1316
+ y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
1317
+ };
1318
+ },
1319
+ draw : function(animationPercent){
1320
+
1321
+ var easingDecimal = animationPercent || 1;
1322
+
1323
+ var ctx = this.ctx;
1324
+
1325
+ ctx.beginPath();
1326
+
1327
+ ctx.arc(this.x, this.y, this.outerRadius < 0 ? 0 : this.outerRadius, this.startAngle, this.endAngle);
1328
+
1329
+ ctx.arc(this.x, this.y, this.innerRadius < 0 ? 0 : this.innerRadius, this.endAngle, this.startAngle, true);
1330
+
1331
+ ctx.closePath();
1332
+ ctx.strokeStyle = this.strokeColor;
1333
+ ctx.lineWidth = this.strokeWidth;
1334
+
1335
+ ctx.fillStyle = this.fillColor;
1336
+
1337
+ ctx.fill();
1338
+ ctx.lineJoin = 'bevel';
1339
+
1340
+ if (this.showStroke){
1341
+ ctx.stroke();
1342
+ }
1343
+ }
1344
+ });
1345
+
1346
+ Chart.Rectangle = Chart.Element.extend({
1347
+ draw : function(){
1348
+ var ctx = this.ctx,
1349
+ halfWidth = this.width/2,
1350
+ leftX = this.x - halfWidth,
1351
+ rightX = this.x + halfWidth,
1352
+ top = this.base - (this.base - this.y),
1353
+ halfStroke = this.strokeWidth / 2;
1354
+
1355
+ // Canvas doesn't allow us to stroke inside the width so we can
1356
+ // adjust the sizes to fit if we're setting a stroke on the line
1357
+ if (this.showStroke){
1358
+ leftX += halfStroke;
1359
+ rightX -= halfStroke;
1360
+ top += halfStroke;
1361
+ }
1362
+
1363
+ ctx.beginPath();
1364
+
1365
+ ctx.fillStyle = this.fillColor;
1366
+ ctx.strokeStyle = this.strokeColor;
1367
+ ctx.lineWidth = this.strokeWidth;
1368
+
1369
+ // It'd be nice to keep this class totally generic to any rectangle
1370
+ // and simply specify which border to miss out.
1371
+ ctx.moveTo(leftX, this.base);
1372
+ ctx.lineTo(leftX, top);
1373
+ ctx.lineTo(rightX, top);
1374
+ ctx.lineTo(rightX, this.base);
1375
+ ctx.fill();
1376
+ if (this.showStroke){
1377
+ ctx.stroke();
1378
+ }
1379
+ },
1380
+ height : function(){
1381
+ return this.base - this.y;
1382
+ },
1383
+ inRange : function(chartX,chartY){
1384
+ return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
1385
+ }
1386
+ });
1387
+
1388
+ Chart.Animation = Chart.Element.extend({
1389
+ currentStep: null, // the current animation step
1390
+ numSteps: 60, // default number of steps
1391
+ easing: "", // the easing to use for this animation
1392
+ render: null, // render function used by the animation service
1393
+
1394
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
1395
+ onAnimationComplete: null, // user specified callback to fire when the animation finishes
1396
+ });
1397
+
1398
+ Chart.Tooltip = Chart.Element.extend({
1399
+ draw : function(){
1400
+
1401
+ var ctx = this.chart.ctx;
1402
+
1403
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1404
+
1405
+ this.xAlign = "center";
1406
+ this.yAlign = "above";
1407
+
1408
+ //Distance between the actual element.y position and the start of the tooltip caret
1409
+ var caretPadding = this.caretPadding = 2;
1410
+
1411
+ var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
1412
+ tooltipRectHeight = this.fontSize + 2*this.yPadding,
1413
+ tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
1414
+
1415
+ if (this.x + tooltipWidth/2 >this.chart.width){
1416
+ this.xAlign = "left";
1417
+ } else if (this.x - tooltipWidth/2 < 0){
1418
+ this.xAlign = "right";
1419
+ }
1420
+
1421
+ if (this.y - tooltipHeight < 0){
1422
+ this.yAlign = "below";
1423
+ }
1424
+
1425
+
1426
+ var tooltipX = this.x - tooltipWidth/2,
1427
+ tooltipY = this.y - tooltipHeight;
1428
+
1429
+ ctx.fillStyle = this.fillColor;
1430
+
1431
+ // Custom Tooltips
1432
+ if(this.custom){
1433
+ this.custom(this);
1434
+ }
1435
+ else{
1436
+ switch(this.yAlign)
1437
+ {
1438
+ case "above":
1439
+ //Draw a caret above the x/y
1440
+ ctx.beginPath();
1441
+ ctx.moveTo(this.x,this.y - caretPadding);
1442
+ ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
1443
+ ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
1444
+ ctx.closePath();
1445
+ ctx.fill();
1446
+ break;
1447
+ case "below":
1448
+ tooltipY = this.y + caretPadding + this.caretHeight;
1449
+ //Draw a caret below the x/y
1450
+ ctx.beginPath();
1451
+ ctx.moveTo(this.x, this.y + caretPadding);
1452
+ ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
1453
+ ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
1454
+ ctx.closePath();
1455
+ ctx.fill();
1456
+ break;
1457
+ }
1458
+
1459
+ switch(this.xAlign)
1460
+ {
1461
+ case "left":
1462
+ tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
1463
+ break;
1464
+ case "right":
1465
+ tooltipX = this.x - (this.cornerRadius + this.caretHeight);
1466
+ break;
1467
+ }
1468
+
1469
+ drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
1470
+
1471
+ ctx.fill();
1472
+
1473
+ ctx.fillStyle = this.textColor;
1474
+ ctx.textAlign = "center";
1475
+ ctx.textBaseline = "middle";
1476
+ ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
1477
+ }
1478
+ }
1479
+ });
1480
+
1481
+ Chart.MultiTooltip = Chart.Element.extend({
1482
+ initialize : function(){
1483
+ this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1484
+
1485
+ this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
1486
+
1487
+ this.titleHeight = this.title ? this.titleFontSize * 1.5 : 0;
1488
+ this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleHeight;
1489
+
1490
+ this.ctx.font = this.titleFont;
1491
+
1492
+ var titleWidth = this.ctx.measureText(this.title).width,
1493
+ //Label has a legend square as well so account for this.
1494
+ labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
1495
+ longestTextWidth = max([labelWidth,titleWidth]);
1496
+
1497
+ this.width = longestTextWidth + (this.xPadding*2);
1498
+
1499
+
1500
+ var halfHeight = this.height/2;
1501
+
1502
+ //Check to ensure the height will fit on the canvas
1503
+ if (this.y - halfHeight < 0 ){
1504
+ this.y = halfHeight;
1505
+ } else if (this.y + halfHeight > this.chart.height){
1506
+ this.y = this.chart.height - halfHeight;
1507
+ }
1508
+
1509
+ //Decide whether to align left or right based on position on canvas
1510
+ if (this.x > this.chart.width/2){
1511
+ this.x -= this.xOffset + this.width;
1512
+ } else {
1513
+ this.x += this.xOffset;
1514
+ }
1515
+
1516
+
1517
+ },
1518
+ getLineHeight : function(index){
1519
+ var baseLineHeight = this.y - (this.height/2) + this.yPadding,
1520
+ afterTitleIndex = index-1;
1521
+
1522
+ //If the index is zero, we're getting the title
1523
+ if (index === 0){
1524
+ return baseLineHeight + this.titleHeight / 3;
1525
+ } else{
1526
+ return baseLineHeight + ((this.fontSize * 1.5 * afterTitleIndex) + this.fontSize / 2) + this.titleHeight;
1527
+ }
1528
+
1529
+ },
1530
+ draw : function(){
1531
+ // Custom Tooltips
1532
+ if(this.custom){
1533
+ this.custom(this);
1534
+ }
1535
+ else{
1536
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
1537
+ var ctx = this.ctx;
1538
+ ctx.fillStyle = this.fillColor;
1539
+ ctx.fill();
1540
+ ctx.closePath();
1541
+
1542
+ ctx.textAlign = "left";
1543
+ ctx.textBaseline = "middle";
1544
+ ctx.fillStyle = this.titleTextColor;
1545
+ ctx.font = this.titleFont;
1546
+
1547
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
1548
+
1549
+ ctx.font = this.font;
1550
+ helpers.each(this.labels,function(label,index){
1551
+ ctx.fillStyle = this.textColor;
1552
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
1553
+
1554
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
1555
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1556
+ //Instead we'll make a white filled block to put the legendColour palette over.
1557
+
1558
+ ctx.fillStyle = this.legendColorBackground;
1559
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1560
+
1561
+ ctx.fillStyle = this.legendColors[index].fill;
1562
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1563
+
1564
+
1565
+ },this);
1566
+ }
1567
+ }
1568
+ });
1569
+
1570
+ Chart.Scale = Chart.Element.extend({
1571
+ initialize : function(){
1572
+ this.fit();
1573
+ },
1574
+ buildYLabels : function(){
1575
+ this.yLabels = [];
1576
+
1577
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1578
+
1579
+ for (var i=0; i<=this.steps; i++){
1580
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1581
+ }
1582
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) + 10 : 0;
1583
+ },
1584
+ addXLabel : function(label){
1585
+ this.xLabels.push(label);
1586
+ this.valuesCount++;
1587
+ this.fit();
1588
+ },
1589
+ removeXLabel : function(){
1590
+ this.xLabels.shift();
1591
+ this.valuesCount--;
1592
+ this.fit();
1593
+ },
1594
+ // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
1595
+ fit: function(){
1596
+ // First we need the width of the yLabels, assuming the xLabels aren't rotated
1597
+
1598
+ // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
1599
+ this.startPoint = (this.display) ? this.fontSize : 0;
1600
+ this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
1601
+
1602
+ // Apply padding settings to the start and end point.
1603
+ this.startPoint += this.padding;
1604
+ this.endPoint -= this.padding;
1605
+
1606
+ // Cache the starting endpoint, excluding the space for x labels
1607
+ var cachedEndPoint = this.endPoint;
1608
+
1609
+ // Cache the starting height, so can determine if we need to recalculate the scale yAxis
1610
+ var cachedHeight = this.endPoint - this.startPoint,
1611
+ cachedYLabelWidth;
1612
+
1613
+ // Build the current yLabels so we have an idea of what size they'll be to start
1614
+ /*
1615
+ * This sets what is returned from calculateScaleRange as static properties of this class:
1616
+ *
1617
+ this.steps;
1618
+ this.stepValue;
1619
+ this.min;
1620
+ this.max;
1621
+ *
1622
+ */
1623
+ this.calculateYRange(cachedHeight);
1624
+
1625
+ // With these properties set we can now build the array of yLabels
1626
+ // and also the width of the largest yLabel
1627
+ this.buildYLabels();
1628
+
1629
+ this.calculateXLabelRotation();
1630
+
1631
+ while((cachedHeight > this.endPoint - this.startPoint)){
1632
+ cachedHeight = this.endPoint - this.startPoint;
1633
+ cachedYLabelWidth = this.yLabelWidth;
1634
+
1635
+ this.calculateYRange(cachedHeight);
1636
+ this.buildYLabels();
1637
+
1638
+ // Only go through the xLabel loop again if the yLabel width has changed
1639
+ if (cachedYLabelWidth < this.yLabelWidth){
1640
+ this.endPoint = cachedEndPoint;
1641
+ this.calculateXLabelRotation();
1642
+ }
1643
+ }
1644
+
1645
+ },
1646
+ calculateXLabelRotation : function(){
1647
+ //Get the width of each grid by calculating the difference
1648
+ //between x offsets between 0 and 1.
1649
+
1650
+ this.ctx.font = this.font;
1651
+
1652
+ var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
1653
+ lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
1654
+ firstRotated,
1655
+ lastRotated;
1656
+
1657
+
1658
+ this.xScalePaddingRight = lastWidth/2 + 3;
1659
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth) ? firstWidth/2 : this.yLabelWidth;
1660
+
1661
+ this.xLabelRotation = 0;
1662
+ if (this.display){
1663
+ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
1664
+ cosRotation,
1665
+ firstRotatedWidth;
1666
+ this.xLabelWidth = originalLabelWidth;
1667
+ //Allow 3 pixels x2 padding either side for label readability
1668
+ var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
1669
+
1670
+ //Max label rotate should be 90 - also act as a loop counter
1671
+ while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
1672
+ cosRotation = Math.cos(toRadians(this.xLabelRotation));
1673
+
1674
+ firstRotated = cosRotation * firstWidth;
1675
+ lastRotated = cosRotation * lastWidth;
1676
+
1677
+ // We're right aligning the text now.
1678
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth){
1679
+ this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
1680
+ }
1681
+ this.xScalePaddingRight = this.fontSize/2;
1682
+
1683
+
1684
+ this.xLabelRotation++;
1685
+ this.xLabelWidth = cosRotation * originalLabelWidth;
1686
+
1687
+ }
1688
+ if (this.xLabelRotation > 0){
1689
+ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
1690
+ }
1691
+ }
1692
+ else{
1693
+ this.xLabelWidth = 0;
1694
+ this.xScalePaddingRight = this.padding;
1695
+ this.xScalePaddingLeft = this.padding;
1696
+ }
1697
+
1698
+ },
1699
+ // Needs to be overidden in each Chart type
1700
+ // Otherwise we need to pass all the data into the scale class
1701
+ calculateYRange: noop,
1702
+ drawingArea: function(){
1703
+ return this.startPoint - this.endPoint;
1704
+ },
1705
+ calculateY : function(value){
1706
+ var scalingFactor = this.drawingArea() / (this.min - this.max);
1707
+ return this.endPoint - (scalingFactor * (value - this.min));
1708
+ },
1709
+ calculateX : function(index){
1710
+ var isRotated = (this.xLabelRotation > 0),
1711
+ // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
1712
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
1713
+ valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
1714
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
1715
+
1716
+ if (this.offsetGridLines){
1717
+ valueOffset += (valueWidth/2);
1718
+ }
1719
+
1720
+ return Math.round(valueOffset);
1721
+ },
1722
+ update : function(newProps){
1723
+ helpers.extend(this, newProps);
1724
+ this.fit();
1725
+ },
1726
+ draw : function(){
1727
+ var ctx = this.ctx,
1728
+ yLabelGap = (this.endPoint - this.startPoint) / this.steps,
1729
+ xStart = Math.round(this.xScalePaddingLeft);
1730
+ if (this.display){
1731
+ ctx.fillStyle = this.textColor;
1732
+ ctx.font = this.font;
1733
+ each(this.yLabels,function(labelString,index){
1734
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
1735
+ linePositionY = Math.round(yLabelCenter),
1736
+ drawHorizontalLine = this.showHorizontalLines;
1737
+
1738
+ ctx.textAlign = "right";
1739
+ ctx.textBaseline = "middle";
1740
+ if (this.showLabels){
1741
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
1742
+ }
1743
+
1744
+ // This is X axis, so draw it
1745
+ if (index === 0 && !drawHorizontalLine){
1746
+ drawHorizontalLine = true;
1747
+ }
1748
+
1749
+ if (drawHorizontalLine){
1750
+ ctx.beginPath();
1751
+ }
1752
+
1753
+ if (index > 0){
1754
+ // This is a grid line in the centre, so drop that
1755
+ ctx.lineWidth = this.gridLineWidth;
1756
+ ctx.strokeStyle = this.gridLineColor;
1757
+ } else {
1758
+ // This is the first line on the scale
1759
+ ctx.lineWidth = this.lineWidth;
1760
+ ctx.strokeStyle = this.lineColor;
1761
+ }
1762
+
1763
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
1764
+
1765
+ if(drawHorizontalLine){
1766
+ ctx.moveTo(xStart, linePositionY);
1767
+ ctx.lineTo(this.width, linePositionY);
1768
+ ctx.stroke();
1769
+ ctx.closePath();
1770
+ }
1771
+
1772
+ ctx.lineWidth = this.lineWidth;
1773
+ ctx.strokeStyle = this.lineColor;
1774
+ ctx.beginPath();
1775
+ ctx.moveTo(xStart - 5, linePositionY);
1776
+ ctx.lineTo(xStart, linePositionY);
1777
+ ctx.stroke();
1778
+ ctx.closePath();
1779
+
1780
+ },this);
1781
+
1782
+ each(this.xLabels,function(label,index){
1783
+ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
1784
+ // Check to see if line/bar here and decide where to place the line
1785
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
1786
+ isRotated = (this.xLabelRotation > 0),
1787
+ drawVerticalLine = this.showVerticalLines;
1788
+
1789
+ // This is Y axis, so draw it
1790
+ if (index === 0 && !drawVerticalLine){
1791
+ drawVerticalLine = true;
1792
+ }
1793
+
1794
+ if (drawVerticalLine){
1795
+ ctx.beginPath();
1796
+ }
1797
+
1798
+ if (index > 0){
1799
+ // This is a grid line in the centre, so drop that
1800
+ ctx.lineWidth = this.gridLineWidth;
1801
+ ctx.strokeStyle = this.gridLineColor;
1802
+ } else {
1803
+ // This is the first line on the scale
1804
+ ctx.lineWidth = this.lineWidth;
1805
+ ctx.strokeStyle = this.lineColor;
1806
+ }
1807
+
1808
+ if (drawVerticalLine){
1809
+ ctx.moveTo(linePos,this.endPoint);
1810
+ ctx.lineTo(linePos,this.startPoint - 3);
1811
+ ctx.stroke();
1812
+ ctx.closePath();
1813
+ }
1814
+
1815
+
1816
+ ctx.lineWidth = this.lineWidth;
1817
+ ctx.strokeStyle = this.lineColor;
1818
+
1819
+
1820
+ // Small lines at the bottom of the base grid line
1821
+ ctx.beginPath();
1822
+ ctx.moveTo(linePos,this.endPoint);
1823
+ ctx.lineTo(linePos,this.endPoint + 5);
1824
+ ctx.stroke();
1825
+ ctx.closePath();
1826
+
1827
+ ctx.save();
1828
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
1829
+ ctx.rotate(toRadians(this.xLabelRotation)*-1);
1830
+ ctx.font = this.font;
1831
+ ctx.textAlign = (isRotated) ? "right" : "center";
1832
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
1833
+ ctx.fillText(label, 0, 0);
1834
+ ctx.restore();
1835
+ },this);
1836
+
1837
+ }
1838
+ }
1839
+
1840
+ });
1841
+
1842
+ Chart.RadialScale = Chart.Element.extend({
1843
+ initialize: function(){
1844
+ this.size = min([this.height, this.width]);
1845
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1846
+ },
1847
+ calculateCenterOffset: function(value){
1848
+ // Take into account half font size + the yPadding of the top value
1849
+ var scalingFactor = this.drawingArea / (this.max - this.min);
1850
+
1851
+ return (value - this.min) * scalingFactor;
1852
+ },
1853
+ update : function(){
1854
+ if (!this.lineArc){
1855
+ this.setScaleSize();
1856
+ } else {
1857
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1858
+ }
1859
+ this.buildYLabels();
1860
+ },
1861
+ buildYLabels: function(){
1862
+ this.yLabels = [];
1863
+
1864
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1865
+
1866
+ for (var i=0; i<=this.steps; i++){
1867
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1868
+ }
1869
+ },
1870
+ getCircumference : function(){
1871
+ return ((Math.PI*2) / this.valuesCount);
1872
+ },
1873
+ setScaleSize: function(){
1874
+ /*
1875
+ * Right, this is really confusing and there is a lot of maths going on here
1876
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
1877
+ *
1878
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
1879
+ *
1880
+ * Solution:
1881
+ *
1882
+ * We assume the radius of the polygon is half the size of the canvas at first
1883
+ * at each index we check if the text overlaps.
1884
+ *
1885
+ * Where it does, we store that angle and that index.
1886
+ *
1887
+ * After finding the largest index and angle we calculate how much we need to remove
1888
+ * from the shape radius to move the point inwards by that x.
1889
+ *
1890
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
1891
+ * along with labels.
1892
+ *
1893
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
1894
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
1895
+ *
1896
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
1897
+ * and position it in the most space efficient manner
1898
+ *
1899
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
1900
+ */
1901
+
1902
+
1903
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
1904
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
1905
+ var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
1906
+ pointPosition,
1907
+ i,
1908
+ textWidth,
1909
+ halfTextWidth,
1910
+ furthestRight = this.width,
1911
+ furthestRightIndex,
1912
+ furthestRightAngle,
1913
+ furthestLeft = 0,
1914
+ furthestLeftIndex,
1915
+ furthestLeftAngle,
1916
+ xProtrusionLeft,
1917
+ xProtrusionRight,
1918
+ radiusReductionRight,
1919
+ radiusReductionLeft,
1920
+ maxWidthRadius;
1921
+ this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1922
+ for (i=0;i<this.valuesCount;i++){
1923
+ // 5px to space the text slightly out - similar to what we do in the draw function.
1924
+ pointPosition = this.getPointPosition(i, largestPossibleRadius);
1925
+ textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
1926
+ if (i === 0 || i === this.valuesCount/2){
1927
+ // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
1928
+ // of the radar chart, so text will be aligned centrally, so we'll half it and compare
1929
+ // w/left and right text sizes
1930
+ halfTextWidth = textWidth/2;
1931
+ if (pointPosition.x + halfTextWidth > furthestRight) {
1932
+ furthestRight = pointPosition.x + halfTextWidth;
1933
+ furthestRightIndex = i;
1934
+ }
1935
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
1936
+ furthestLeft = pointPosition.x - halfTextWidth;
1937
+ furthestLeftIndex = i;
1938
+ }
1939
+ }
1940
+ else if (i < this.valuesCount/2) {
1941
+ // Less than half the values means we'll left align the text
1942
+ if (pointPosition.x + textWidth > furthestRight) {
1943
+ furthestRight = pointPosition.x + textWidth;
1944
+ furthestRightIndex = i;
1945
+ }
1946
+ }
1947
+ else if (i > this.valuesCount/2){
1948
+ // More than half the values means we'll right align the text
1949
+ if (pointPosition.x - textWidth < furthestLeft) {
1950
+ furthestLeft = pointPosition.x - textWidth;
1951
+ furthestLeftIndex = i;
1952
+ }
1953
+ }
1954
+ }
1955
+
1956
+ xProtrusionLeft = furthestLeft;
1957
+
1958
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
1959
+
1960
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
1961
+
1962
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
1963
+
1964
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
1965
+
1966
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
1967
+
1968
+ // Ensure we actually need to reduce the size of the chart
1969
+ radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
1970
+ radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
1971
+
1972
+ this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
1973
+
1974
+ //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
1975
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
1976
+
1977
+ },
1978
+ setCenterPoint: function(leftMovement, rightMovement){
1979
+
1980
+ var maxRight = this.width - rightMovement - this.drawingArea,
1981
+ maxLeft = leftMovement + this.drawingArea;
1982
+
1983
+ this.xCenter = (maxLeft + maxRight)/2;
1984
+ // Always vertically in the centre as the text height doesn't change
1985
+ this.yCenter = (this.height/2);
1986
+ },
1987
+
1988
+ getIndexAngle : function(index){
1989
+ var angleMultiplier = (Math.PI * 2) / this.valuesCount;
1990
+ // Start from the top instead of right, so remove a quarter of the circle
1991
+
1992
+ return index * angleMultiplier - (Math.PI/2);
1993
+ },
1994
+ getPointPosition : function(index, distanceFromCenter){
1995
+ var thisAngle = this.getIndexAngle(index);
1996
+ return {
1997
+ x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
1998
+ y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
1999
+ };
2000
+ },
2001
+ draw: function(){
2002
+ if (this.display){
2003
+ var ctx = this.ctx;
2004
+ each(this.yLabels, function(label, index){
2005
+ // Don't draw a centre value
2006
+ if (index > 0){
2007
+ var yCenterOffset = index * (this.drawingArea/this.steps),
2008
+ yHeight = this.yCenter - yCenterOffset,
2009
+ pointPosition;
2010
+
2011
+ // Draw circular lines around the scale
2012
+ if (this.lineWidth > 0){
2013
+ ctx.strokeStyle = this.lineColor;
2014
+ ctx.lineWidth = this.lineWidth;
2015
+
2016
+ if(this.lineArc){
2017
+ ctx.beginPath();
2018
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
2019
+ ctx.closePath();
2020
+ ctx.stroke();
2021
+ } else{
2022
+ ctx.beginPath();
2023
+ for (var i=0;i<this.valuesCount;i++)
2024
+ {
2025
+ pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
2026
+ if (i === 0){
2027
+ ctx.moveTo(pointPosition.x, pointPosition.y);
2028
+ } else {
2029
+ ctx.lineTo(pointPosition.x, pointPosition.y);
2030
+ }
2031
+ }
2032
+ ctx.closePath();
2033
+ ctx.stroke();
2034
+ }
2035
+ }
2036
+ if(this.showLabels){
2037
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
2038
+ if (this.showLabelBackdrop){
2039
+ var labelWidth = ctx.measureText(label).width;
2040
+ ctx.fillStyle = this.backdropColor;
2041
+ ctx.fillRect(
2042
+ this.xCenter - labelWidth/2 - this.backdropPaddingX,
2043
+ yHeight - this.fontSize/2 - this.backdropPaddingY,
2044
+ labelWidth + this.backdropPaddingX*2,
2045
+ this.fontSize + this.backdropPaddingY*2
2046
+ );
2047
+ }
2048
+ ctx.textAlign = 'center';
2049
+ ctx.textBaseline = "middle";
2050
+ ctx.fillStyle = this.fontColor;
2051
+ ctx.fillText(label, this.xCenter, yHeight);
2052
+ }
2053
+ }
2054
+ }, this);
2055
+
2056
+ if (!this.lineArc){
2057
+ ctx.lineWidth = this.angleLineWidth;
2058
+ ctx.strokeStyle = this.angleLineColor;
2059
+ for (var i = this.valuesCount - 1; i >= 0; i--) {
2060
+ var centerOffset = null, outerPosition = null;
2061
+
2062
+ if (this.angleLineWidth > 0 && (i % this.angleLineInterval === 0)){
2063
+ centerOffset = this.calculateCenterOffset(this.max);
2064
+ outerPosition = this.getPointPosition(i, centerOffset);
2065
+ ctx.beginPath();
2066
+ ctx.moveTo(this.xCenter, this.yCenter);
2067
+ ctx.lineTo(outerPosition.x, outerPosition.y);
2068
+ ctx.stroke();
2069
+ ctx.closePath();
2070
+ }
2071
+
2072
+ if (this.backgroundColors && this.backgroundColors.length == this.valuesCount) {
2073
+ if (centerOffset == null)
2074
+ centerOffset = this.calculateCenterOffset(this.max);
2075
+
2076
+ if (outerPosition == null)
2077
+ outerPosition = this.getPointPosition(i, centerOffset);
2078
+
2079
+ var previousOuterPosition = this.getPointPosition(i === 0 ? this.valuesCount - 1 : i - 1, centerOffset);
2080
+ var nextOuterPosition = this.getPointPosition(i === this.valuesCount - 1 ? 0 : i + 1, centerOffset);
2081
+
2082
+ var previousOuterHalfway = { x: (previousOuterPosition.x + outerPosition.x) / 2, y: (previousOuterPosition.y + outerPosition.y) / 2 };
2083
+ var nextOuterHalfway = { x: (outerPosition.x + nextOuterPosition.x) / 2, y: (outerPosition.y + nextOuterPosition.y) / 2 };
2084
+
2085
+ ctx.beginPath();
2086
+ ctx.moveTo(this.xCenter, this.yCenter);
2087
+ ctx.lineTo(previousOuterHalfway.x, previousOuterHalfway.y);
2088
+ ctx.lineTo(outerPosition.x, outerPosition.y);
2089
+ ctx.lineTo(nextOuterHalfway.x, nextOuterHalfway.y);
2090
+ ctx.fillStyle = this.backgroundColors[i];
2091
+ ctx.fill();
2092
+ ctx.closePath();
2093
+ }
2094
+ // Extra 3px out for some label spacing
2095
+ var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
2096
+ ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
2097
+ ctx.fillStyle = this.pointLabelFontColor;
2098
+
2099
+ var labelsCount = this.labels.length,
2100
+ halfLabelsCount = this.labels.length/2,
2101
+ quarterLabelsCount = halfLabelsCount/2,
2102
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
2103
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
2104
+ if (i === 0){
2105
+ ctx.textAlign = 'center';
2106
+ } else if(i === halfLabelsCount){
2107
+ ctx.textAlign = 'center';
2108
+ } else if (i < halfLabelsCount){
2109
+ ctx.textAlign = 'left';
2110
+ } else {
2111
+ ctx.textAlign = 'right';
2112
+ }
2113
+
2114
+ // Set the correct text baseline based on outer positioning
2115
+ if (exactQuarter){
2116
+ ctx.textBaseline = 'middle';
2117
+ } else if (upperHalf){
2118
+ ctx.textBaseline = 'bottom';
2119
+ } else {
2120
+ ctx.textBaseline = 'top';
2121
+ }
2122
+
2123
+ ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
2124
+ }
2125
+ }
2126
+ }
2127
+ }
2128
+ });
2129
+
2130
+ Chart.animationService = {
2131
+ frameDuration: 17,
2132
+ animations: [],
2133
+ dropFrames: 0,
2134
+ addAnimation: function(chartInstance, animationObject) {
2135
+ for (var index = 0; index < this.animations.length; ++ index){
2136
+ if (this.animations[index].chartInstance === chartInstance){
2137
+ // replacing an in progress animation
2138
+ this.animations[index].animationObject = animationObject;
2139
+ return;
2140
+ }
2141
+ }
2142
+
2143
+ this.animations.push({
2144
+ chartInstance: chartInstance,
2145
+ animationObject: animationObject
2146
+ });
2147
+
2148
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
2149
+ if (this.animations.length == 1) {
2150
+ helpers.requestAnimFrame.call(window, this.digestWrapper);
2151
+ }
2152
+ },
2153
+ // Cancel the animation for a given chart instance
2154
+ cancelAnimation: function(chartInstance) {
2155
+ var index = helpers.findNextWhere(this.animations, function(animationWrapper) {
2156
+ return animationWrapper.chartInstance === chartInstance;
2157
+ });
2158
+
2159
+ if (index)
2160
+ {
2161
+ this.animations.splice(index, 1);
2162
+ }
2163
+ },
2164
+ // calls startDigest with the proper context
2165
+ digestWrapper: function() {
2166
+ Chart.animationService.startDigest.call(Chart.animationService);
2167
+ },
2168
+ startDigest: function() {
2169
+
2170
+ var startTime = Date.now();
2171
+ var framesToDrop = 0;
2172
+
2173
+ if(this.dropFrames > 1){
2174
+ framesToDrop = Math.floor(this.dropFrames);
2175
+ this.dropFrames -= framesToDrop;
2176
+ }
2177
+
2178
+ for (var i = 0; i < this.animations.length; i++) {
2179
+
2180
+ if (this.animations[i].animationObject.currentStep === null){
2181
+ this.animations[i].animationObject.currentStep = 0;
2182
+ }
2183
+
2184
+ this.animations[i].animationObject.currentStep += 1 + framesToDrop;
2185
+ if(this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps){
2186
+ this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
2187
+ }
2188
+
2189
+ this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
2190
+
2191
+ // Check if executed the last frame.
2192
+ if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps){
2193
+ // Call onAnimationComplete
2194
+ this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance);
2195
+ // Remove the animation.
2196
+ this.animations.splice(i, 1);
2197
+ // Keep the index in place to offset the splice
2198
+ i--;
2199
+ }
2200
+ }
2201
+
2202
+ var endTime = Date.now();
2203
+ var delay = endTime - startTime - this.frameDuration;
2204
+ var frameDelay = delay / this.frameDuration;
2205
+
2206
+ if(frameDelay > 1){
2207
+ this.dropFrames += frameDelay;
2208
+ }
2209
+
2210
+ // Do we have more stuff to animate?
2211
+ if (this.animations.length > 0){
2212
+ helpers.requestAnimFrame.call(window, this.digestWrapper);
2213
+ }
2214
+ }
2215
+ };
2216
+
2217
+ // Attach global event to resize each chart instance when the browser resizes
2218
+ helpers.addEvent(window, "resize", (function(){
2219
+ // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
2220
+ var timeout;
2221
+ return function(){
2222
+ clearTimeout(timeout);
2223
+ timeout = setTimeout(function(){
2224
+ each(Chart.instances,function(instance){
2225
+ // If the responsive flag is set in the chart instance config
2226
+ // Cascade the resize event down to the chart.
2227
+ if (instance.options.responsive){
2228
+ instance.resize(instance.render, true);
2229
+ }
2230
+ });
2231
+ }, 50);
2232
+ };
2233
+ })());
2234
+
2235
+
2236
+ if (amd) {
2237
+ define('Chart', [], function(){
2238
+ return Chart;
2239
+ });
2240
+ } else if (typeof module === 'object' && module.exports) {
2241
+ module.exports = Chart;
2242
+ }
2243
+
2244
+ root.Chart = Chart;
2245
+
2246
+ Chart.noConflict = function(){
2247
+ root.Chart = previous;
2248
+ return Chart;
2249
+ };
2250
+
2251
+ }).call(this);
2252
+
2253
+ (function(){
2254
+ "use strict";
2255
+
2256
+ var root = this,
2257
+ Chart = root.Chart,
2258
+ helpers = Chart.helpers;
2259
+
2260
+
2261
+ var defaultConfig = {
2262
+ //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
2263
+ scaleBeginAtZero : true,
2264
+
2265
+ //Boolean - Whether grid lines are shown across the chart
2266
+ scaleShowGridLines : true,
2267
+
2268
+ //String - Colour of the grid lines
2269
+ scaleGridLineColor : "rgba(0,0,0,.05)",
2270
+
2271
+ //Number - Width of the grid lines
2272
+ scaleGridLineWidth : 1,
2273
+
2274
+ //Boolean - Whether to show horizontal lines (except X axis)
2275
+ scaleShowHorizontalLines: true,
2276
+
2277
+ //Boolean - Whether to show vertical lines (except Y axis)
2278
+ scaleShowVerticalLines: true,
2279
+
2280
+ //Boolean - If there is a stroke on each bar
2281
+ barShowStroke : true,
2282
+
2283
+ //Number - Pixel width of the bar stroke
2284
+ barStrokeWidth : 2,
2285
+
2286
+ //Number - Spacing between each of the X value sets
2287
+ barValueSpacing : 5,
2288
+
2289
+ //Number - Spacing between data sets within X values
2290
+ barDatasetSpacing : 1,
2291
+
2292
+ //String - A legend template
2293
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>"
2294
+
2295
+ };
2296
+
2297
+
2298
+ Chart.Type.extend({
2299
+ name: "Bar",
2300
+ defaults : defaultConfig,
2301
+ initialize: function(data){
2302
+
2303
+ //Expose options as a scope variable here so we can access it in the ScaleClass
2304
+ var options = this.options;
2305
+
2306
+ this.ScaleClass = Chart.Scale.extend({
2307
+ offsetGridLines : true,
2308
+ calculateBarX : function(datasetCount, datasetIndex, barIndex){
2309
+ //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
2310
+ var xWidth = this.calculateBaseWidth(),
2311
+ xAbsolute = this.calculateX(barIndex) - (xWidth/2),
2312
+ barWidth = this.calculateBarWidth(datasetCount);
2313
+
2314
+ return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
2315
+ },
2316
+ calculateBaseWidth : function(){
2317
+ return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
2318
+ },
2319
+ calculateBarWidth : function(datasetCount){
2320
+ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
2321
+ var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
2322
+
2323
+ return (baseWidth / datasetCount);
2324
+ }
2325
+ });
2326
+
2327
+ this.datasets = [];
2328
+
2329
+ //Set up tooltip events on the chart
2330
+ if (this.options.showTooltips){
2331
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2332
+ var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
2333
+
2334
+ this.eachBars(function(bar){
2335
+ bar.restore(['fillColor', 'strokeColor']);
2336
+ });
2337
+ helpers.each(activeBars, function(activeBar){
2338
+ if (activeBar) {
2339
+ activeBar.fillColor = activeBar.highlightFill;
2340
+ activeBar.strokeColor = activeBar.highlightStroke;
2341
+ }
2342
+ });
2343
+ this.showTooltip(activeBars);
2344
+ });
2345
+ }
2346
+
2347
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
2348
+ this.BarClass = Chart.Rectangle.extend({
2349
+ strokeWidth : this.options.barStrokeWidth,
2350
+ showStroke : this.options.barShowStroke,
2351
+ ctx : this.chart.ctx
2352
+ });
2353
+
2354
+ //Iterate through each of the datasets, and build this into a property of the chart
2355
+ helpers.each(data.datasets,function(dataset,datasetIndex){
2356
+
2357
+ var datasetObject = {
2358
+ label : dataset.label || null,
2359
+ fillColor : dataset.fillColor,
2360
+ strokeColor : dataset.strokeColor,
2361
+ bars : []
2362
+ };
2363
+
2364
+ this.datasets.push(datasetObject);
2365
+
2366
+ helpers.each(dataset.data,function(dataPoint,index){
2367
+ //Add a new point for each piece of data, passing any required data to draw.
2368
+ datasetObject.bars.push(new this.BarClass({
2369
+ value : dataPoint,
2370
+ label : data.labels[index],
2371
+ datasetLabel: dataset.label,
2372
+ strokeColor : (typeof dataset.strokeColor != 'string') ? dataset.strokeColor[index] : dataset.strokeColor,
2373
+ fillColor : (typeof dataset.fillColor != 'string') ? dataset.fillColor[index] : dataset.fillColor,
2374
+ highlightFill : (dataset.highlightFill) ? (typeof dataset.highlightFill != 'string') ? dataset.highlightFill[index] : dataset.highlightFill : (typeof dataset.fillColor != 'string') ? dataset.fillColor[index] : dataset.fillColor,
2375
+ highlightStroke : (dataset.highlightStroke) ? (typeof dataset.highlightStroke != 'string') ? dataset.highlightStroke[index] : dataset.highlightStroke : (typeof dataset.strokeColor != 'string') ? dataset.strokeColor[index] : dataset.strokeColor
2376
+ }));
2377
+ },this);
2378
+
2379
+ },this);
2380
+
2381
+ this.buildScale(data.labels);
2382
+
2383
+ this.BarClass.prototype.base = this.scale.endPoint;
2384
+
2385
+ this.eachBars(function(bar, index, datasetIndex){
2386
+ helpers.extend(bar, {
2387
+ width : this.scale.calculateBarWidth(this.datasets.length),
2388
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2389
+ y: this.scale.endPoint
2390
+ });
2391
+ bar.save();
2392
+ }, this);
2393
+
2394
+ this.render();
2395
+ },
2396
+ update : function(){
2397
+ this.scale.update();
2398
+ // Reset any highlight colours before updating.
2399
+ helpers.each(this.activeElements, function(activeElement){
2400
+ activeElement.restore(['fillColor', 'strokeColor']);
2401
+ });
2402
+
2403
+ this.eachBars(function(bar){
2404
+ bar.save();
2405
+ });
2406
+ this.render();
2407
+ },
2408
+ eachBars : function(callback){
2409
+ helpers.each(this.datasets,function(dataset, datasetIndex){
2410
+ helpers.each(dataset.bars, callback, this, datasetIndex);
2411
+ },this);
2412
+ },
2413
+ getBarsAtEvent : function(e){
2414
+ var barsArray = [],
2415
+ eventPosition = helpers.getRelativePosition(e),
2416
+ datasetIterator = function(dataset){
2417
+ barsArray.push(dataset.bars[barIndex]);
2418
+ },
2419
+ barIndex;
2420
+
2421
+ for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
2422
+ for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
2423
+ if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
2424
+ helpers.each(this.datasets, datasetIterator);
2425
+ return barsArray;
2426
+ }
2427
+ }
2428
+ }
2429
+
2430
+ return barsArray;
2431
+ },
2432
+ buildScale : function(labels){
2433
+ var self = this;
2434
+
2435
+ var dataTotal = function(){
2436
+ var values = [];
2437
+ self.eachBars(function(bar){
2438
+ values.push(bar.value);
2439
+ });
2440
+ return values;
2441
+ };
2442
+
2443
+ var scaleOptions = {
2444
+ templateString : this.options.scaleLabel,
2445
+ height : this.chart.height,
2446
+ width : this.chart.width,
2447
+ ctx : this.chart.ctx,
2448
+ textColor : this.options.scaleFontColor,
2449
+ fontSize : this.options.scaleFontSize,
2450
+ fontStyle : this.options.scaleFontStyle,
2451
+ fontFamily : this.options.scaleFontFamily,
2452
+ valuesCount : labels.length,
2453
+ beginAtZero : this.options.scaleBeginAtZero,
2454
+ integersOnly : this.options.scaleIntegersOnly,
2455
+ calculateYRange: function(currentHeight){
2456
+ var updatedRanges = helpers.calculateScaleRange(
2457
+ dataTotal(),
2458
+ currentHeight,
2459
+ this.fontSize,
2460
+ this.beginAtZero,
2461
+ this.integersOnly
2462
+ );
2463
+ helpers.extend(this, updatedRanges);
2464
+ },
2465
+ xLabels : labels,
2466
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2467
+ lineWidth : this.options.scaleLineWidth,
2468
+ lineColor : this.options.scaleLineColor,
2469
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
2470
+ showVerticalLines : this.options.scaleShowVerticalLines,
2471
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2472
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2473
+ padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
2474
+ showLabels : this.options.scaleShowLabels,
2475
+ display : this.options.showScale
2476
+ };
2477
+
2478
+ if (this.options.scaleOverride){
2479
+ helpers.extend(scaleOptions, {
2480
+ calculateYRange: helpers.noop,
2481
+ steps: this.options.scaleSteps,
2482
+ stepValue: this.options.scaleStepWidth,
2483
+ min: this.options.scaleStartValue,
2484
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2485
+ });
2486
+ }
2487
+
2488
+ this.scale = new this.ScaleClass(scaleOptions);
2489
+ },
2490
+ addData : function(valuesArray,label){
2491
+ //Map the values array for each of the datasets
2492
+ helpers.each(valuesArray,function(value,datasetIndex){
2493
+ //Add a new point for each piece of data, passing any required data to draw.
2494
+ this.datasets[datasetIndex].bars.push(new this.BarClass({
2495
+ value : value,
2496
+ label : label,
2497
+ datasetLabel: this.datasets[datasetIndex].label,
2498
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
2499
+ y: this.scale.endPoint,
2500
+ width : this.scale.calculateBarWidth(this.datasets.length),
2501
+ base : this.scale.endPoint,
2502
+ strokeColor : this.datasets[datasetIndex].strokeColor,
2503
+ fillColor : this.datasets[datasetIndex].fillColor
2504
+ }));
2505
+ },this);
2506
+
2507
+ this.scale.addXLabel(label);
2508
+ //Then re-render the chart.
2509
+ this.update();
2510
+ },
2511
+ removeData : function(){
2512
+ this.scale.removeXLabel();
2513
+ //Then re-render the chart.
2514
+ helpers.each(this.datasets,function(dataset){
2515
+ dataset.bars.shift();
2516
+ },this);
2517
+ this.update();
2518
+ },
2519
+ reflow : function(){
2520
+ helpers.extend(this.BarClass.prototype,{
2521
+ y: this.scale.endPoint,
2522
+ base : this.scale.endPoint
2523
+ });
2524
+ var newScaleProps = helpers.extend({
2525
+ height : this.chart.height,
2526
+ width : this.chart.width
2527
+ });
2528
+ this.scale.update(newScaleProps);
2529
+ },
2530
+ draw : function(ease){
2531
+ var easingDecimal = ease || 1;
2532
+ this.clear();
2533
+
2534
+ var ctx = this.chart.ctx;
2535
+
2536
+ this.scale.draw(easingDecimal);
2537
+
2538
+ //Draw all the bars for each dataset
2539
+ helpers.each(this.datasets,function(dataset,datasetIndex){
2540
+ helpers.each(dataset.bars,function(bar,index){
2541
+ if (bar.hasValue()){
2542
+ bar.base = this.scale.endPoint;
2543
+ //Transition then draw
2544
+ bar.transition({
2545
+ x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2546
+ y : this.scale.calculateY(bar.value),
2547
+ width : this.scale.calculateBarWidth(this.datasets.length)
2548
+ }, easingDecimal).draw();
2549
+ }
2550
+ },this);
2551
+
2552
+ },this);
2553
+ }
2554
+ });
2555
+
2556
+
2557
+ }).call(this);
2558
+
2559
+ (function(){
2560
+ "use strict";
2561
+
2562
+ var root = this,
2563
+ Chart = root.Chart,
2564
+ //Cache a local reference to Chart.helpers
2565
+ helpers = Chart.helpers;
2566
+
2567
+ var defaultConfig = {
2568
+ //Boolean - Whether we should show a stroke on each segment
2569
+ segmentShowStroke : true,
2570
+
2571
+ //String - The colour of each segment stroke
2572
+ segmentStrokeColor : "#fff",
2573
+
2574
+ //Number - The width of each segment stroke
2575
+ segmentStrokeWidth : 2,
2576
+
2577
+ //The percentage of the chart that we cut out of the middle.
2578
+ percentageInnerCutout : 50,
2579
+
2580
+ //Number - Amount of animation steps
2581
+ animationSteps : 100,
2582
+
2583
+ //String - Animation easing effect
2584
+ animationEasing : "easeOutBounce",
2585
+
2586
+ //Boolean - Whether we animate the rotation of the Doughnut
2587
+ animateRotate : true,
2588
+
2589
+ //Boolean - Whether we animate scaling the Doughnut from the centre
2590
+ animateScale : false,
2591
+
2592
+ //String - A legend template
2593
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"><%if(segments[i].label){%><%=segments[i].label%><%}%></span></li><%}%></ul>"
2594
+
2595
+ };
2596
+
2597
+ Chart.Type.extend({
2598
+ //Passing in a name registers this chart in the Chart namespace
2599
+ name: "Doughnut",
2600
+ //Providing a defaults will also register the defaults in the chart namespace
2601
+ defaults : defaultConfig,
2602
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
2603
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
2604
+ initialize: function(data){
2605
+
2606
+ //Declare segments as a static property to prevent inheriting across the Chart type prototype
2607
+ this.segments = [];
2608
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
2609
+
2610
+ this.SegmentArc = Chart.Arc.extend({
2611
+ ctx : this.chart.ctx,
2612
+ x : this.chart.width/2,
2613
+ y : this.chart.height/2
2614
+ });
2615
+
2616
+ //Set up tooltip events on the chart
2617
+ if (this.options.showTooltips){
2618
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2619
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
2620
+
2621
+ helpers.each(this.segments,function(segment){
2622
+ segment.restore(["fillColor"]);
2623
+ });
2624
+ helpers.each(activeSegments,function(activeSegment){
2625
+ activeSegment.fillColor = activeSegment.highlightColor;
2626
+ });
2627
+ this.showTooltip(activeSegments);
2628
+ });
2629
+ }
2630
+ this.calculateTotal(data);
2631
+
2632
+ helpers.each(data,function(datapoint, index){
2633
+ if (!datapoint.color) {
2634
+ datapoint.color = 'hsl(' + (360 * index / data.length) + ', 100%, 50%)';
2635
+ }
2636
+ this.addData(datapoint, index, true);
2637
+ },this);
2638
+
2639
+ this.render();
2640
+ },
2641
+ getSegmentsAtEvent : function(e){
2642
+ var segmentsArray = [];
2643
+
2644
+ var location = helpers.getRelativePosition(e);
2645
+
2646
+ helpers.each(this.segments,function(segment){
2647
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
2648
+ },this);
2649
+ return segmentsArray;
2650
+ },
2651
+ addData : function(segment, atIndex, silent){
2652
+ var index = atIndex !== undefined ? atIndex : this.segments.length;
2653
+ if ( typeof(segment.color) === "undefined" ) {
2654
+ segment.color = Chart.defaults.global.segmentColorDefault[index % Chart.defaults.global.segmentColorDefault.length];
2655
+ segment.highlight = Chart.defaults.global.segmentHighlightColorDefaults[index % Chart.defaults.global.segmentHighlightColorDefaults.length];
2656
+ }
2657
+ this.segments.splice(index, 0, new this.SegmentArc({
2658
+ value : segment.value,
2659
+ outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
2660
+ innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
2661
+ fillColor : segment.color,
2662
+ highlightColor : segment.highlight || segment.color,
2663
+ showStroke : this.options.segmentShowStroke,
2664
+ strokeWidth : this.options.segmentStrokeWidth,
2665
+ strokeColor : this.options.segmentStrokeColor,
2666
+ startAngle : Math.PI * 1.5,
2667
+ circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
2668
+ label : segment.label
2669
+ }));
2670
+ if (!silent){
2671
+ this.reflow();
2672
+ this.update();
2673
+ }
2674
+ },
2675
+ calculateCircumference : function(value) {
2676
+ if ( this.total > 0 ) {
2677
+ return (Math.PI*2)*(value / this.total);
2678
+ } else {
2679
+ return 0;
2680
+ }
2681
+ },
2682
+ calculateTotal : function(data){
2683
+ this.total = 0;
2684
+ helpers.each(data,function(segment){
2685
+ this.total += Math.abs(segment.value);
2686
+ },this);
2687
+ },
2688
+ update : function(){
2689
+ this.calculateTotal(this.segments);
2690
+
2691
+ // Reset any highlight colours before updating.
2692
+ helpers.each(this.activeElements, function(activeElement){
2693
+ activeElement.restore(['fillColor']);
2694
+ });
2695
+
2696
+ helpers.each(this.segments,function(segment){
2697
+ segment.save();
2698
+ });
2699
+ this.render();
2700
+ },
2701
+
2702
+ removeData: function(atIndex){
2703
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
2704
+ this.segments.splice(indexToDelete, 1);
2705
+ this.reflow();
2706
+ this.update();
2707
+ },
2708
+
2709
+ reflow : function(){
2710
+ helpers.extend(this.SegmentArc.prototype,{
2711
+ x : this.chart.width/2,
2712
+ y : this.chart.height/2
2713
+ });
2714
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
2715
+ helpers.each(this.segments, function(segment){
2716
+ segment.update({
2717
+ outerRadius : this.outerRadius,
2718
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2719
+ });
2720
+ }, this);
2721
+ },
2722
+ draw : function(easeDecimal){
2723
+ var animDecimal = (easeDecimal) ? easeDecimal : 1;
2724
+ this.clear();
2725
+ helpers.each(this.segments,function(segment,index){
2726
+ segment.transition({
2727
+ circumference : this.calculateCircumference(segment.value),
2728
+ outerRadius : this.outerRadius,
2729
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2730
+ },animDecimal);
2731
+
2732
+ segment.endAngle = segment.startAngle + segment.circumference;
2733
+
2734
+ segment.draw();
2735
+ if (index === 0){
2736
+ segment.startAngle = Math.PI * 1.5;
2737
+ }
2738
+ //Check to see if it's the last segment, if not get the next and update the start angle
2739
+ if (index < this.segments.length-1){
2740
+ this.segments[index+1].startAngle = segment.endAngle;
2741
+ }
2742
+ },this);
2743
+
2744
+ }
2745
+ });
2746
+
2747
+ Chart.types.Doughnut.extend({
2748
+ name : "Pie",
2749
+ defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
2750
+ });
2751
+
2752
+ }).call(this);
2753
+
2754
+ (function(){
2755
+ "use strict";
2756
+
2757
+ var root = this,
2758
+ Chart = root.Chart,
2759
+ helpers = Chart.helpers;
2760
+
2761
+ var defaultConfig = {
2762
+
2763
+ ///Boolean - Whether grid lines are shown across the chart
2764
+ scaleShowGridLines : true,
2765
+
2766
+ //String - Colour of the grid lines
2767
+ scaleGridLineColor : "rgba(0,0,0,.05)",
2768
+
2769
+ //Number - Width of the grid lines
2770
+ scaleGridLineWidth : 1,
2771
+
2772
+ //Boolean - Whether to show horizontal lines (except X axis)
2773
+ scaleShowHorizontalLines: true,
2774
+
2775
+ //Boolean - Whether to show vertical lines (except Y axis)
2776
+ scaleShowVerticalLines: true,
2777
+
2778
+ //Boolean - Whether the line is curved between points
2779
+ bezierCurve : true,
2780
+
2781
+ //Number - Tension of the bezier curve between points
2782
+ bezierCurveTension : 0.4,
2783
+
2784
+ //Boolean - Whether to show a dot for each point
2785
+ pointDot : true,
2786
+
2787
+ //Number - Radius of each point dot in pixels
2788
+ pointDotRadius : 4,
2789
+
2790
+ //Number - Pixel width of point dot stroke
2791
+ pointDotStrokeWidth : 1,
2792
+
2793
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
2794
+ pointHitDetectionRadius : 20,
2795
+
2796
+ //Boolean - Whether to show a stroke for datasets
2797
+ datasetStroke : true,
2798
+
2799
+ //Number - Pixel width of dataset stroke
2800
+ datasetStrokeWidth : 2,
2801
+
2802
+ //Boolean - Whether to fill the dataset with a colour
2803
+ datasetFill : true,
2804
+
2805
+ //String - A legend template
2806
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>",
2807
+
2808
+ //Boolean - Whether to horizontally center the label and point dot inside the grid
2809
+ offsetGridLines : false
2810
+
2811
+ };
2812
+
2813
+
2814
+ Chart.Type.extend({
2815
+ name: "Line",
2816
+ defaults : defaultConfig,
2817
+ initialize: function(data){
2818
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
2819
+ this.PointClass = Chart.Point.extend({
2820
+ offsetGridLines : this.options.offsetGridLines,
2821
+ strokeWidth : this.options.pointDotStrokeWidth,
2822
+ radius : this.options.pointDotRadius,
2823
+ display: this.options.pointDot,
2824
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
2825
+ ctx : this.chart.ctx,
2826
+ inRange : function(mouseX){
2827
+ return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
2828
+ }
2829
+ });
2830
+
2831
+ this.datasets = [];
2832
+
2833
+ //Set up tooltip events on the chart
2834
+ if (this.options.showTooltips){
2835
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2836
+ var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
2837
+ this.eachPoints(function(point){
2838
+ point.restore(['fillColor', 'strokeColor']);
2839
+ });
2840
+ helpers.each(activePoints, function(activePoint){
2841
+ activePoint.fillColor = activePoint.highlightFill;
2842
+ activePoint.strokeColor = activePoint.highlightStroke;
2843
+ });
2844
+ this.showTooltip(activePoints);
2845
+ });
2846
+ }
2847
+
2848
+ //Iterate through each of the datasets, and build this into a property of the chart
2849
+ helpers.each(data.datasets,function(dataset){
2850
+
2851
+ var datasetObject = {
2852
+ label : dataset.label || null,
2853
+ fillColor : dataset.fillColor,
2854
+ strokeColor : dataset.strokeColor,
2855
+ pointColor : dataset.pointColor,
2856
+ pointStrokeColor : dataset.pointStrokeColor,
2857
+ points : []
2858
+ };
2859
+
2860
+ this.datasets.push(datasetObject);
2861
+
2862
+
2863
+ helpers.each(dataset.data,function(dataPoint,index){
2864
+ //Add a new point for each piece of data, passing any required data to draw.
2865
+ datasetObject.points.push(new this.PointClass({
2866
+ value : dataPoint,
2867
+ label : data.labels[index],
2868
+ datasetLabel: dataset.label,
2869
+ strokeColor : dataset.pointStrokeColor,
2870
+ fillColor : dataset.pointColor,
2871
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
2872
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
2873
+ }));
2874
+ },this);
2875
+
2876
+ this.buildScale(data.labels);
2877
+
2878
+
2879
+ this.eachPoints(function(point, index){
2880
+ helpers.extend(point, {
2881
+ x: this.scale.calculateX(index),
2882
+ y: this.scale.endPoint
2883
+ });
2884
+ point.save();
2885
+ }, this);
2886
+
2887
+ },this);
2888
+
2889
+
2890
+ this.render();
2891
+ },
2892
+ update : function(){
2893
+ this.scale.update();
2894
+ // Reset any highlight colours before updating.
2895
+ helpers.each(this.activeElements, function(activeElement){
2896
+ activeElement.restore(['fillColor', 'strokeColor']);
2897
+ });
2898
+ this.eachPoints(function(point){
2899
+ point.save();
2900
+ });
2901
+ this.render();
2902
+ },
2903
+ eachPoints : function(callback){
2904
+ helpers.each(this.datasets,function(dataset){
2905
+ helpers.each(dataset.points,callback,this);
2906
+ },this);
2907
+ },
2908
+ getPointsAtEvent : function(e){
2909
+ var pointsArray = [],
2910
+ eventPosition = helpers.getRelativePosition(e);
2911
+ helpers.each(this.datasets,function(dataset){
2912
+ helpers.each(dataset.points,function(point){
2913
+ if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
2914
+ });
2915
+ },this);
2916
+ return pointsArray;
2917
+ },
2918
+ buildScale : function(labels){
2919
+ var self = this;
2920
+
2921
+ var dataTotal = function(){
2922
+ var values = [];
2923
+ self.eachPoints(function(point){
2924
+ values.push(point.value);
2925
+ });
2926
+
2927
+ return values;
2928
+ };
2929
+
2930
+ var scaleOptions = {
2931
+ templateString : this.options.scaleLabel,
2932
+ height : this.chart.height,
2933
+ width : this.chart.width,
2934
+ ctx : this.chart.ctx,
2935
+ textColor : this.options.scaleFontColor,
2936
+ offsetGridLines : this.options.offsetGridLines,
2937
+ fontSize : this.options.scaleFontSize,
2938
+ fontStyle : this.options.scaleFontStyle,
2939
+ fontFamily : this.options.scaleFontFamily,
2940
+ valuesCount : labels.length,
2941
+ beginAtZero : this.options.scaleBeginAtZero,
2942
+ integersOnly : this.options.scaleIntegersOnly,
2943
+ calculateYRange : function(currentHeight){
2944
+ var updatedRanges = helpers.calculateScaleRange(
2945
+ dataTotal(),
2946
+ currentHeight,
2947
+ this.fontSize,
2948
+ this.beginAtZero,
2949
+ this.integersOnly
2950
+ );
2951
+ helpers.extend(this, updatedRanges);
2952
+ },
2953
+ xLabels : labels,
2954
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2955
+ lineWidth : this.options.scaleLineWidth,
2956
+ lineColor : this.options.scaleLineColor,
2957
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
2958
+ showVerticalLines : this.options.scaleShowVerticalLines,
2959
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2960
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2961
+ padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
2962
+ showLabels : this.options.scaleShowLabels,
2963
+ display : this.options.showScale
2964
+ };
2965
+
2966
+ if (this.options.scaleOverride){
2967
+ helpers.extend(scaleOptions, {
2968
+ calculateYRange: helpers.noop,
2969
+ steps: this.options.scaleSteps,
2970
+ stepValue: this.options.scaleStepWidth,
2971
+ min: this.options.scaleStartValue,
2972
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2973
+ });
2974
+ }
2975
+
2976
+
2977
+ this.scale = new Chart.Scale(scaleOptions);
2978
+ },
2979
+ addData : function(valuesArray,label){
2980
+ //Map the values array for each of the datasets
2981
+
2982
+ helpers.each(valuesArray,function(value,datasetIndex){
2983
+ //Add a new point for each piece of data, passing any required data to draw.
2984
+ this.datasets[datasetIndex].points.push(new this.PointClass({
2985
+ value : value,
2986
+ label : label,
2987
+ datasetLabel: this.datasets[datasetIndex].label,
2988
+ x: this.scale.calculateX(this.scale.valuesCount+1),
2989
+ y: this.scale.endPoint,
2990
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
2991
+ fillColor : this.datasets[datasetIndex].pointColor
2992
+ }));
2993
+ },this);
2994
+
2995
+ this.scale.addXLabel(label);
2996
+ //Then re-render the chart.
2997
+ this.update();
2998
+ },
2999
+ removeData : function(){
3000
+ this.scale.removeXLabel();
3001
+ //Then re-render the chart.
3002
+ helpers.each(this.datasets,function(dataset){
3003
+ dataset.points.shift();
3004
+ },this);
3005
+ this.update();
3006
+ },
3007
+ reflow : function(){
3008
+ var newScaleProps = helpers.extend({
3009
+ height : this.chart.height,
3010
+ width : this.chart.width
3011
+ });
3012
+ this.scale.update(newScaleProps);
3013
+ },
3014
+ draw : function(ease){
3015
+ var easingDecimal = ease || 1;
3016
+ this.clear();
3017
+
3018
+ var ctx = this.chart.ctx;
3019
+
3020
+ // Some helper methods for getting the next/prev points
3021
+ var hasValue = function(item){
3022
+ return item.value !== null;
3023
+ },
3024
+ nextPoint = function(point, collection, index){
3025
+ return helpers.findNextWhere(collection, hasValue, index) || point;
3026
+ },
3027
+ previousPoint = function(point, collection, index){
3028
+ return helpers.findPreviousWhere(collection, hasValue, index) || point;
3029
+ };
3030
+
3031
+ if (!this.scale) return;
3032
+ this.scale.draw(easingDecimal);
3033
+
3034
+
3035
+ helpers.each(this.datasets,function(dataset){
3036
+ var pointsWithValues = helpers.where(dataset.points, hasValue);
3037
+
3038
+ //Transition each point first so that the line and point drawing isn't out of sync
3039
+ //We can use this extra loop to calculate the control points of this dataset also in this loop
3040
+
3041
+ helpers.each(dataset.points, function(point, index){
3042
+ if (point.hasValue()){
3043
+ point.transition({
3044
+ y : this.scale.calculateY(point.value),
3045
+ x : this.scale.calculateX(index)
3046
+ }, easingDecimal);
3047
+ }
3048
+ },this);
3049
+
3050
+
3051
+ // Control points need to be calculated in a separate loop, because we need to know the current x/y of the point
3052
+ // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
3053
+ if (this.options.bezierCurve){
3054
+ helpers.each(pointsWithValues, function(point, index){
3055
+ var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
3056
+ point.controlPoints = helpers.splineCurve(
3057
+ previousPoint(point, pointsWithValues, index),
3058
+ point,
3059
+ nextPoint(point, pointsWithValues, index),
3060
+ tension
3061
+ );
3062
+
3063
+ // Prevent the bezier going outside of the bounds of the graph
3064
+
3065
+ // Cap puter bezier handles to the upper/lower scale bounds
3066
+ if (point.controlPoints.outer.y > this.scale.endPoint){
3067
+ point.controlPoints.outer.y = this.scale.endPoint;
3068
+ }
3069
+ else if (point.controlPoints.outer.y < this.scale.startPoint){
3070
+ point.controlPoints.outer.y = this.scale.startPoint;
3071
+ }
3072
+
3073
+ // Cap inner bezier handles to the upper/lower scale bounds
3074
+ if (point.controlPoints.inner.y > this.scale.endPoint){
3075
+ point.controlPoints.inner.y = this.scale.endPoint;
3076
+ }
3077
+ else if (point.controlPoints.inner.y < this.scale.startPoint){
3078
+ point.controlPoints.inner.y = this.scale.startPoint;
3079
+ }
3080
+ },this);
3081
+ }
3082
+
3083
+
3084
+ //Draw the line between all the points
3085
+ ctx.lineWidth = this.options.datasetStrokeWidth;
3086
+ ctx.strokeStyle = dataset.strokeColor;
3087
+ ctx.beginPath();
3088
+
3089
+ helpers.each(pointsWithValues, function(point, index){
3090
+ if (index === 0){
3091
+ ctx.moveTo(point.x, point.y);
3092
+ }
3093
+ else{
3094
+ if(this.options.bezierCurve){
3095
+ var previous = previousPoint(point, pointsWithValues, index);
3096
+
3097
+ ctx.bezierCurveTo(
3098
+ previous.controlPoints.outer.x,
3099
+ previous.controlPoints.outer.y,
3100
+ point.controlPoints.inner.x,
3101
+ point.controlPoints.inner.y,
3102
+ point.x,
3103
+ point.y
3104
+ );
3105
+ }
3106
+ else{
3107
+ ctx.lineTo(point.x,point.y);
3108
+ }
3109
+ }
3110
+ }, this);
3111
+
3112
+ if (this.options.datasetStroke) {
3113
+ ctx.stroke();
3114
+ }
3115
+
3116
+ if (this.options.datasetFill && pointsWithValues.length > 0){
3117
+ //Round off the line by going to the base of the chart, back to the start, then fill.
3118
+ ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
3119
+ ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
3120
+ ctx.fillStyle = dataset.fillColor;
3121
+ ctx.closePath();
3122
+ ctx.fill();
3123
+ }
3124
+
3125
+ //Now draw the points over the line
3126
+ //A little inefficient double looping, but better than the line
3127
+ //lagging behind the point positions
3128
+ helpers.each(pointsWithValues,function(point){
3129
+ point.draw();
3130
+ });
3131
+ },this);
3132
+ }
3133
+ });
3134
+
3135
+
3136
+ }).call(this);
3137
+
3138
+ (function(){
3139
+ "use strict";
3140
+
3141
+ var root = this,
3142
+ Chart = root.Chart,
3143
+ //Cache a local reference to Chart.helpers
3144
+ helpers = Chart.helpers;
3145
+
3146
+ var defaultConfig = {
3147
+ //Boolean - Show a backdrop to the scale label
3148
+ scaleShowLabelBackdrop : true,
3149
+
3150
+ //String - The colour of the label backdrop
3151
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
3152
+
3153
+ // Boolean - Whether the scale should begin at zero
3154
+ scaleBeginAtZero : true,
3155
+
3156
+ //Number - The backdrop padding above & below the label in pixels
3157
+ scaleBackdropPaddingY : 2,
3158
+
3159
+ //Number - The backdrop padding to the side of the label in pixels
3160
+ scaleBackdropPaddingX : 2,
3161
+
3162
+ //Boolean - Show line for each value in the scale
3163
+ scaleShowLine : true,
3164
+
3165
+ //Boolean - Stroke a line around each segment in the chart
3166
+ segmentShowStroke : true,
3167
+
3168
+ //String - The colour of the stroke on each segment.
3169
+ segmentStrokeColor : "#fff",
3170
+
3171
+ //Number - The width of the stroke value in pixels
3172
+ segmentStrokeWidth : 2,
3173
+
3174
+ //Number - Amount of animation steps
3175
+ animationSteps : 100,
3176
+
3177
+ //String - Animation easing effect.
3178
+ animationEasing : "easeOutBounce",
3179
+
3180
+ //Boolean - Whether to animate the rotation of the chart
3181
+ animateRotate : true,
3182
+
3183
+ //Boolean - Whether to animate scaling the chart from the centre
3184
+ animateScale : false,
3185
+
3186
+ //String - A legend template
3187
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"><%if(segments[i].label){%><%=segments[i].label%><%}%></span></li><%}%></ul>"
3188
+ };
3189
+
3190
+
3191
+ Chart.Type.extend({
3192
+ //Passing in a name registers this chart in the Chart namespace
3193
+ name: "PolarArea",
3194
+ //Providing a defaults will also register the defaults in the chart namespace
3195
+ defaults : defaultConfig,
3196
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
3197
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
3198
+ initialize: function(data){
3199
+ this.segments = [];
3200
+ //Declare segment class as a chart instance specific class, so it can share props for this instance
3201
+ this.SegmentArc = Chart.Arc.extend({
3202
+ showStroke : this.options.segmentShowStroke,
3203
+ strokeWidth : this.options.segmentStrokeWidth,
3204
+ strokeColor : this.options.segmentStrokeColor,
3205
+ ctx : this.chart.ctx,
3206
+ innerRadius : 0,
3207
+ x : this.chart.width/2,
3208
+ y : this.chart.height/2
3209
+ });
3210
+ this.scale = new Chart.RadialScale({
3211
+ display: this.options.showScale,
3212
+ fontStyle: this.options.scaleFontStyle,
3213
+ fontSize: this.options.scaleFontSize,
3214
+ fontFamily: this.options.scaleFontFamily,
3215
+ fontColor: this.options.scaleFontColor,
3216
+ showLabels: this.options.scaleShowLabels,
3217
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3218
+ backdropColor: this.options.scaleBackdropColor,
3219
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
3220
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
3221
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
3222
+ lineColor: this.options.scaleLineColor,
3223
+ lineArc: true,
3224
+ width: this.chart.width,
3225
+ height: this.chart.height,
3226
+ xCenter: this.chart.width/2,
3227
+ yCenter: this.chart.height/2,
3228
+ ctx : this.chart.ctx,
3229
+ templateString: this.options.scaleLabel,
3230
+ valuesCount: data.length
3231
+ });
3232
+
3233
+ this.updateScaleRange(data);
3234
+
3235
+ this.scale.update();
3236
+
3237
+ helpers.each(data,function(segment,index){
3238
+ this.addData(segment,index,true);
3239
+ },this);
3240
+
3241
+ //Set up tooltip events on the chart
3242
+ if (this.options.showTooltips){
3243
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3244
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
3245
+ helpers.each(this.segments,function(segment){
3246
+ segment.restore(["fillColor"]);
3247
+ });
3248
+ helpers.each(activeSegments,function(activeSegment){
3249
+ activeSegment.fillColor = activeSegment.highlightColor;
3250
+ });
3251
+ this.showTooltip(activeSegments);
3252
+ });
3253
+ }
3254
+
3255
+ this.render();
3256
+ },
3257
+ getSegmentsAtEvent : function(e){
3258
+ var segmentsArray = [];
3259
+
3260
+ var location = helpers.getRelativePosition(e);
3261
+
3262
+ helpers.each(this.segments,function(segment){
3263
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
3264
+ },this);
3265
+ return segmentsArray;
3266
+ },
3267
+ addData : function(segment, atIndex, silent){
3268
+ var index = atIndex || this.segments.length;
3269
+
3270
+ this.segments.splice(index, 0, new this.SegmentArc({
3271
+ fillColor: segment.color,
3272
+ highlightColor: segment.highlight || segment.color,
3273
+ label: segment.label,
3274
+ value: segment.value,
3275
+ outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
3276
+ circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
3277
+ startAngle: Math.PI * 1.5
3278
+ }));
3279
+ if (!silent){
3280
+ this.reflow();
3281
+ this.update();
3282
+ }
3283
+ },
3284
+ removeData: function(atIndex){
3285
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
3286
+ this.segments.splice(indexToDelete, 1);
3287
+ this.reflow();
3288
+ this.update();
3289
+ },
3290
+ calculateTotal: function(data){
3291
+ this.total = 0;
3292
+ helpers.each(data,function(segment){
3293
+ this.total += segment.value;
3294
+ },this);
3295
+ this.scale.valuesCount = this.segments.length;
3296
+ },
3297
+ updateScaleRange: function(datapoints){
3298
+ var valuesArray = [];
3299
+ helpers.each(datapoints,function(segment){
3300
+ valuesArray.push(segment.value);
3301
+ });
3302
+
3303
+ var scaleSizes = (this.options.scaleOverride) ?
3304
+ {
3305
+ steps: this.options.scaleSteps,
3306
+ stepValue: this.options.scaleStepWidth,
3307
+ min: this.options.scaleStartValue,
3308
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3309
+ } :
3310
+ helpers.calculateScaleRange(
3311
+ valuesArray,
3312
+ helpers.min([this.chart.width, this.chart.height])/2,
3313
+ this.options.scaleFontSize,
3314
+ this.options.scaleBeginAtZero,
3315
+ this.options.scaleIntegersOnly
3316
+ );
3317
+
3318
+ helpers.extend(
3319
+ this.scale,
3320
+ scaleSizes,
3321
+ {
3322
+ size: helpers.min([this.chart.width, this.chart.height]),
3323
+ xCenter: this.chart.width/2,
3324
+ yCenter: this.chart.height/2
3325
+ }
3326
+ );
3327
+
3328
+ },
3329
+ update : function(){
3330
+ this.calculateTotal(this.segments);
3331
+
3332
+ helpers.each(this.segments,function(segment){
3333
+ segment.save();
3334
+ });
3335
+
3336
+ this.reflow();
3337
+ this.render();
3338
+ },
3339
+ reflow : function(){
3340
+ helpers.extend(this.SegmentArc.prototype,{
3341
+ x : this.chart.width/2,
3342
+ y : this.chart.height/2
3343
+ });
3344
+ this.updateScaleRange(this.segments);
3345
+ this.scale.update();
3346
+
3347
+ helpers.extend(this.scale,{
3348
+ xCenter: this.chart.width/2,
3349
+ yCenter: this.chart.height/2
3350
+ });
3351
+
3352
+ helpers.each(this.segments, function(segment){
3353
+ segment.update({
3354
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
3355
+ });
3356
+ }, this);
3357
+
3358
+ },
3359
+ draw : function(ease){
3360
+ var easingDecimal = ease || 1;
3361
+ //Clear & draw the canvas
3362
+ this.clear();
3363
+ helpers.each(this.segments,function(segment, index){
3364
+ segment.transition({
3365
+ circumference : this.scale.getCircumference(),
3366
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
3367
+ },easingDecimal);
3368
+
3369
+ segment.endAngle = segment.startAngle + segment.circumference;
3370
+
3371
+ // If we've removed the first segment we need to set the first one to
3372
+ // start at the top.
3373
+ if (index === 0){
3374
+ segment.startAngle = Math.PI * 1.5;
3375
+ }
3376
+
3377
+ //Check to see if it's the last segment, if not get the next and update the start angle
3378
+ if (index < this.segments.length - 1){
3379
+ this.segments[index+1].startAngle = segment.endAngle;
3380
+ }
3381
+ segment.draw();
3382
+ }, this);
3383
+ this.scale.draw();
3384
+ }
3385
+ });
3386
+
3387
+ }).call(this);
3388
+
3389
+ (function(){
3390
+ "use strict";
3391
+
3392
+ var root = this,
3393
+ Chart = root.Chart,
3394
+ helpers = Chart.helpers;
3395
+
3396
+
3397
+
3398
+ Chart.Type.extend({
3399
+ name: "Radar",
3400
+ defaults:{
3401
+ //Boolean - Whether to show lines for each scale point
3402
+ scaleShowLine : true,
3403
+
3404
+ //Boolean - Whether we show the angle lines out of the radar
3405
+ angleShowLineOut : true,
3406
+
3407
+ //Boolean - Whether to show labels on the scale
3408
+ scaleShowLabels : false,
3409
+
3410
+ // Boolean - Whether the scale should begin at zero
3411
+ scaleBeginAtZero : true,
3412
+
3413
+ //String - Colour of the angle line
3414
+ angleLineColor : "rgba(0,0,0,.1)",
3415
+
3416
+ //Number - Pixel width of the angle line
3417
+ angleLineWidth : 1,
3418
+
3419
+ //Number - Interval at which to draw angle lines ("every Nth point")
3420
+ angleLineInterval: 1,
3421
+
3422
+ //String - Point label font declaration
3423
+ pointLabelFontFamily : "'Arial'",
3424
+
3425
+ //String - Point label font weight
3426
+ pointLabelFontStyle : "normal",
3427
+
3428
+ //Number - Point label font size in pixels
3429
+ pointLabelFontSize : 10,
3430
+
3431
+ //String - Point label font colour
3432
+ pointLabelFontColor : "#666",
3433
+
3434
+ //Boolean - Whether to show a dot for each point
3435
+ pointDot : true,
3436
+
3437
+ //Number - Radius of each point dot in pixels
3438
+ pointDotRadius : 3,
3439
+
3440
+ //Number - Pixel width of point dot stroke
3441
+ pointDotStrokeWidth : 1,
3442
+
3443
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
3444
+ pointHitDetectionRadius : 20,
3445
+
3446
+ //Boolean - Whether to show a stroke for datasets
3447
+ datasetStroke : true,
3448
+
3449
+ //Number - Pixel width of dataset stroke
3450
+ datasetStrokeWidth : 2,
3451
+
3452
+ //Boolean - Whether to fill the dataset with a colour
3453
+ datasetFill : true,
3454
+
3455
+ //String - A legend template
3456
+ legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>"
3457
+
3458
+ },
3459
+
3460
+ initialize: function(data){
3461
+ this.PointClass = Chart.Point.extend({
3462
+ strokeWidth : this.options.pointDotStrokeWidth,
3463
+ radius : this.options.pointDotRadius,
3464
+ display: this.options.pointDot,
3465
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
3466
+ ctx : this.chart.ctx
3467
+ });
3468
+
3469
+ this.datasets = [];
3470
+
3471
+ this.buildScale(data);
3472
+
3473
+ //Set up tooltip events on the chart
3474
+ if (this.options.showTooltips){
3475
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3476
+ var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
3477
+
3478
+ this.eachPoints(function(point){
3479
+ point.restore(['fillColor', 'strokeColor']);
3480
+ });
3481
+ helpers.each(activePointsCollection, function(activePoint){
3482
+ activePoint.fillColor = activePoint.highlightFill;
3483
+ activePoint.strokeColor = activePoint.highlightStroke;
3484
+ });
3485
+
3486
+ this.showTooltip(activePointsCollection);
3487
+ });
3488
+ }
3489
+
3490
+ //Iterate through each of the datasets, and build this into a property of the chart
3491
+ helpers.each(data.datasets,function(dataset){
3492
+
3493
+ var datasetObject = {
3494
+ label: dataset.label || null,
3495
+ fillColor : dataset.fillColor,
3496
+ strokeColor : dataset.strokeColor,
3497
+ pointColor : dataset.pointColor,
3498
+ pointStrokeColor : dataset.pointStrokeColor,
3499
+ points : []
3500
+ };
3501
+
3502
+ this.datasets.push(datasetObject);
3503
+
3504
+ helpers.each(dataset.data,function(dataPoint,index){
3505
+ //Add a new point for each piece of data, passing any required data to draw.
3506
+ var pointPosition;
3507
+ if (!this.scale.animation){
3508
+ pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
3509
+ }
3510
+ datasetObject.points.push(new this.PointClass({
3511
+ value : dataPoint,
3512
+ label : data.labels[index],
3513
+ datasetLabel: dataset.label,
3514
+ x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
3515
+ y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
3516
+ strokeColor : dataset.pointStrokeColor,
3517
+ fillColor : dataset.pointColor,
3518
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
3519
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
3520
+ }));
3521
+ },this);
3522
+
3523
+ },this);
3524
+
3525
+ this.render();
3526
+ },
3527
+ eachPoints : function(callback){
3528
+ helpers.each(this.datasets,function(dataset){
3529
+ helpers.each(dataset.points,callback,this);
3530
+ },this);
3531
+ },
3532
+
3533
+ getPointsAtEvent : function(evt){
3534
+ var mousePosition = helpers.getRelativePosition(evt),
3535
+ fromCenter = helpers.getAngleFromPoint({
3536
+ x: this.scale.xCenter,
3537
+ y: this.scale.yCenter
3538
+ }, mousePosition);
3539
+
3540
+ var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
3541
+ pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
3542
+ activePointsCollection = [];
3543
+
3544
+ // If we're at the top, make the pointIndex 0 to get the first of the array.
3545
+ if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
3546
+ pointIndex = 0;
3547
+ }
3548
+
3549
+ if (fromCenter.distance <= this.scale.drawingArea){
3550
+ helpers.each(this.datasets, function(dataset){
3551
+ activePointsCollection.push(dataset.points[pointIndex]);
3552
+ });
3553
+ }
3554
+
3555
+ return activePointsCollection;
3556
+ },
3557
+
3558
+ buildScale : function(data){
3559
+ this.scale = new Chart.RadialScale({
3560
+ display: this.options.showScale,
3561
+ fontStyle: this.options.scaleFontStyle,
3562
+ fontSize: this.options.scaleFontSize,
3563
+ fontFamily: this.options.scaleFontFamily,
3564
+ fontColor: this.options.scaleFontColor,
3565
+ showLabels: this.options.scaleShowLabels,
3566
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3567
+ backdropColor: this.options.scaleBackdropColor,
3568
+ backgroundColors: this.options.scaleBackgroundColors,
3569
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
3570
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
3571
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
3572
+ lineColor: this.options.scaleLineColor,
3573
+ angleLineColor : this.options.angleLineColor,
3574
+ angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
3575
+ angleLineInterval: (this.options.angleLineInterval) ? this.options.angleLineInterval : 1,
3576
+ // Point labels at the edge of each line
3577
+ pointLabelFontColor : this.options.pointLabelFontColor,
3578
+ pointLabelFontSize : this.options.pointLabelFontSize,
3579
+ pointLabelFontFamily : this.options.pointLabelFontFamily,
3580
+ pointLabelFontStyle : this.options.pointLabelFontStyle,
3581
+ height : this.chart.height,
3582
+ width: this.chart.width,
3583
+ xCenter: this.chart.width/2,
3584
+ yCenter: this.chart.height/2,
3585
+ ctx : this.chart.ctx,
3586
+ templateString: this.options.scaleLabel,
3587
+ labels: data.labels,
3588
+ valuesCount: data.datasets[0].data.length
3589
+ });
3590
+
3591
+ this.scale.setScaleSize();
3592
+ this.updateScaleRange(data.datasets);
3593
+ this.scale.buildYLabels();
3594
+ },
3595
+ updateScaleRange: function(datasets){
3596
+ var valuesArray = (function(){
3597
+ var totalDataArray = [];
3598
+ helpers.each(datasets,function(dataset){
3599
+ if (dataset.data){
3600
+ totalDataArray = totalDataArray.concat(dataset.data);
3601
+ }
3602
+ else {
3603
+ helpers.each(dataset.points, function(point){
3604
+ totalDataArray.push(point.value);
3605
+ });
3606
+ }
3607
+ });
3608
+ return totalDataArray;
3609
+ })();
3610
+
3611
+
3612
+ var scaleSizes = (this.options.scaleOverride) ?
3613
+ {
3614
+ steps: this.options.scaleSteps,
3615
+ stepValue: this.options.scaleStepWidth,
3616
+ min: this.options.scaleStartValue,
3617
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3618
+ } :
3619
+ helpers.calculateScaleRange(
3620
+ valuesArray,
3621
+ helpers.min([this.chart.width, this.chart.height])/2,
3622
+ this.options.scaleFontSize,
3623
+ this.options.scaleBeginAtZero,
3624
+ this.options.scaleIntegersOnly
3625
+ );
3626
+
3627
+ helpers.extend(
3628
+ this.scale,
3629
+ scaleSizes
3630
+ );
3631
+
3632
+ },
3633
+ addData : function(valuesArray,label){
3634
+ //Map the values array for each of the datasets
3635
+ this.scale.valuesCount++;
3636
+ helpers.each(valuesArray,function(value,datasetIndex){
3637
+ var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
3638
+ this.datasets[datasetIndex].points.push(new this.PointClass({
3639
+ value : value,
3640
+ label : label,
3641
+ datasetLabel: this.datasets[datasetIndex].label,
3642
+ x: pointPosition.x,
3643
+ y: pointPosition.y,
3644
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
3645
+ fillColor : this.datasets[datasetIndex].pointColor
3646
+ }));
3647
+ },this);
3648
+
3649
+ this.scale.labels.push(label);
3650
+
3651
+ this.reflow();
3652
+
3653
+ this.update();
3654
+ },
3655
+ removeData : function(){
3656
+ this.scale.valuesCount--;
3657
+ this.scale.labels.shift();
3658
+ helpers.each(this.datasets,function(dataset){
3659
+ dataset.points.shift();
3660
+ },this);
3661
+ this.reflow();
3662
+ this.update();
3663
+ },
3664
+ update : function(){
3665
+ this.eachPoints(function(point){
3666
+ point.save();
3667
+ });
3668
+ this.reflow();
3669
+ this.render();
3670
+ },
3671
+ reflow: function(){
3672
+ helpers.extend(this.scale, {
3673
+ width : this.chart.width,
3674
+ height: this.chart.height,
3675
+ size : helpers.min([this.chart.width, this.chart.height]),
3676
+ xCenter: this.chart.width/2,
3677
+ yCenter: this.chart.height/2
3678
+ });
3679
+ this.updateScaleRange(this.datasets);
3680
+ this.scale.setScaleSize();
3681
+ this.scale.buildYLabels();
3682
+ },
3683
+ draw : function(ease){
3684
+ var easeDecimal = ease || 1,
3685
+ ctx = this.chart.ctx;
3686
+ this.clear();
3687
+ this.scale.draw();
3688
+
3689
+ helpers.each(this.datasets,function(dataset){
3690
+
3691
+ //Transition each point first so that the line and point drawing isn't out of sync
3692
+ helpers.each(dataset.points,function(point,index){
3693
+ if (point.hasValue()){
3694
+ point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
3695
+ }
3696
+ },this);
3697
+
3698
+
3699
+
3700
+ //Draw the line between all the points
3701
+ ctx.lineWidth = this.options.datasetStrokeWidth;
3702
+ ctx.strokeStyle = dataset.strokeColor;
3703
+ ctx.beginPath();
3704
+ helpers.each(dataset.points,function(point,index){
3705
+ if (index === 0){
3706
+ ctx.moveTo(point.x,point.y);
3707
+ }
3708
+ else{
3709
+ ctx.lineTo(point.x,point.y);
3710
+ }
3711
+ },this);
3712
+ ctx.closePath();
3713
+ ctx.stroke();
3714
+
3715
+ ctx.fillStyle = dataset.fillColor;
3716
+ if(this.options.datasetFill){
3717
+ ctx.fill();
3718
+ }
3719
+ //Now draw the points over the line
3720
+ //A little inefficient double looping, but better than the line
3721
+ //lagging behind the point positions
3722
+ helpers.each(dataset.points,function(point){
3723
+ if (point.hasValue()){
3724
+ point.draw();
3725
+ }
3726
+ });
3727
+
3728
+ },this);
3729
+
3730
+ }
3731
+
3732
+ });
3733
+
3734
+
3735
+
3736
+
3737
+
3738
+ }).call(this);