diff --git a/README.md b/README.md index 623ad80..35994b5 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,13 @@ Fork, hack, send a pull request :) ## Changelog +### 0.2.6 - 17th March 2012 + +- The X labels can be disabled (with `drawXlabels` option, default is true) +- The Y labels can be disabled (with `drawYlabels` option, default is true) +- The automatic data sort can be disabled (with `sortData` option, default is true) +- The options.data can be sorted via custom function (with `sortDataFunction` option, default is null) + ### 0.2.5 - 15th March 2012 - Raw millisecond timestamp support (with `dateFormat` option) diff --git a/examples/custom-sort.html b/examples/custom-sort.html new file mode 100644 index 0000000..54980cd --- /dev/null +++ b/examples/custom-sort.html @@ -0,0 +1,39 @@ + + + + + + + + + + + +

Custom data sort

+
+
+	var data = [
+	  {"elapsed": "I", "value": 34},
+	  {"elapsed": "II", "value": 24},
+	  {"elapsed": "III", "value": 3},
+	  {"elapsed": "IV", "value": 12},
+	  {"elapsed": "V", "value": 13},
+	  {"elapsed": "VI", "value": 22},
+	  {"elapsed": "VII", "value": 5},
+	  {"elapsed": "VIII", "value": 26},
+	  {"elapsed": "IX", "value": 12},
+	  {"elapsed": "X", "value": 19},
+	];
+	Morris.Line({
+	  element: 'graph',
+	  data: data,
+	  xkey: 'elapsed',
+	  ykeys: ['value'],
+	  labels: ['value'],
+	  parseTime: false,
+	  sortDataFunction: function(a,b){
+		  return a["value"] < b["value"];
+	  }
+	});
+
+ diff --git a/examples/no-xylabels.html b/examples/no-xylabels.html new file mode 100644 index 0000000..3b68357 --- /dev/null +++ b/examples/no-xylabels.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +

Disable X and Y labels

+
+
+	var timestamp_data = [
+	  {"period": 1349046000000, "licensed": 3407, "sorned": 660},
+	  {"period": 1313103600000, "licensed": 3351, "sorned": 629},
+	  {"period": 1299110400000, "licensed": 3269, "sorned": 618},
+	  {"period": 1281222000000, "licensed": 3246, "sorned": 661},
+	  {"period": 1273446000000, "licensed": 3257, "sorned": 667},
+	  {"period": 1268524800000, "licensed": 3248, "sorned": 627},
+	  {"period": 1263081600000, "licensed": 3171, "sorned": 660},
+	  {"period": 1260403200000, "licensed": 3171, "sorned": 676},
+	  {"period": 1254870000000, "licensed": 3201, "sorned": 656},
+	  {"period": 1253833200000, "licensed": 3215, "sorned": 622}
+	];
+	Morris.Line({
+	  element: 'graph',
+	  data: timestamp_data,
+	  xkey: 'period',
+	  ykeys: ['licensed', 'sorned'],
+	  labels: ['Licensed', 'SORN'],
+	  drawYlabel: false,
+	  drawXlabel: false,
+	  dateFormat: function (x) { return new Date(x).toDateString(); }
+	});
+
+ diff --git a/morris.coffee b/morris.coffee index cfdf6ec..b2c9e4c 100644 --- a/morris.coffee +++ b/morris.coffee @@ -63,12 +63,20 @@ class Morris.Line xlabel: 'year' xlabelMargin: 50 dateFormat: (x) -> new Date(x).toString() + sortData: true + sortDataFunction: null + drawYlabel: true + drawXlabel: true # Do any necessary pre-processing for a new dataset # precalc: -> # sort data - @options.data.sort (a, b) => (a[@options.xkey] < b[@options.xkey]) - (b[@options.xkey] < a[@options.xkey]) + if @options.sortData + if typeof @options.sortDataFunction isnt 'function' + @options.data.sort (a,b) => (a[@options.xkey] < b[@options.xkey]) - (b[@options.xkey] < a[@options.xkey]) + else + @options.data.sort @options.sortDataFunction # extract labels @columnLabels = $.map @options.data, (d) => d[@options.xkey] @seriesLabels = @options.labels @@ -147,74 +155,76 @@ class Morris.Line for lineY in [firstY..lastY] by yInterval v = Math.floor(lineY) y = transY(v) - @r.text(left - @options.marginLeft/2, y, v + @options.units) - .attr('font-size', @options.gridTextSize) - .attr('fill', @options.gridTextColor) - .attr('text-anchor', 'end') + if @options.drawYlabels + @r.text(left - @options.marginLeft/2, y, v + @options.units) + .attr('font-size', @options.gridTextSize) + .attr('fill', @options.gridTextColor) + .attr('text-anchor', 'end') @r.path("M" + left + "," + y + 'H' + (left + width)) .attr('stroke', @options.gridLineColor) .attr('stroke-width', @options.gridStrokeWidth) - ## draw x axis labels - prevLabelMargin = null - xLabelMargin = @options.xlabelMargin # make this an option? - xLabel = @options.xlabel - nextLabel = (cur) -> # cur is a Date() - switch xLabel - when 'day' - new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + 1, 0) - when 'month' - if cur.getMonth() == 11 + ## draw x axis labels (if enabled) + if @options.drawXlabel + prevLabelMargin = null + xLabelMargin = @options.xlabelMargin # make this an option? + xLabel = @options.xlabel + nextLabel = (cur) -> # cur is a Date() + switch xLabel + when 'day' + new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + 1, 0) + when 'month' + if cur.getMonth() == 11 + new Date(cur.getFullYear() + 1, 0, 1) + else + new Date(cur.getFullYear(), cur.getMonth() + 1, 1) + else # default: year new Date(cur.getFullYear() + 1, 0, 1) - else - new Date(cur.getFullYear(), cur.getMonth() + 1, 1) - else # default: year - new Date(cur.getFullYear() + 1, 0, 1) - startLabel = (cur) -> # cur is a Date() - if xLabel == 'day' and cur.getHours() == 0 - cur - else if xLabel == 'month' and cur.getDate() == 1 - cur - else if xLabel == 'year' and cur.getDate() == 1 && cur.getMonth() == 0 - cur - else - nextLabel(cur) - labelText = (cur) -> # cur is still a Date() - switch xLabel - when 'day' - d = cur.getDate().toString() - if d.length == 2 then d else '0' + d - when 'month' - m = (cur.getMonth() + 1).toString() - if m.length == 2 then m else '0' + m - else cur.getFullYear() + startLabel = (cur) -> # cur is a Date() + if xLabel == 'day' and cur.getHours() == 0 + cur + else if xLabel == 'month' and cur.getDate() == 1 + cur + else if xLabel == 'year' and cur.getDate() == 1 && cur.getMonth() == 0 + cur + else + nextLabel(cur) + labelText = (cur) -> # cur is still a Date() + switch xLabel + when 'day' + d = cur.getDate().toString() + if d.length == 2 then d else '0' + d + when 'month' + m = (cur.getMonth() + 1).toString() + if m.length == 2 then m else '0' + m + else cur.getFullYear() - r = @r - options = @options - renderLabel = (xpos, text) -> - label = r.text(transX(xpos), options.marginTop + height + options.marginBottom / 2, text) + r = @r + options = @options + renderLabel = (xpos, text) -> + label = r.text(transX(xpos), options.marginTop + height + options.marginBottom / 2, text) .attr('font-size', options.gridTextSize) .attr('fill', options.gridTextColor) - labelBox = label.getBBox() - # ensure a minimum of `xLabelMargin` pixels between labels - if prevLabelMargin is null or prevLabelMargin <= labelBox.x - prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin - else - label.remove() + labelBox = label.getBBox() + # ensure a minimum of `xLabelMargin` pixels between labels + if prevLabelMargin is null or prevLabelMargin <= labelBox.x + prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin + else + label.remove() - if @options.parseTime - start = startLabel(new Date(@xmin)) - end = new Date(@xmax) - cur = start - while cur <= end - xpos = cur.getTime() - if xpos < @xmin - continue - renderLabel(xpos, labelText cur) - cur = nextLabel cur - else - for i in [@xmin..@xmax] - renderLabel(i, @columnLabels[@columnLabels.length-i-1]) + if @options.parseTime + start = startLabel(new Date(@xmin)) + end = new Date(@xmax) + cur = start + while cur <= end + xpos = cur.getTime() + if xpos < @xmin + continue + renderLabel(xpos, labelText cur) + cur = nextLabel cur + else + for i in [@xmin..@xmax] + renderLabel(i, @columnLabels[@columnLabels.length-i-1]) # draw the actual series columns = (transX(x) for x in @xvals) diff --git a/morris.js b/morris.js index 6759dcd..5aa84bc 100644 --- a/morris.js +++ b/morris.js @@ -53,15 +53,25 @@ xlabelMargin: 50, dateFormat: function(x) { return new Date(x).toString(); - } + }, + sortData: true, + sortDataFunction: null, + drawYlabel: true, + drawXlabel: true }; Line.prototype.precalc = function() { var ykey, ymax, ymin, _i, _j, _len, _ref, _ref2, _results, _this = this; - this.options.data.sort(function(a, b) { - return (a[_this.options.xkey] < b[_this.options.xkey]) - (b[_this.options.xkey] < a[_this.options.xkey]); - }); + if (this.options.sortData) { + if (typeof this.options.sortDataFunction !== 'function') { + this.options.data.sort(function(a, b) { + return (a[_this.options.xkey] < b[_this.options.xkey]) - (b[_this.options.xkey] < a[_this.options.xkey]); + }); + } else { + this.options.data.sort(this.options.sortDataFunction); + } + } this.columnLabels = $.map(this.options.data, function(d) { return d[_this.options.xkey]; }); @@ -145,86 +155,90 @@ for (lineY = firstY; firstY <= lastY ? lineY <= lastY : lineY >= lastY; lineY += yInterval) { v = Math.floor(lineY); y = transY(v); - this.r.text(left - this.options.marginLeft / 2, y, v + this.options.units).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end'); + if (this.options.drawYlabels) { + this.r.text(left - this.options.marginLeft / 2, y, v + this.options.units).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end'); + } this.r.path("M" + left + "," + y + 'H' + (left + width)).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth); } - prevLabelMargin = null; - xLabelMargin = this.options.xlabelMargin; - xLabel = this.options.xlabel; - nextLabel = function(cur) { - switch (xLabel) { - case 'day': - return new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + 1, 0); - case 'month': - if (cur.getMonth() === 11) { + if (this.options.drawXlabel) { + prevLabelMargin = null; + xLabelMargin = this.options.xlabelMargin; + xLabel = this.options.xlabel; + nextLabel = function(cur) { + switch (xLabel) { + case 'day': + return new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + 1, 0); + case 'month': + if (cur.getMonth() === 11) { + return new Date(cur.getFullYear() + 1, 0, 1); + } else { + return new Date(cur.getFullYear(), cur.getMonth() + 1, 1); + } + break; + default: return new Date(cur.getFullYear() + 1, 0, 1); - } else { - return new Date(cur.getFullYear(), cur.getMonth() + 1, 1); - } - break; - default: - return new Date(cur.getFullYear() + 1, 0, 1); - } - }; - startLabel = function(cur) { - if (xLabel === 'day' && cur.getHours() === 0) { - return cur; - } else if (xLabel === 'month' && cur.getDate() === 1) { - return cur; - } else if (xLabel === 'year' && cur.getDate() === 1 && cur.getMonth() === 0) { - return cur; + } + }; + startLabel = function(cur) { + if (xLabel === 'day' && cur.getHours() === 0) { + return cur; + } else if (xLabel === 'month' && cur.getDate() === 1) { + return cur; + } else if (xLabel === 'year' && cur.getDate() === 1 && cur.getMonth() === 0) { + return cur; + } else { + return nextLabel(cur); + } + }; + labelText = function(cur) { + var d, m; + switch (xLabel) { + case 'day': + d = cur.getDate().toString(); + if (d.length === 2) { + return d; + } else { + return '0' + d; + } + break; + case 'month': + m = (cur.getMonth() + 1).toString(); + if (m.length === 2) { + return m; + } else { + return '0' + m; + } + break; + default: + return cur.getFullYear(); + } + }; + r = this.r; + options = this.options; + renderLabel = function(xpos, text) { + var label, labelBox; + label = r.text(transX(xpos), options.marginTop + height + options.marginBottom / 2, text).attr('font-size', options.gridTextSize).attr('fill', options.gridTextColor); + labelBox = label.getBBox(); + if (prevLabelMargin === null || prevLabelMargin <= labelBox.x) { + return prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin; + } else { + return label.remove(); + } + }; + if (this.options.parseTime) { + start = startLabel(new Date(this.xmin)); + end = new Date(this.xmax); + cur = start; + while (cur <= end) { + xpos = cur.getTime(); + if (xpos < this.xmin) continue; + renderLabel(xpos, labelText(cur)); + cur = nextLabel(cur); + } } else { - return nextLabel(cur); - } - }; - labelText = function(cur) { - var d, m; - switch (xLabel) { - case 'day': - d = cur.getDate().toString(); - if (d.length === 2) { - return d; - } else { - return '0' + d; - } - break; - case 'month': - m = (cur.getMonth() + 1).toString(); - if (m.length === 2) { - return m; - } else { - return '0' + m; - } - break; - default: - return cur.getFullYear(); - } - }; - r = this.r; - options = this.options; - renderLabel = function(xpos, text) { - var label, labelBox; - label = r.text(transX(xpos), options.marginTop + height + options.marginBottom / 2, text).attr('font-size', options.gridTextSize).attr('fill', options.gridTextColor); - labelBox = label.getBBox(); - if (prevLabelMargin === null || prevLabelMargin <= labelBox.x) { - return prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin; - } else { - return label.remove(); - } - }; - if (this.options.parseTime) { - start = startLabel(new Date(this.xmin)); - end = new Date(this.xmax); - cur = start; - while (cur <= end) { - xpos = cur.getTime(); - if (xpos < this.xmin) continue; - renderLabel(xpos, labelText(cur)); - cur = nextLabel(cur); - } - } else { - for (i = _ref = this.xmin, _ref2 = this.xmax; _ref <= _ref2 ? i <= _ref2 : i >= _ref2; _ref <= _ref2 ? i++ : i--) { - renderLabel(i, this.columnLabels[this.columnLabels.length - i - 1]); + for (i = _ref = this.xmin, _ref2 = this.xmax; _ref <= _ref2 ? i <= _ref2 : i >= _ref2; _ref <= _ref2 ? i++ : i--) { + renderLabel(i, this.columnLabels[this.columnLabels.length - i - 1]); + } } } columns = (function() { diff --git a/morris.min.js b/morris.min.js index b2885de..aae65e3 100644 --- a/morris.min.js +++ b/morris.min.js @@ -1 +1 @@ -((function(){var a,b;a=jQuery,b={},b.Line=function(){function c(c){if(!(this instanceof b.Line))return new b.Line(c);typeof c.element=="string"?this.el=a(document.getElementById(c.element)):this.el=a(c.element),this.options=a.extend({},this.defaults,c);if(this.options.data===void 0||this.options.data.length===0)return;this.el.addClass("graph-initialised"),this.precalc(),this.redraw()}return c.prototype.defaults={lineWidth:3,pointSize:4,lineColors:["#0b62a4","#7A92A3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],ymax:"auto",ymin:"auto 0",marginTop:25,marginRight:25,marginBottom:30,marginLeft:25,numLines:5,gridLineColor:"#aaa",gridTextColor:"#888",gridTextSize:12,gridStrokeWidth:.5,hoverPaddingX:10,hoverPaddingY:5,hoverMargin:10,hoverFillColor:"#fff",hoverBorderColor:"#ccc",hoverBorderWidth:2,hoverOpacity:.95,hoverLabelColor:"#444",hoverFontSize:12,smooth:!0,hideHover:!1,parseTime:!0,units:"",xlabel:"year",xlabelMargin:50,dateFormat:function(a){return(new Date(a)).toString()}},c.prototype.precalc=function(){var b,c,d,e,f,g,h,i,j,k=this;this.options.data.sort(function(a,b){return(a[k.options.xkey]=0;i<=0?a++:a--)j.push(a);return j}.apply(this),this.options.parseTime&&(this.columnLabels=a.map(this.columnLabels,function(a){return typeof a=="number"?k.options.dateFormat(a):a})),this.xmin=Math.min.apply(null,this.xvals),this.xmax=Math.max.apply(null,this.xvals),this.xmin===this.xmax&&(this.xmin-=1,this.xmax+=1),typeof this.options.ymax=="string"&&this.options.ymax.slice(0,4)==="auto"&&(c=Math.max.apply(null,Array.prototype.concat.apply([],this.series)),this.options.ymax.length>5?this.options.ymax=Math.max(parseInt(this.options.ymax.slice(5),10),c):this.options.ymax=c);if(typeof this.options.ymin=="string"&&this.options.ymin.slice(0,4)==="auto")return d=Math.min.apply(null,Array.prototype.concat.apply([],this.series)),this.options.ymin.length>5?this.options.ymin=Math.min(parseInt(this.options.ymin.slice(5),10),d):this.options.ymin=d},c.prototype.redraw=function(){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,_,ba,bb,bc,bd,be,bf,bg,bh,bi,bj,bk=this;this.el.empty(),this.r=new Raphael(this.el[0]),w=Math.max(this.measureText(this.options.ymin+this.options.units,this.options.gridTextSize).width,this.measureText(this.options.ymax+this.options.units,this.options.gridTextSize).width),u=w+this.options.marginLeft,R=this.el.width()-u-this.options.marginRight,k=this.el.height()-this.options.marginTop-this.options.marginBottom,g=R/(this.xmax-this.xmin),h=k/(this.options.ymax-this.options.ymin),M=function(a){return bk.xvals.length===1?u+R/2:u+(a-bk.xmin)*g},N=function(a){return bk.options.marginTop+k-(a-bk.options.ymin)*h},X=(this.options.ymax-this.options.ymin)/(this.options.numLines-1),j=Math.ceil(this.options.ymin/X)*X,t=Math.floor(this.options.ymax/X)*X;for(v=j;j<=t?v<=t:v>=t;v+=X)Q=Math.floor(v),W=N(Q),this.r.text(u-this.options.marginLeft/2,W,Q+this.options.units).attr("font-size",this.options.gridTextSize).attr("fill",this.options.gridTextColor).attr("text-anchor","end"),this.r.path("M"+u+","+W+"H"+(u+R)).attr("stroke",this.options.gridLineColor).attr("stroke-width",this.options.gridStrokeWidth);D=null,U=this.options.xlabelMargin,T=this.options.xlabel,x=function(a){switch(T){case"day":return new Date(a.getFullYear(),a.getMonth(),a.getDate()+1,0);case"month":return a.getMonth()===11?new Date(a.getFullYear()+1,0,1):new Date(a.getFullYear(),a.getMonth()+1,1);default:return new Date(a.getFullYear()+1,0,1)}},K=function(a){return T==="day"&&a.getHours()===0?a:T==="month"&&a.getDate()===1?a:T==="year"&&a.getDate()===1&&a.getMonth()===0?a:x(a)},s=function(a){var b,c;switch(T){case"day":return b=a.getDate().toString(),b.length===2?b:"0"+b;case"month":return c=(a.getMonth()+1).toString(),c.length===2?c:"0"+c;default:return a.getFullYear()}},E=this.r,y=this.options,F=function(a,b){var c,d;return c=E.text(M(a),y.marginTop+k+y.marginBottom/2,b).attr("font-size",y.gridTextSize).attr("fill",y.gridTextColor),d=c.getBBox(),D===null||D<=d.x?D=d.x+d.width+U:c.remove()};if(this.options.parseTime){J=K(new Date(this.xmin)),i=new Date(this.xmax),f=J;while(f<=i){V=f.getTime();if(V=be;bd<=be?r++:r--)F(r,this.columnLabels[this.columnLabels.length-r-1]);d=function(){var a,b,c,d;c=this.xvals,d=[];for(a=0,b=c.length;a=0;bg<=0?r++:r--)e=H[r],e.length>1&&(z=this.createPath(e,this.options.marginTop,u,this.options.marginTop+k,u+R),this.r.path(z).attr("stroke",this.options.lineColors[r]).attr("stroke-width",this.options.lineWidth));I=function(){var a,b;b=[];for(r=0,a=H.length-1;0<=a?r<=a:r>=a;0<=a?r++:r--)b.push([]);return b}();for(r=bh=H.length-1;bh<=0?r<=0:r>=0;bh<=0?r++:r--){bi=H[r];for(ba=0,bc=bi.length;ba=bj;0<=bj?r++:r--)Y=this.r.text(0,this.options.hoverFontSize*1.5*(r+1.5)-o/2,"").attr("fill",this.options.lineColors[r]).attr("font-size",this.options.hoverFontSize),Z.push(Y),q.push(Y);return P=function(b){var c,e,f,g,h;q.show(),T.attr("text",bk.columnLabels[b]);for(c=0,h=bk.series.length-1;0<=h?c<=h:c>=h;0<=h?c++:c--)Z[c].attr("text",""+bk.seriesLabels[c]+": "+bk.commas(bk.series[c][b])+bk.options.units);return e=Math.max.apply(null,a.map(Z,function(a){return a.getBBox().width})),e=Math.max(e,T.getBBox().width),n.attr("width",e+bk.options.hoverPaddingX*2),n.attr("x",-bk.options.hoverPaddingX-e/2),g=Math.min.apply(null,a.map(bk.series,function(a){return N(a[b])})),g>o+bk.options.hoverPaddingY*2+bk.options.hoverMargin+bk.options.marginTop?g=g-o/2-bk.options.hoverPaddingY-bk.options.hoverMargin:g=g+o/2+bk.options.hoverPaddingY+bk.options.hoverMargin,g=Math.max(bk.options.marginTop+o/2+bk.options.hoverPaddingY,g),g=Math.min(bk.options.marginTop+k-o/2-bk.options.hoverPaddingY,g),f=Math.min(u+R-e/2-bk.options.hoverPaddingX,d[b]),f=Math.max(u+e/2+bk.options.hoverPaddingX,f),q.attr("transform","t"+f+","+g)},l=function(){return q.hide()},p=a.map(d.slice(1),function(a,b){return(a+d[b])/2}),C=null,A=Raphael.animation({r:this.options.pointSize+3},25,"linear"),B=Raphael.animation({r:this.options.pointSize},25,"linear"),m=function(a){var b,c,d;if(C!==null&&C!==a)for(b=0,c=I.length-1;0<=c?b<=c:b>=c;0<=c?b++:b--)I[b][C].animate(B);if(a!==null&&C!==a){for(b=0,d=I.length-1;0<=d?b<=d:b>=d;0<=d?b++:b--)I[b][a].animate(A);P(a)}C=a;if(a===null)return l()},O=function(a){var b,c,d;a-=bk.el.offset().left,d=[];for(b=c=p.length;c<=0?b<=0:b>=0;c<=0?b++:b--){if(b===0||p[b-1]>a){m(b);break}d.push(void 0)}return d},this.el.mousemove(function(a){return O(a.pageX)}),this.options.hideHover&&this.el.mouseout(function(a){return m(null)}),L=function(a){var b;return b=a.originalEvent.touches[0]||a.originalEvent.changedTouches[0],O(b.pageX),b},this.el.bind("touchstart",L),this.el.bind("touchmove",L),this.el.bind("touchend",L),m(this.options.hideHover?null:0)},c.prototype.createPath=function(b,c,d,e,f){var g,h,i,j,k,l,m,n,o,p,q,r,s;n="";if(this.options.smooth){i=this.gradients(b);for(j=0,s=b.length-1;0<=s?j<=s:j>=s;0<=s?j++:j--)g=b[j],j===0?n+="M"+g.x+","+g.y:(h=i[j],l=b[j-1],m=i[j-1],k=(g.x-l.x)/4,o=l.x+k,q=Math.min(e,l.y+k*m),p=g.x-k,r=Math.min(e,g.y-k*h),n+="C"+o+","+q+","+p+","+r+","+g.x+","+g.y)}else n="M"+a.map(b,function(a){return""+a.x+","+a.y}).join("L");return n},c.prototype.gradients=function(b){return a.map(b,function(a,c){return c===0?(b[1].y-a.y)/(b[1].x-a.x):c===b.length-1?(a.y-b[c-1].y)/(a.x-b[c-1].x):(b[c+1].y-b[c-1].y)/(b[c+1].x-b[c-1].x)})},c.prototype.measureText=function(a,b){var c,d;return b==null&&(b=12),d=this.r.text(100,100,a).attr("font-size",b),c=d.getBBox(),d.remove(),c},c.prototype.parseYear=function(a){var b,c,d,e,f,g,h,i,j,k;return typeof a=="number"?a:(c=a.match(/^(\d+) Q(\d)$/),e=a.match(/^(\d+)-(\d+)$/),f=a.match(/^(\d+)-(\d+)-(\d+)$/),g=a.match(/^(\d+) W(\d+)$/),h=a.match(/^(\d+)-(\d+)-(\d+) (\d+):(\d+)$/),i=a.match(/^(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+(\.\d+)?)$/),c?(new Date(parseInt(c[1],10),parseInt(c[2],10)*3-1,1)).getTime():e?(new Date(parseInt(e[1],10),parseInt(e[2],10)-1,1)).getTime():f?(new Date(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10))).getTime():g?(j=new Date(parseInt(g[1],10),0,1),j.getDay()!==4&&j.setMonth(0,1+(4-j.getDay()+7)%7),j.getTime()+parseInt(g[2],10)*6048e5):h?(new Date(parseInt(h[1],10),parseInt(h[2],10)-1,parseInt(h[3],10),parseInt(h[4],10),parseInt(h[5],10))).getTime():i?(k=parseFloat(i[6]),b=Math.floor(k),d=Math.floor((k-b)*1e3),(new Date(parseInt(i[1],10),parseInt(i[2],10)-1,parseInt(i[3],10),parseInt(i[4],10),parseInt(i[5],10),b,d)).getTime()):new Date(parseInt(a,10),0,1))},c.prototype.commas=function(a){var b,c,d,e;return d=a<0?"-":"",b=Math.abs(a),c=Math.floor(b).toFixed(0),d+=c.replace(/(?=(?:\d{3})+$)(?!^)/g,","),e=b.toString(),e.length>c.length&&(d+=e.slice(c.length)),d},c}(),window.Morris=b})).call(this); \ No newline at end of file +((function(){var a,b;a=jQuery,b={},b.Line=function(){function c(c){if(!(this instanceof b.Line))return new b.Line(c);typeof c.element=="string"?this.el=a(document.getElementById(c.element)):this.el=a(c.element),this.options=a.extend({},this.defaults,c);if(this.options.data===void 0||this.options.data.length===0)return;this.el.addClass("graph-initialised"),this.precalc(),this.redraw()}return c.prototype.defaults={lineWidth:3,pointSize:4,lineColors:["#0b62a4","#7A92A3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],ymax:"auto",ymin:"auto 0",marginTop:25,marginRight:25,marginBottom:30,marginLeft:25,numLines:5,gridLineColor:"#aaa",gridTextColor:"#888",gridTextSize:12,gridStrokeWidth:.5,hoverPaddingX:10,hoverPaddingY:5,hoverMargin:10,hoverFillColor:"#fff",hoverBorderColor:"#ccc",hoverBorderWidth:2,hoverOpacity:.95,hoverLabelColor:"#444",hoverFontSize:12,smooth:!0,hideHover:!1,parseTime:!0,units:"",xlabel:"year",xlabelMargin:50,dateFormat:function(a){return(new Date(a)).toString()},sortData:!0,sortDataFunction:null,drawYlabel:!0,drawXlabel:!0},c.prototype.precalc=function(){var b,c,d,e,f,g,h,i,j,k=this;this.options.sortData&&(typeof this.options.sortDataFunction!="function"?this.options.data.sort(function(a,b){return(a[k.options.xkey]=0;i<=0?a++:a--)j.push(a);return j}.apply(this),this.options.parseTime&&(this.columnLabels=a.map(this.columnLabels,function(a){return typeof a=="number"?k.options.dateFormat(a):a})),this.xmin=Math.min.apply(null,this.xvals),this.xmax=Math.max.apply(null,this.xvals),this.xmin===this.xmax&&(this.xmin-=1,this.xmax+=1),typeof this.options.ymax=="string"&&this.options.ymax.slice(0,4)==="auto"&&(c=Math.max.apply(null,Array.prototype.concat.apply([],this.series)),this.options.ymax.length>5?this.options.ymax=Math.max(parseInt(this.options.ymax.slice(5),10),c):this.options.ymax=c);if(typeof this.options.ymin=="string"&&this.options.ymin.slice(0,4)==="auto")return d=Math.min.apply(null,Array.prototype.concat.apply([],this.series)),this.options.ymin.length>5?this.options.ymin=Math.min(parseInt(this.options.ymin.slice(5),10),d):this.options.ymin=d},c.prototype.redraw=function(){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,_,ba,bb,bc,bd,be,bf,bg,bh,bi,bj,bk=this;this.el.empty(),this.r=new Raphael(this.el[0]),w=Math.max(this.measureText(this.options.ymin+this.options.units,this.options.gridTextSize).width,this.measureText(this.options.ymax+this.options.units,this.options.gridTextSize).width),u=w+this.options.marginLeft,R=this.el.width()-u-this.options.marginRight,k=this.el.height()-this.options.marginTop-this.options.marginBottom,g=R/(this.xmax-this.xmin),h=k/(this.options.ymax-this.options.ymin),M=function(a){return bk.xvals.length===1?u+R/2:u+(a-bk.xmin)*g},N=function(a){return bk.options.marginTop+k-(a-bk.options.ymin)*h},X=(this.options.ymax-this.options.ymin)/(this.options.numLines-1),j=Math.ceil(this.options.ymin/X)*X,t=Math.floor(this.options.ymax/X)*X;for(v=j;j<=t?v<=t:v>=t;v+=X)Q=Math.floor(v),W=N(Q),this.options.drawYlabels&&this.r.text(u-this.options.marginLeft/2,W,Q+this.options.units).attr("font-size",this.options.gridTextSize).attr("fill",this.options.gridTextColor).attr("text-anchor","end"),this.r.path("M"+u+","+W+"H"+(u+R)).attr("stroke",this.options.gridLineColor).attr("stroke-width",this.options.gridStrokeWidth);if(this.options.drawXlabel){D=null,U=this.options.xlabelMargin,T=this.options.xlabel,x=function(a){switch(T){case"day":return new Date(a.getFullYear(),a.getMonth(),a.getDate()+1,0);case"month":return a.getMonth()===11?new Date(a.getFullYear()+1,0,1):new Date(a.getFullYear(),a.getMonth()+1,1);default:return new Date(a.getFullYear()+1,0,1)}},K=function(a){return T==="day"&&a.getHours()===0?a:T==="month"&&a.getDate()===1?a:T==="year"&&a.getDate()===1&&a.getMonth()===0?a:x(a)},s=function(a){var b,c;switch(T){case"day":return b=a.getDate().toString(),b.length===2?b:"0"+b;case"month":return c=(a.getMonth()+1).toString(),c.length===2?c:"0"+c;default:return a.getFullYear()}},E=this.r,y=this.options,F=function(a,b){var c,d;return c=E.text(M(a),y.marginTop+k+y.marginBottom/2,b).attr("font-size",y.gridTextSize).attr("fill",y.gridTextColor),d=c.getBBox(),D===null||D<=d.x?D=d.x+d.width+U:c.remove()};if(this.options.parseTime){J=K(new Date(this.xmin)),i=new Date(this.xmax),f=J;while(f<=i){V=f.getTime();if(V=be;bd<=be?r++:r--)F(r,this.columnLabels[this.columnLabels.length-r-1])}d=function(){var a,b,c,d;c=this.xvals,d=[];for(a=0,b=c.length;a=0;bg<=0?r++:r--)e=H[r],e.length>1&&(z=this.createPath(e,this.options.marginTop,u,this.options.marginTop+k,u+R),this.r.path(z).attr("stroke",this.options.lineColors[r]).attr("stroke-width",this.options.lineWidth));I=function(){var a,b;b=[];for(r=0,a=H.length-1;0<=a?r<=a:r>=a;0<=a?r++:r--)b.push([]);return b}();for(r=bh=H.length-1;bh<=0?r<=0:r>=0;bh<=0?r++:r--){bi=H[r];for(ba=0,bc=bi.length;ba=bj;0<=bj?r++:r--)Y=this.r.text(0,this.options.hoverFontSize*1.5*(r+1.5)-o/2,"").attr("fill",this.options.lineColors[r]).attr("font-size",this.options.hoverFontSize),Z.push(Y),q.push(Y);return P=function(b){var c,e,f,g,h;q.show(),T.attr("text",bk.columnLabels[b]);for(c=0,h=bk.series.length-1;0<=h?c<=h:c>=h;0<=h?c++:c--)Z[c].attr("text",""+bk.seriesLabels[c]+": "+bk.commas(bk.series[c][b])+bk.options.units);return e=Math.max.apply(null,a.map(Z,function(a){return a.getBBox().width})),e=Math.max(e,T.getBBox().width),n.attr("width",e+bk.options.hoverPaddingX*2),n.attr("x",-bk.options.hoverPaddingX-e/2),g=Math.min.apply(null,a.map(bk.series,function(a){return N(a[b])})),g>o+bk.options.hoverPaddingY*2+bk.options.hoverMargin+bk.options.marginTop?g=g-o/2-bk.options.hoverPaddingY-bk.options.hoverMargin:g=g+o/2+bk.options.hoverPaddingY+bk.options.hoverMargin,g=Math.max(bk.options.marginTop+o/2+bk.options.hoverPaddingY,g),g=Math.min(bk.options.marginTop+k-o/2-bk.options.hoverPaddingY,g),f=Math.min(u+R-e/2-bk.options.hoverPaddingX,d[b]),f=Math.max(u+e/2+bk.options.hoverPaddingX,f),q.attr("transform","t"+f+","+g)},l=function(){return q.hide()},p=a.map(d.slice(1),function(a,b){return(a+d[b])/2}),C=null,A=Raphael.animation({r:this.options.pointSize+3},25,"linear"),B=Raphael.animation({r:this.options.pointSize},25,"linear"),m=function(a){var b,c,d;if(C!==null&&C!==a)for(b=0,c=I.length-1;0<=c?b<=c:b>=c;0<=c?b++:b--)I[b][C].animate(B);if(a!==null&&C!==a){for(b=0,d=I.length-1;0<=d?b<=d:b>=d;0<=d?b++:b--)I[b][a].animate(A);P(a)}C=a;if(a===null)return l()},O=function(a){var b,c,d;a-=bk.el.offset().left,d=[];for(b=c=p.length;c<=0?b<=0:b>=0;c<=0?b++:b--){if(b===0||p[b-1]>a){m(b);break}d.push(void 0)}return d},this.el.mousemove(function(a){return O(a.pageX)}),this.options.hideHover&&this.el.mouseout(function(a){return m(null)}),L=function(a){var b;return b=a.originalEvent.touches[0]||a.originalEvent.changedTouches[0],O(b.pageX),b},this.el.bind("touchstart",L),this.el.bind("touchmove",L),this.el.bind("touchend",L),m(this.options.hideHover?null:0)},c.prototype.createPath=function(b,c,d,e,f){var g,h,i,j,k,l,m,n,o,p,q,r,s;n="";if(this.options.smooth){i=this.gradients(b);for(j=0,s=b.length-1;0<=s?j<=s:j>=s;0<=s?j++:j--)g=b[j],j===0?n+="M"+g.x+","+g.y:(h=i[j],l=b[j-1],m=i[j-1],k=(g.x-l.x)/4,o=l.x+k,q=Math.min(e,l.y+k*m),p=g.x-k,r=Math.min(e,g.y-k*h),n+="C"+o+","+q+","+p+","+r+","+g.x+","+g.y)}else n="M"+a.map(b,function(a){return""+a.x+","+a.y}).join("L");return n},c.prototype.gradients=function(b){return a.map(b,function(a,c){return c===0?(b[1].y-a.y)/(b[1].x-a.x):c===b.length-1?(a.y-b[c-1].y)/(a.x-b[c-1].x):(b[c+1].y-b[c-1].y)/(b[c+1].x-b[c-1].x)})},c.prototype.measureText=function(a,b){var c,d;return b==null&&(b=12),d=this.r.text(100,100,a).attr("font-size",b),c=d.getBBox(),d.remove(),c},c.prototype.parseYear=function(a){var b,c,d,e,f,g,h,i,j,k;return typeof a=="number"?a:(c=a.match(/^(\d+) Q(\d)$/),e=a.match(/^(\d+)-(\d+)$/),f=a.match(/^(\d+)-(\d+)-(\d+)$/),g=a.match(/^(\d+) W(\d+)$/),h=a.match(/^(\d+)-(\d+)-(\d+) (\d+):(\d+)$/),i=a.match(/^(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+(\.\d+)?)$/),c?(new Date(parseInt(c[1],10),parseInt(c[2],10)*3-1,1)).getTime():e?(new Date(parseInt(e[1],10),parseInt(e[2],10)-1,1)).getTime():f?(new Date(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10))).getTime():g?(j=new Date(parseInt(g[1],10),0,1),j.getDay()!==4&&j.setMonth(0,1+(4-j.getDay()+7)%7),j.getTime()+parseInt(g[2],10)*6048e5):h?(new Date(parseInt(h[1],10),parseInt(h[2],10)-1,parseInt(h[3],10),parseInt(h[4],10),parseInt(h[5],10))).getTime():i?(k=parseFloat(i[6]),b=Math.floor(k),d=Math.floor((k-b)*1e3),(new Date(parseInt(i[1],10),parseInt(i[2],10)-1,parseInt(i[3],10),parseInt(i[4],10),parseInt(i[5],10),b,d)).getTime()):new Date(parseInt(a,10),0,1))},c.prototype.commas=function(a){var b,c,d,e;return d=a<0?"-":"",b=Math.abs(a),c=Math.floor(b).toFixed(0),d+=c.replace(/(?=(?:\d{3})+$)(?!^)/g,","),e=b.toString(),e.length>c.length&&(d+=e.slice(c.length)),d},c}(),window.Morris=b})).call(this); \ No newline at end of file