jquery-svg-rails 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ /* http://keith-wood.name/svg.html
2
+ SVG graphing extension for jQuery v1.5.0.
3
+ Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
4
+ Available under the MIT (http://keith-wood.name/licence.html) license.
5
+ Please attribute the author if you use it. */
6
+ (function($){$.svg.addExtension('graph',SVGGraph);$.svg.graphing=new SVGGraphing();function SVGGraphing(){this.regional=[];this.regional['']={percentageText:'Percentage'};this.region=this.regional['']}$.extend(SVGGraphing.prototype,{_chartTypes:[],addChartType:function(a,b){this._chartTypes[a]=b},chartTypes:function(){return this._chartTypes}});function SVGGraph(a){this._wrapper=a;this._drawNow=false;for(var b in $.svg.graphing._chartTypes){this._chartType=$.svg.graphing._chartTypes[b];break}this._chartOptions={};this._title={value:'',offset:25,settings:{textAnchor:'middle'}};this._area=[0.1,0.1,0.8,0.9];this._chartFormat={fill:'none',stroke:'black'};this._gridlines=[];this._series=[];this._onstatus=null;this._chartCont=this._wrapper.svg(0,0,0,0,{class_:'svg-graph'});this.xAxis=new SVGGraphAxis(this);this.xAxis.title('',40);this.yAxis=new SVGGraphAxis(this);this.yAxis.title('',40);this.x2Axis=null;this.y2Axis=null;this.legend=new SVGGraphLegend(this);this._drawNow=true}$.extend(SVGGraph.prototype,{X:0,Y:1,W:2,H:3,L:0,T:1,R:2,B:3,_percentageAxis:new SVGGraphAxis(this,$.svg.graphing.region.percentageText,0,100,10,0),container:function(a){if(arguments.length===0){return this._chartCont}this._chartCont=a;return this},chartType:function(a,b){return(arguments.length===0?this.type():this.type(a,b))},type:function(a,b){if(arguments.length===0){return this._chartType}var c=$.svg.graphing._chartTypes[a];if(c){this._chartType=c;this._chartOptions=$.extend({},b||{})}this._drawGraph();return this},chartOptions:function(a){return(arguments.length===0?this.options():this.options(a))},options:function(a){if(arguments.length===0){return this._chartOptions}this._chartOptions=$.extend({},a);this._drawGraph();return this},chartFormat:function(a,b,c){return(arguments.length===0?this.format():this.format(a,b,c))},format:function(a,b,c){if(arguments.length===0){return this._chartFormat}if(typeof b==='object'){c=b;b=null}this._chartFormat=$.extend({fill:a},(b?{stroke:b}:{}),c||{});this._drawGraph();return this},chartArea:function(a,b,c,d){return(arguments.length===0?this.area():this.area(a,b,c,d))},area:function(a,b,c,d){if(arguments.length===0){return this._area}this._area=($.isArray(a)?a:[a,b,c,d]);this._drawGraph();return this},gridlines:function(a,b){if(arguments.length===0){return this._gridlines}this._gridlines=[(typeof a==='string'?{stroke:a}:a),(typeof b==='string'?{stroke:b}:b)];if(this._gridlines[0]==null&&this._gridlines[1]==null){this._gridlines=[]}this._drawGraph();return this},title:function(a,b,c,d){if(arguments.length===0){return this._title}if(typeof b!=='number'){d=c;c=b;b=null}if(typeof c!=='string'){d=c;c=null}this._title={value:a,offset:b||this._title.offset,settings:$.extend({textAnchor:'middle'},(c?{fill:c}:{}),d||{})};this._drawGraph();return this},addSeries:function(a,b,c,d,e,f){this._series.push(new SVGGraphSeries(this,a,b,c,d,e,f));this._drawGraph();return this},series:function(i){return(arguments.length>0?this._series[i]:null)||this._series},noDraw:function(){this._drawNow=false;return this},redraw:function(){this._drawNow=true;this._drawGraph();return this},status:function(a){this._onstatus=a;return this},_drawGraph:function(){if(!this._drawNow){return}while(this._chartCont.firstChild){this._chartCont.removeChild(this._chartCont.firstChild)}if(!this._chartCont.parent){this._wrapper._svg.appendChild(this._chartCont)}if(!this._chartCont.width){this._chartCont.setAttribute('width',parseInt(this._chartCont.getAttribute('width'),10)||this._wrapper.width())}else if(this._chartCont.width.baseVal){this._chartCont.width.baseVal.value=this._chartCont.width.baseVal.value||this._wrapper.width()}else{this._chartCont.width=this._chartCont.width||this._wrapper.width()}if(!this._chartCont.height){this._chartCont.setAttribute('height',parseInt(this._chartCont.getAttribute('height'),10)||this._wrapper.height())}else if(this._chartCont.height.baseVal){this._chartCont.height.baseVal.value=this._chartCont.height.baseVal.value||this._wrapper.height()}else{this._chartCont.height=this._chartCont.height||this._wrapper.height()}this._chartType.drawGraph(this)},_getValue:function(a,b){return(!a[b]?parseInt(a.getAttribute(b),10):(a[b].baseVal?a[b].baseVal.value:a[b]))},_drawTitle:function(){this._wrapper.text(this._chartCont,this._getValue(this._chartCont,'width')/2,this._title.offset,this._title.value,this._title.settings)},_getDims:function(a){a=a||this._area;var b=this._getValue(this._chartCont,'width');var c=this._getValue(this._chartCont,'height');var d=(a[this.L]>1?a[this.L]:b*a[this.L]);var e=(a[this.T]>1?a[this.T]:c*a[this.T]);var f=(a[this.R]>1?a[this.R]:b*a[this.R])-d;var g=(a[this.B]>1?a[this.B]:c*a[this.B])-e;return[d,e,f,g]},_drawChartBackground:function(a,b){var c=this._wrapper.group(this._chartCont,{class_:'background'});var d=this._getDims();this._wrapper.rect(c,d[this.X],d[this.Y],d[this.W],d[this.H],this._chartFormat);if(this._gridlines[0]&&this.yAxis._ticks.major&&!b){this._drawGridlines(c,this.yAxis,true,d,this._gridlines[0])}if(this._gridlines[1]&&this.xAxis._ticks.major&&!a){this._drawGridlines(c,this.xAxis,false,d,this._gridlines[1])}return c},_drawGridlines:function(a,b,c,d,e){var g=this._wrapper.group(a,e);var f=(c?d[this.H]:d[this.W])/(b._scale.max-b._scale.min);var h=Math.floor(b._scale.min/b._ticks.major)*b._ticks.major;h=(h<b._scale.min?h+b._ticks.major:h);while(h<=b._scale.max){var v=(c?b._scale.max-h:h-b._scale.min)*f+(c?d[this.Y]:d[this.X]);this._wrapper.line(g,(c?d[this.X]:v),(c?v:d[this.Y]),(c?d[this.X]+d[this.W]:v),(c?v:d[this.Y]+d[this.H]));h+=b._ticks.major}},_drawAxes:function(a){var b=this._getDims();if(this.xAxis&&!a){if(this.xAxis._title){this._wrapper.text(this._chartCont,b[this.X]+b[this.W]/2,b[this.Y]+b[this.H]+this.xAxis._titleOffset,this.xAxis._title,this.xAxis._titleFormat)}this._drawAxis(this.xAxis,'xAxis',b[this.X],b[this.Y]+b[this.H],b[this.X]+b[this.W],b[this.Y]+b[this.H])}if(this.yAxis){if(this.yAxis._title){this._wrapper.text(this._chartCont,0,0,this.yAxis._title,$.extend({textAnchor:'middle',transform:'translate('+(b[this.X]-this.yAxis._titleOffset)+','+(b[this.Y]+b[this.H]/2)+') rotate(-90)'},this.yAxis._titleFormat||{}))}this._drawAxis(this.yAxis,'yAxis',b[this.X],b[this.Y],b[this.X],b[this.Y]+b[this.H])}if(this.x2Axis&&!a){if(this.x2Axis._title){this._wrapper.text(this._chartCont,b[this.X]+b[this.W]/2,b[this.X]-this.x2Axis._titleOffset,this.x2Axis._title,this.x2Axis._titleFormat)}this._drawAxis(this.x2Axis,'x2Axis',b[this.X],b[this.Y],b[this.X]+b[this.W],b[this.Y])}if(this.y2Axis){if(this.y2Axis._title){this._wrapper.text(this._chartCont,0,0,this.y2Axis._title,$.extend({textAnchor:'middle',transform:'translate('+(b[this.X]+b[this.W]+this.y2Axis._titleOffset)+','+(b[this.Y]+b[this.H]/2)+') rotate(-90)'},this.y2Axis._titleFormat||{}))}this._drawAxis(this.y2Axis,'y2Axis',b[this.X]+b[this.W],b[this.Y],b[this.X]+b[this.W],b[this.Y]+b[this.H])}},_drawAxis:function(a,b,c,d,e,f){var g=(d===f);var h=this._wrapper.group(this._chartCont,$.extend({class_:b},a._lineFormat));var i=this._wrapper.group(this._chartCont,$.extend({class_:b+'Labels',textAnchor:(g?'middle':'end')},a._labelFormat));this._wrapper.line(h,c,d,e,f);if(a._ticks.major){var j=(e>(this._getValue(this._chartCont,'width')/2)&&f>(this._getValue(this._chartCont,'height')/2));var k=(g?e-c:f-d)/(a._scale.max-a._scale.min);var l=a._ticks.size;var m=Math.floor(a._scale.min/a._ticks.major)*a._ticks.major;m=(m<a._scale.min?m+a._ticks.major:m);var n=(!a._ticks.minor?a._scale.max+1:Math.floor(a._scale.min/a._ticks.minor)*a._ticks.minor);n=(n<a._scale.min?n+a._ticks.minor:n);var o=this._getTickOffsets(a,j);var p=0;while(m<=a._scale.max||n<=a._scale.max){var q=Math.min(m,n);var r=(q===m?l:l/2);var v=(g?c:d)+(g?q-a._scale.min:a._scale.max-q)*k;this._wrapper.line(h,(g?v:c+r*o[0]),(g?d+r*o[0]:v),(g?v:c+r*o[1]),(g?d+r*o[1]:v));if(q===m){this._wrapper.text(i,(g?v:c-l),(g?d+2*l:v),(a._labels?a._labels[p++]:''+q))}m+=(q===m?a._ticks.major:0);n+=(q===n?a._ticks.minor:0)}}},_getTickOffsets:function(a,b){return[(a._ticks.position===(b?'in':'out')||a._ticks.position==='both'?-1:0),(a._ticks.position===(b?'out':'in')||a._ticks.position==='both'?+1:0),]},_getPercentageAxis:function(){this._percentageAxis._title=$.svg.graphing.region.percentageText;return this._percentageAxis},_getTotals:function(){var a=[];var b=(this._series.length?this._series[0]._values.length:0);for(var i=0;i<b;i++){a[i]=0;for(var j=0;j<this._series.length;j++){a[i]+=this._series[j]._values[i]}}return a},_drawLegend:function(){if(!this.legend._show){return}var g=this._wrapper.group(this._chartCont,{class_:'legend'});var a=this._getDims(this.legend._area);this._wrapper.rect(g,a[this.X],a[this.Y],a[this.W],a[this.H],this.legend._bgSettings);var b=a[this.W]>a[this.H];var c=this._series.length;var d=(b?a[this.W]:a[this.H])/c;var e=a[this.X]+5;var f=a[this.Y]+((b?a[this.H]:d)+this.legend._sampleSize)/2;for(var i=0;i<c;i++){var h=this._series[i];this._wrapper.rect(g,e+(b?i*d:0),f+(b?0:i*d)-this.legend._sampleSize,this.legend._sampleSize,this.legend._sampleSize,{fill:h._fill,stroke:h._stroke,strokeWidth:1});this._wrapper.text(g,e+(b?i*d:0)+this.legend._sampleSize+5,f+(b?0:i*d),h._name,this.legend._textSettings)}},_showStatus:function(a,b,c){var d=this._onstatus;if(this._onstatus){$(a).hover(function(){d.apply(this,[b,c])},function(){d.apply(this,['',0])})}}});function SVGGraphSeries(a,b,c,d,e,f,g){if(typeof b!=='string'){g=f;f=e;e=d;d=c;c=b;b=null}if(typeof e!=='string'){g=f;f=e;e=null}if(typeof f!=='number'){g=f;f=null}this._graph=a;this._name=b||'';this._values=c||[];this._axis=1;this._fill=d||'green';this._stroke=e||'black';this._strokeWidth=f||1;this._settings=g||{}}$.extend(SVGGraphSeries.prototype,{name:function(a){if(arguments.length===0){return this._name}this._name=a;this._graph._drawGraph();return this},values:function(a,b){if(arguments.length===0){return this._values}if($.isArray(a)){b=a;a=null}this._name=a||this._name;this._values=b;this._graph._drawGraph();return this},format:function(a,b,c,d){if(arguments.length===0){return $.extend({fill:this._fill,stroke:this._stroke,strokeWidth:this._strokeWidth},this._settings)}if(typeof b!=='string'){d=c;c=b;b=null}if(typeof c!=='number'){d=c;c=null}this._fill=a||this._fill;this._stroke=b||this._stroke;this._strokeWidth=c||this._strokeWidth;$.extend(this._settings,d||{});this._graph._drawGraph();return this},end:function(){return this._graph}});function SVGGraphAxis(a,b,c,d,e,f){this._graph=a;this._title=b||'';this._titleFormat={};this._titleOffset=0;this._labels=null;this._labelFormat={};this._lineFormat={stroke:'black',strokeWidth:1};this._ticks={major:e||10,minor:f||0,size:10,position:'out'};this._scale={min:c||0,max:d||100};this._crossAt=0}$.extend(SVGGraphAxis.prototype,{scale:function(a,b){if(arguments.length===0){return this._scale}this._scale.min=a;this._scale.max=b;this._graph._drawGraph();return this},ticks:function(a,b,c,d){if(arguments.length===0){return this._ticks}if(typeof c==='string'){d=c;c=null}this._ticks.major=a;this._ticks.minor=b;this._ticks.size=c||this._ticks.size;this._ticks.position=d||this._ticks.position;this._graph._drawGraph();return this},title:function(a,b,c,d){if(arguments.length===0){return{title:this._title,offset:this._titleOffset,format:this._titleFormat}}if(typeof b!=='number'){d=c;c=b;b=null}if(typeof c!=='string'){d=c;c=null}this._title=a;this._titleOffset=(b!=null?b:this._titleOffset);if(c||d){this._titleFormat=$.extend(d||{},(c?{fill:c}:{}))}this._graph._drawGraph();return this},labels:function(a,b,c){if(arguments.length===0){return{labels:this._labels,format:this._labelFormat}}if(typeof b!=='string'){c=b;b=null}this._labels=a;if(b||c){this._labelFormat=$.extend(c||{},(b?{fill:b}:{}))}this._graph._drawGraph();return this},line:function(a,b,c){if(arguments.length===0){return this._lineFormat}if(typeof b==='object'){c=b;b=null}$.extend(this._lineFormat,{stroke:a},(b?{strokeWidth:b}:{}),c||{});this._graph._drawGraph();return this},end:function(){return this._graph}});function SVGGraphLegend(a,b,c){this._graph=a;this._show=true;this._area=[0.9,0.1,1.0,0.9];this._sampleSize=15;this._bgSettings=b||{stroke:'gray'};this._textSettings=c||{}}$.extend(SVGGraphLegend.prototype,{show:function(a){if(arguments.length===0){return this._show}this._show=a;this._graph._drawGraph();return this},area:function(a,b,c,d){if(arguments.length===0){return this._area}this._area=($.isArray(a)?a:[a,b,c,d]);this._graph._drawGraph();return this},settings:function(a,b,c){if(arguments.length===0){return{sampleSize:this._sampleSize,bgSettings:this._bgSettings,textSettings:this._textSettings}}if(typeof a!=='number'){c=b;b=a;a=null}this._sampleSize=a||this._sampleSize;this._bgSettings=b;this._textSettings=c||this._textSettings;this._graph._drawGraph();return this},end:function(){return this._graph}});function roundNumber(a,b){return Math.round(a*Math.pow(10,b))/Math.pow(10,b)}var B=['barWidth (number) - the width of each bar','barGap (number) - the gap between sets of bars'];function SVGColumnChart(){}$.extend(SVGColumnChart.prototype,{title:function(){return'Basic column chart'},description:function(){return'Compare sets of values as vertical bars with grouped categories.'},options:function(){return B},drawGraph:function(a){a._drawChartBackground(true);var b=a._chartOptions.barWidth||10;var c=a._chartOptions.barGap||10;var d=a._series.length;var e=(d?(a._series[0])._values.length:0);var f=a._getDims();var g=f[a.W]/((d*b+c)*e+c);var h=f[a.H]/(a.yAxis._scale.max-a.yAxis._scale.min);this._chart=a._wrapper.group(a._chartCont,{class_:'chart'});for(var i=0;i<d;i++){this._drawSeries(a,i,d,b,c,f,g,h)}a._drawTitle();a._drawAxes(true);this._drawXAxis(a,d,e,b,c,f,g);a._drawLegend()},_drawSeries:function(a,b,c,d,e,f,h,j){var k=a._series[b];var g=a._wrapper.group(this._chart,$.extend({class_:'series'+b,fill:k._fill,stroke:k._stroke,strokeWidth:k._strokeWidth},k._settings||{}));for(var i=0;i<k._values.length;i++){var r=a._wrapper.rect(g,f[a.X]+h*(e+i*(c*d+e)+(b*d)),f[a.Y]+j*(a.yAxis._scale.max-k._values[i]),h*d,j*k._values[i]);a._showStatus(r,k._name,k._values[i])}},_drawXAxis:function(a,b,c,d,e,f,g){var h=a.xAxis;if(h._title){a._wrapper.text(a._chartCont,f[a.X]+f[a.W]/2,f[a.Y]+f[a.H]+h._titleOffset,h._title,$.extend({textAnchor:'middle'},h._titleFormat||{}))}var j=a._wrapper.group(a._chartCont,$.extend({class_:'xAxis'},h._lineFormat));var k=a._wrapper.group(a._chartCont,$.extend({class_:'xAxisLabels',textAnchor:'middle'},h._labelFormat));a._wrapper.line(j,f[a.X],f[a.Y]+f[a.H],f[a.X]+f[a.W],f[a.Y]+f[a.H]);if(h._ticks.major){var l=a._getTickOffsets(h,true);for(var i=1;i<c;i++){var x=f[a.X]+g*(e/2+i*(b*d+e));a._wrapper.line(j,x,f[a.Y]+f[a.H]+l[0]*h._ticks.size,x,f[a.Y]+f[a.H]+l[1]*h._ticks.size)}for(var i=0;i<c;i++){var x=f[a.X]+g*(e/2+(i+0.5)*(b*d+e));a._wrapper.text(k,x,f[a.Y]+f[a.H]+2*h._ticks.size,(h._labels?h._labels[i]:''+i))}}}});function SVGStackedColumnChart(){}$.extend(SVGStackedColumnChart.prototype,{title:function(){return'Stacked column chart'},description:function(){return'Compare sets of values as vertical bars showing '+'relative contributions to the whole for each category.'},options:function(){return B},drawGraph:function(a){var b=a._drawChartBackground(true,true);var c=a._getDims();if(a._gridlines[0]&&a.xAxis._ticks.major){a._drawGridlines(b,a._getPercentageAxis(),true,c,a._gridlines[0])}var d=a._chartOptions.barWidth||10;var e=a._chartOptions.barGap||10;var f=a._series.length;var g=(f?(a._series[0])._values.length:0);var h=c[a.W]/((d+e)*g+e);var i=c[a.H];this._chart=a._wrapper.group(a._chartCont,{class_:'chart'});this._drawColumns(a,f,g,d,e,c,h,i);a._drawTitle();a._wrapper.text(a._chartCont,0,0,$.svg.graphing.region.percentageText,$.extend({textAnchor:'middle',transform:'translate('+(c[a.X]-a.yAxis._titleOffset)+','+(c[a.Y]+c[a.H]/2)+') rotate(-90)'},a.yAxis._titleFormat||{}));var j=$.extend({},a._getPercentageAxis());$.extend(j._labelFormat,a.yAxis._labelFormat||{});a._drawAxis(j,'yAxis',c[a.X],c[a.Y],c[a.X],c[a.Y]+c[a.H]);this._drawXAxis(a,g,d,e,c,h);a._drawLegend()},_drawColumns:function(a,b,c,d,e,f,h,j){var k=a._getTotals();var l=[];for(var i=0;i<c;i++){l[i]=0}for(var s=0;s<b;s++){var m=a._series[s];var g=a._wrapper.group(this._chart,$.extend({class_:'series'+s,fill:m._fill,stroke:m._stroke,strokeWidth:m._strokeWidth},m._settings||{}));for(var i=0;i<m._values.length;i++){l[i]+=m._values[i];var r=a._wrapper.rect(g,f[a.X]+h*(e+i*(d+e)),f[a.Y]+j*(k[i]-l[i])/k[i],h*d,j*m._values[i]/k[i]);a._showStatus(r,m._name,roundNumber(m._values[i]/k[i]*100,2))}}},_drawXAxis:function(a,b,c,d,e,f){var g=a.xAxis;if(g._title){a._wrapper.text(a._chartCont,e[a.X]+e[a.W]/2,e[a.Y]+e[a.H]+g._titleOffset,g._title,$.extend({textAnchor:'middle'},g._titleFormat||{}))}var h=a._wrapper.group(a._chartCont,$.extend({class_:'xAxis'},g._lineFormat));var j=a._wrapper.group(a._chartCont,$.extend({class_:'xAxisLabels',textAnchor:'middle'},g._labelFormat));a._wrapper.line(h,e[a.X],e[a.Y]+e[a.H],e[a.X]+e[a.W],e[a.Y]+e[a.H]);if(g._ticks.major){var k=a._getTickOffsets(g,true);for(var i=1;i<b;i++){var x=e[a.X]+f*(d/2+i*(c+d));a._wrapper.line(h,x,e[a.Y]+e[a.H]+k[0]*g._ticks.size,x,e[a.Y]+e[a.H]+k[1]*g._ticks.size)}for(var i=0;i<b;i++){var x=e[a.X]+f*(d/2+(i+0.5)*(c+d));a._wrapper.text(j,x,e[a.Y]+e[a.H]+2*g._ticks.size,(g._labels?g._labels[i]:''+i))}}}});function SVGRowChart(){}$.extend(SVGRowChart.prototype,{title:function(){return'Basic row chart'},description:function(){return'Compare sets of values as horizontal rows with grouped categories.'},options:function(){return B},drawGraph:function(a){var b=a._drawChartBackground(true,true);var c=a._getDims();a._drawGridlines(b,a.yAxis,false,c,a._gridlines[0]);var d=a._chartOptions.barWidth||10;var e=a._chartOptions.barGap||10;var f=a._series.length;var g=(f?(a._series[0])._values.length:0);var h=c[a.W]/(a.yAxis._scale.max-a.yAxis._scale.min);var j=c[a.H]/((f*d+e)*g+e);this._chart=a._wrapper.group(a._chartCont,{class_:'chart'});for(var i=0;i<f;i++){this._drawSeries(a,i,f,d,e,c,h,j)}a._drawTitle();this._drawAxes(a,f,g,d,e,c,j);a._drawLegend()},_drawSeries:function(a,b,c,d,e,f,h,j){var k=a._series[b];var g=a._wrapper.group(this._chart,$.extend({class_:'series'+b,fill:k._fill,stroke:k._stroke,strokeWidth:k._strokeWidth},k._settings||{}));for(var i=0;i<k._values.length;i++){var r=a._wrapper.rect(g,f[a.X]+h*(0-a.yAxis._scale.min),f[a.Y]+j*(e+i*(c*d+e)+(b*d)),h*k._values[i],j*d);a._showStatus(r,k._name,k._values[i])}},_drawAxes:function(a,b,c,d,e,f,g){var h=a.yAxis;if(h){if(h._title){a._wrapper.text(a._chartCont,f[a.X]+f[a.W]/2,f[a.Y]+f[a.H]+h._titleOffset,h._title,h._titleFormat)}a._drawAxis(h,'xAxis',f[a.X],f[a.Y]+f[a.H],f[a.X]+f[a.W],f[a.Y]+f[a.H])}var h=a.xAxis;if(h._title){a._wrapper.text(a._chartCont,0,0,h._title,$.extend({textAnchor:'middle',transform:'translate('+(f[a.X]-h._titleOffset)+','+(f[a.Y]+f[a.H]/2)+') rotate(-90)'},h._titleFormat||{}))}var j=a._wrapper.group(a._chartCont,$.extend({class_:'yAxis'},h._lineFormat));var k=a._wrapper.group(a._chartCont,$.extend({class_:'yAxisLabels',textAnchor:'end'},h._labelFormat));a._wrapper.line(j,f[a.X],f[a.Y],f[a.X],f[a.Y]+f[a.H]);if(h._ticks.major){var l=a._getTickOffsets(h,false);for(var i=1;i<c;i++){var y=f[a.Y]+g*(e/2+i*(b*d+e));a._wrapper.line(j,f[a.X]+l[0]*h._ticks.size,y,f[a.X]+l[1]*h._ticks.size,y)}for(var i=0;i<c;i++){var y=f[a.Y]+g*(e/2+(i+0.5)*(b*d+e));a._wrapper.text(k,f[a.X]-h._ticks.size,y,(h._labels?h._labels[i]:''+i))}}}});function SVGStackedRowChart(){}$.extend(SVGStackedRowChart.prototype,{title:function(){return'Stacked row chart'},description:function(){return'Compare sets of values as horizontal bars showing '+'relative contributions to the whole for each category.'},options:function(){return B},drawGraph:function(a){var b=a._drawChartBackground(true,true);var c=a._getDims();if(a._gridlines[0]&&a.xAxis._ticks.major){a._drawGridlines(b,a._getPercentageAxis(),false,c,a._gridlines[0])}var d=a._chartOptions.barWidth||10;var e=a._chartOptions.barGap||10;var f=a._series.length;var g=(f?(a._series[0])._values.length:0);var h=c[a.W];var i=c[a.H]/((d+e)*g+e);this._chart=a._wrapper.group(a._chartCont,{class_:'chart'});this._drawRows(a,f,g,d,e,c,h,i);a._drawTitle();a._wrapper.text(a._chartCont,c[a.X]+c[a.W]/2,c[a.Y]+c[a.H]+a.xAxis._titleOffset,$.svg.graphing.region.percentageText,$.extend({textAnchor:'middle'},a.yAxis._titleFormat||{}));var j=$.extend({},a._getPercentageAxis());$.extend(j._labelFormat,a.yAxis._labelFormat||{});a._drawAxis(j,'xAxis',c[a.X],c[a.Y]+c[a.H],c[a.X]+c[a.W],c[a.Y]+c[a.H]);this._drawYAxis(a,g,d,e,c,i);a._drawLegend()},_drawRows:function(a,b,c,d,e,f,h,j){var k=a._getTotals();var l=[];for(var i=0;i<c;i++){l[i]=0}for(var s=0;s<b;s++){var m=a._series[s];var g=a._wrapper.group(this._chart,$.extend({class_:'series'+s,fill:m._fill,stroke:m._stroke,strokeWidth:m._strokeWidth},m._settings||{}));for(var i=0;i<m._values.length;i++){var r=a._wrapper.rect(g,f[a.X]+h*l[i]/k[i],f[a.Y]+j*(e+i*(d+e)),h*m._values[i]/k[i],j*d);a._showStatus(r,m._name,roundNumber(m._values[i]/k[i]*100,2));l[i]+=m._values[i]}}},_drawYAxis:function(a,b,c,d,e,f){var g=a.xAxis;if(g._title){a._wrapper.text(a._chartCont,0,0,g._title,$.extend({textAnchor:'middle',transform:'translate('+(e[a.X]-g._titleOffset)+','+(e[a.Y]+e[a.H]/2)+') rotate(-90)'},g._titleFormat||{}))}var h=a._wrapper.group(a._chartCont,$.extend({class_:'yAxis'},g._lineFormat));var j=a._wrapper.group(a._chartCont,$.extend({class_:'yAxisLabels',textAnchor:'end'},g._labelFormat));a._wrapper.line(h,e[a.X],e[a.Y],e[a.X],e[a.Y]+e[a.H]);if(g._ticks.major){var k=a._getTickOffsets(g,false);for(var i=1;i<b;i++){var y=e[a.Y]+f*(d/2+i*(c+d));a._wrapper.line(h,e[a.X]+k[0]*g._ticks.size,y,e[a.X]+k[1]*g._ticks.size,y)}for(var i=0;i<b;i++){var y=e[a.Y]+f*(d/2+(i+0.5)*(c+d));a._wrapper.text(j,e[a.X]-g._ticks.size,y,(g._labels?g._labels[i]:''+i))}}}});function SVGLineChart(){}$.extend(SVGLineChart.prototype,{title:function(){return'Basic line chart'},description:function(){return'Compare sets of values as continuous lines.'},options:function(){return[]},drawGraph:function(a){a._drawChartBackground();var b=a._getDims();var c=b[a.W]/(a.xAxis._scale.max-a.xAxis._scale.min);var d=b[a.H]/(a.yAxis._scale.max-a.yAxis._scale.min);this._chart=a._wrapper.group(a._chartCont,{class_:'chart'});for(var i=0;i<a._series.length;i++){this._drawSeries(a,i,b,c,d)}a._drawTitle();a._drawAxes();a._drawLegend()},_drawSeries:function(a,b,c,d,e){var f=a._series[b];var g=a._wrapper.createPath();for(var i=0;i<f._values.length;i++){var x=c[a.X]+i*d;var y=c[a.Y]+(a.yAxis._scale.max-f._values[i])*e;if(i===0){g.move(x,y)}else{g.line(x,y)}}var p=a._wrapper.path(this._chart,g,$.extend({id:'series'+b,fill:'none',stroke:f._stroke,strokeWidth:f._strokeWidth},f._settings||{}));a._showStatus(p,f._name,0)}});function SVGPieChart(){}$.extend(SVGPieChart.prototype,{_options:['explode (number or number[]) - indexes of sections to explode out of the pie','explodeDist (number) - the distance to move an exploded section','pieGap (number) - the distance between pies for multiple values'],title:function(){return'Pie chart'},description:function(){return'Compare relative sizes of values as contributions to the whole.'},options:function(){return this._options},drawGraph:function(a){a._drawChartBackground(true,true);this._chart=a._wrapper.group(a._chartCont,{class_:'chart'});var b=a._getDims();this._drawSeries(a,b);a._drawTitle();a._drawLegend()},_drawSeries:function(a,b){var c=a._getTotals();var d=a._series.length;var e=(d?(a._series[0])._values.length:0);var f=a._wrapper.createPath();var g=a._chartOptions.explode||[];g=($.isArray(g)?g:[g]);var h=a._chartOptions.explodeDist||10;var l=(e<=1?0:a._chartOptions.pieGap||10);var m=(b[a.W]-(e*l)-l)/e/2;var n=b[a.H]/2;var o=Math.min(m,n)-(g.length>0?h:0);var q=a._wrapper.group(a._chartCont,$.extend({class_:'xAxisLabels',textAnchor:'middle'},a.xAxis._labelFormat));var r=[];for(var i=0;i<e;i++){var s=b[a.X]+m+(i*(2*Math.min(m,n)+l))+l;var t=b[a.Y]+n;var u=0;for(var j=0;j<d;j++){var v=a._series[j];if(i===0){r[j]=a._wrapper.group(this._chart,$.extend({class_:'series'+j,fill:v._fill,stroke:v._stroke,strokeWidth:v._strokeWidth},v._settings||{}))}if(v._values[i]===0){continue}var w=(u/c[i])*2*Math.PI;u+=v._values[i];var z=(u/c[i])*2*Math.PI;var A=false;for(var k=0;k<g.length;k++){if(g[k]===j){A=true;break}}var x=s+(A?h*Math.cos((w+z)/2):0);var y=t+(A?h*Math.sin((w+z)/2):0);var p=a._wrapper.path(r[j],f.reset().move(x,y).line(x+o*Math.cos(w),y+o*Math.sin(w)).arc(o,o,0,(z-w<Math.PI?0:1),1,x+o*Math.cos(z),y+o*Math.sin(z)).close());a._showStatus(p,v._name,roundNumber((z-w)/2/Math.PI*100,2))}if(a.xAxis){a._wrapper.text(q,s,b[a.Y]+b[a.H]+a.xAxis._titleOffset,a.xAxis._labels[i])}}}});$.svg.graphing.addChartType('column',new SVGColumnChart());$.svg.graphing.addChartType('stackedColumn',new SVGStackedColumnChart());$.svg.graphing.addChartType('row',new SVGRowChart());$.svg.graphing.addChartType('stackedRow',new SVGStackedRowChart());$.svg.graphing.addChartType('line',new SVGLineChart());$.svg.graphing.addChartType('pie',new SVGPieChart())})(jQuery)
@@ -0,0 +1,813 @@
1
+ /* http://keith-wood.name/svg.html
2
+ SVG plotting extension for jQuery v1.5.0.
3
+ Written by Keith Wood (kbwood{at}iinet.com.au) December 2008.
4
+ Available under the MIT (http://keith-wood.name/licence.html) license.
5
+ Please attribute the author if you use it. */
6
+
7
+ (function($) { // Hide scope, no $ conflict
8
+
9
+ $.svg.addExtension('plot', SVGPlot);
10
+
11
+ /** The SVG plotting manager.
12
+ <p>Use the singleton instance of this class, $.svg.plot,
13
+ to interact with the SVG plotting functionality.</p>
14
+ @module SVGPlot */
15
+ function SVGPlot(wrapper) {
16
+ this._wrapper = wrapper; // The attached SVG wrapper object
17
+ this._drawNow = false; // True for immediate update, false to wait for redraw call
18
+ // The plot title and settings
19
+ this._title = {value: '', offset: 25, settings: {textAnchor: 'middle'}};
20
+ this._area = [0.1, 0.1, 0.8, 0.9]; // The chart area: left, top, right, bottom, > 1 in pixels, <= 1 as proportion
21
+ this._areaFormat = {fill: 'none', stroke: 'black'}; // The formatting for the plot area
22
+ this._gridlines = []; // The formatting of the x- and y-gridlines
23
+ this._equalXY = true; // True for equal-sized x- and y-units, false to fill available space
24
+ this._functions = []; // The functions to be plotted, each is an object
25
+ this._onstatus = null; // The callback function for status updates
26
+ this._uuid = new Date().getTime();
27
+ this._plotCont = this._wrapper.svg(0, 0, 0, 0, {class_: 'svg-plot'}); // The main container for the plot
28
+
29
+ /** The main x-axis for this plot. */
30
+ this.xAxis = new SVGPlotAxis(this);
31
+ this.xAxis.title('X', 20);
32
+ /** The main y-axis for this plot. */
33
+ this.yAxis = new SVGPlotAxis(this);
34
+ this.yAxis.title('Y', 20);
35
+ /** The legend for this plot. */
36
+ this.legend = new SVGPlotLegend(this);
37
+ this._drawNow = true;
38
+ }
39
+
40
+ $.extend(SVGPlot.prototype, {
41
+
42
+ /* Useful indexes. */
43
+ /** Index in a dimensions array for x-coordinate. */
44
+ X: 0,
45
+ /** Index in a dimensions array for y-coordinate. */
46
+ Y: 1,
47
+ /** Index in a dimensions array for width. */
48
+ W: 2,
49
+ /** Index in a dimensions array for height. */
50
+ H: 3,
51
+ /** Index in an area array for left x-coordinate. */
52
+ L: 0,
53
+ /** Index in an area array for top y-coordinate. */
54
+ T: 1,
55
+ /** Index in an area array for right x-coordinate. */
56
+ R: 2,
57
+ /** Index in an area array for bottom y-coordinate. */
58
+ B: 3,
59
+
60
+ /** Set or retrieve the container for the plot.
61
+ @param cont {SVGElement} The container for the plot.
62
+ @return {SVGPlot|SVGElement} This plot object or the current container (if no parameters). */
63
+ container: function(cont) {
64
+ if (arguments.length === 0) {
65
+ return this._plotCont;
66
+ }
67
+ this._plotCont = cont;
68
+ return this;
69
+ },
70
+
71
+ /** Set or retrieve the main plotting area.
72
+ @param left {number|number[]} > 1 is pixels, <= 1 is proportion of width or array for left, top, right, bottom.
73
+ @param [top] {number) > 1 is pixels, <= 1 is proportion of height.
74
+ @param [right] {number) > 1 is pixels, <= 1 is proportion of width.
75
+ @param [bottom] {number) > 1 is pixels, <= 1 is proportion of height.
76
+ @return {SVGPlot|number[]} This plot object or the plotting area: left, top, right, bottom (if no parameters). */
77
+ area: function(left, top, right, bottom) {
78
+ if (arguments.length === 0) {
79
+ return this._area;
80
+ }
81
+ this._area = ($.isArray(left) ? left : [left, top, right, bottom]);
82
+ this._drawPlot();
83
+ return this;
84
+ },
85
+
86
+ /** Set or retrieve the background of the plot area.
87
+ @param fill {string} How to fill the area background.
88
+ @param [stroke] {string} The colour of the outline.
89
+ @param [settings] {object} Additional formatting for the area background.
90
+ @return {SVGPlot|object} This plot object or the area format (if no parameters). */
91
+ format: function(fill, stroke, settings) {
92
+ if (arguments.length === 0) {
93
+ return this._areaFormat;
94
+ }
95
+ if (typeof stroke === 'object') {
96
+ settings = stroke;
97
+ stroke = null;
98
+ }
99
+ this._areaFormat = $.extend({fill: fill}, (stroke ? {stroke: stroke} : {}), settings || {});
100
+ this._drawPlot();
101
+ return this;
102
+ },
103
+
104
+ /** Set or retrieve the gridlines formatting for the plot area.
105
+ @param xSettings {string|object} The colour of the gridlines along the x-axis,
106
+ or formatting for the gridlines along the x-axis, or <code>null</code> for none.
107
+ @param ySettings {string|object} The colour of the gridlines along the y-axis,
108
+ or formatting for the gridlines along the y-axis, or <code>null</code> for none.
109
+ @return {SVGPlot|object[]} This plot object or the gridlines formatting (if no parameters). */
110
+ gridlines: function(xSettings, ySettings) {
111
+ if (arguments.length === 0) {
112
+ return this._gridlines;
113
+ }
114
+ this._gridlines = [(typeof xSettings === 'string' ? {stroke: xSettings} : xSettings),
115
+ (typeof ySettings === 'string' ? {stroke: ySettings} : ySettings)];
116
+ if (this._gridlines[0] == null && this._gridlines[1] == null) {
117
+ this._gridlines = [];
118
+ }
119
+ this._drawPlot();
120
+ return this;
121
+ },
122
+
123
+ /** Set or retrieve the equality of the x- and y-axes.
124
+ @param value {boolean} <code>true</code> for equal x- and y-units,
125
+ <code>false</code> to fill the available space.
126
+ @return {SVGPlot|boolean} This plot object or the current setting (if no parameters). */
127
+ equalXY: function(value) {
128
+ if (arguments.length === 0) {
129
+ return this._equalXY;
130
+ }
131
+ this._equalXY = value;
132
+ return this;
133
+ },
134
+
135
+ /** Set or retrieve the title of the plot and its formatting.
136
+ @param value {string} The title.
137
+ @param [offset] {number} The vertical positioning of the title, > 1 is pixels, <= 1 is proportion of width.
138
+ @param [colour] {string} The colour of the title.
139
+ @param [settings] {object} Formatting for the title.
140
+ @return {SVGPlot|object} This plot object or value, offset, and settings for the title (if no parameters). */
141
+ title: function(value, offset, colour, settings) {
142
+ if (arguments.length === 0) {
143
+ return this._title;
144
+ }
145
+ if (typeof offset !== 'number') {
146
+ settings = colour;
147
+ colour = offset;
148
+ offset = null;
149
+ }
150
+ if (typeof colour !== 'string') {
151
+ settings = colour;
152
+ colour = null;
153
+ }
154
+ this._title = {value: value, offset: offset || this._title.offset,
155
+ settings: $.extend({textAnchor: 'middle'}, (colour ? {fill: colour} : {}), settings || {})};
156
+ this._drawPlot();
157
+ return this;
158
+ },
159
+
160
+ /** Add a function to be plotted on the plot.
161
+ @param [name] {string} The name of this series.
162
+ @param fn {function} The function to be plotted.
163
+ @param [range] {number[]} The range of values to plot.
164
+ @param [points] {number} The number of points to plot within this range.
165
+ @param [stroke] {string} The colour of the plotted lines.
166
+ @param [strokeWidth] {number} The width of the plotted lines.
167
+ @param [settings] {object} Additional settings for the plotted values.
168
+ @return {SVGPlot} This plot object. */
169
+ addFunction: function(name, fn, range, points, stroke, strokeWidth, settings) {
170
+ this._functions.push(new SVGPlotFunction(this, name, fn, range, points, stroke, strokeWidth, settings));
171
+ this._drawPlot();
172
+ return this;
173
+ },
174
+
175
+ /** Retrieve the function wrappers.
176
+ @param [i] {number} the function index.
177
+ @return {SVGPlotFunction|SVGPlotFunction[]} the specified function or the list of functions. */
178
+ functions: function(i) {
179
+ return (arguments.length > 0 ? this._functions[i] : null) || this._functions;
180
+ },
181
+
182
+ /** Suppress drawing of the plot until redraw() is called.
183
+ @return {SVGPlot} this plot object. */
184
+ noDraw: function() {
185
+ this._drawNow = false;
186
+ return this;
187
+ },
188
+
189
+ /** Redraw the entire plot with the current settings and values.
190
+ @return {SVGPlot} This plot object. */
191
+ redraw: function() {
192
+ this._drawNow = true;
193
+ this._drawPlot();
194
+ return this;
195
+ },
196
+
197
+ /** Set the callback function for status updates.
198
+ @param onstatus {function} The callback function.
199
+ @return {SVGPlot} This plot object. */
200
+ status: function(onstatus) {
201
+ this._onstatus = onstatus;
202
+ return this;
203
+ },
204
+
205
+ /** Actually draw the plot (if allowed).
206
+ @private */
207
+ _drawPlot: function() {
208
+ if (!this._drawNow) {
209
+ return;
210
+ }
211
+ while (this._plotCont.firstChild) {
212
+ this._plotCont.removeChild(this._plotCont.firstChild);
213
+ }
214
+ if (!this._plotCont.parent) {
215
+ this._wrapper._svg.appendChild(this._plotCont);
216
+ }
217
+ // Set sizes if not already there
218
+ if (!this._plotCont.width) {
219
+ this._plotCont.setAttribute('width',
220
+ parseInt(this._plotCont.getAttribute('width'), 10) || this._wrapper.width());
221
+ }
222
+ else if (this._plotCont.width.baseVal) {
223
+ this._plotCont.width.baseVal.value = this._plotCont.width.baseVal.value || this._wrapper.width();
224
+ }
225
+ else {
226
+ this._plotCont.width = this._plotCont.width || this._wrapper.width();
227
+ }
228
+ if (!this._plotCont.height) {
229
+ this._plotCont.setAttribute('height',
230
+ parseInt(this._plotCont.getAttribute('height'), 10) || this._wrapper.height());
231
+ }
232
+ else if (this._plotCont.height.baseVal) {
233
+ this._plotCont.height.baseVal.value = this._plotCont.height.baseVal.value || this._wrapper.height();
234
+ }
235
+ else {
236
+ this._plotCont.height = this._plotCont.height || this._wrapper.height();
237
+ }
238
+ this._drawChartBackground();
239
+ var dims = this._getDims();
240
+ var clip = this._wrapper.other(this._plotCont, 'clipPath', {id: 'clip' + this._uuid});
241
+ this._wrapper.rect(clip, dims[this.X], dims[this.Y], dims[this.W], dims[this.H]);
242
+ this._plot = this._wrapper.group(this._plotCont,
243
+ {class_: 'foreground', clipPath: 'url(#clip' + this._uuid + ')'});
244
+ this._drawAxis(true);
245
+ this._drawAxis(false);
246
+ for (var i = 0; i < this._functions.length; i++) {
247
+ this._plotFunction(this._functions[i], i);
248
+ }
249
+ this._drawTitle();
250
+ this._drawLegend();
251
+ },
252
+
253
+ /** Decode an attribute value.
254
+ @private
255
+ @param node {SVGElement} The node to examine.
256
+ @param name {string} The attribute name.
257
+ @return {string} The actual value. */
258
+ _getValue: function(node, name) {
259
+ return (!node[name] ? parseInt(node.getAttribute(name), 10) :
260
+ (node[name].baseVal ? node[name].baseVal.value : node[name]));
261
+ },
262
+
263
+ /** Calculate the actual dimensions of the plot area.
264
+ @private
265
+ @param [area] {number[]} The area values to evaluate, or the current area if not specified.
266
+ @return {number[]} An array of dimension values: left, top, width, height. */
267
+ _getDims: function(area) {
268
+ var otherArea = (area != null);
269
+ area = area || this._area;
270
+ var availWidth = this._getValue(this._plotCont, 'width');
271
+ var availHeight = this._getValue(this._plotCont, 'height');
272
+ var left = (area[this.L] > 1 ? area[this.L] : availWidth * area[this.L]);
273
+ var top = (area[this.T] > 1 ? area[this.T] : availHeight * area[this.T]);
274
+ var width = (area[this.R] > 1 ? area[this.R] : availWidth * area[this.R]) - left;
275
+ var height = (area[this.B] > 1 ? area[this.B] : availHeight * area[this.B]) - top;
276
+ if (this._equalXY && !otherArea) {
277
+ var scale = Math.min(width / (this.xAxis._scale.max - this.xAxis._scale.min),
278
+ height / (this.yAxis._scale.max - this.yAxis._scale.min));
279
+ width = scale * (this.xAxis._scale.max - this.xAxis._scale.min);
280
+ height = scale * (this.yAxis._scale.max - this.yAxis._scale.min);
281
+ }
282
+ return [left, top, width, height];
283
+ },
284
+
285
+ /** Calculate the scaling factors for the plot area.
286
+ @private
287
+ @return {number[]} The x- and y-scaling factors. */
288
+ _getScales: function() {
289
+ var dims = this._getDims();
290
+ return [dims[this.W] / (this.xAxis._scale.max - this.xAxis._scale.min),
291
+ dims[this.H] / (this.yAxis._scale.max - this.yAxis._scale.min)];
292
+ },
293
+
294
+ /** Draw the chart background, including gridlines.
295
+ @private
296
+ @param [noXGrid] {boolean} <code>true</code> to suppress the x-gridlines, <code>false</code> to draw them.
297
+ @param [noYGrid] {boolean} <code>true</code> to suppress the y-gridlines, <code>false</code> to draw them.
298
+ @return {SVGElement} The background group element. */
299
+ _drawChartBackground: function(noXGrid, noYGrid) {
300
+ var bg = this._wrapper.group(this._plotCont, {class_: 'background'});
301
+ var dims = this._getDims();
302
+ this._wrapper.rect(bg, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], this._areaFormat);
303
+ if (this._gridlines[0] && this.yAxis._ticks.major && !noYGrid) {
304
+ this._drawGridlines(bg, true, this._gridlines[0], dims);
305
+ }
306
+ if (this._gridlines[1] && this.xAxis._ticks.major && !noXGrid) {
307
+ this._drawGridlines(bg, false, this._gridlines[1], dims);
308
+ }
309
+ return bg;
310
+ },
311
+
312
+ /** Draw one set of gridlines.
313
+ @private
314
+ @param bg {SVGElement} The background group element.
315
+ @param horiz {boolean} <code>true</code> if horizontal, <code>false</code> if vertical.
316
+ @param format {object} Additional settings for the gridlines. */
317
+ _drawGridlines: function(bg, horiz, format, dims) {
318
+ var g = this._wrapper.group(bg, format);
319
+ var axis = (horiz ? this.yAxis : this.xAxis);
320
+ var scales = this._getScales();
321
+ var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
322
+ major += (major <= axis._scale.min ? axis._ticks.major : 0);
323
+ while (major < axis._scale.max) {
324
+ var v = (horiz ? axis._scale.max - major : major - axis._scale.min) *
325
+ scales[horiz ? 1 : 0] + (horiz ? dims[this.Y] : dims[this.X]);
326
+ this._wrapper.line(g, (horiz ? dims[this.X] : v), (horiz ? v : dims[this.Y]),
327
+ (horiz ? dims[this.X] + dims[this.W] : v), (horiz ? v : dims[this.Y] + dims[this.H]));
328
+ major += axis._ticks.major;
329
+ }
330
+ },
331
+
332
+ /** Draw an axis, its tick marks, and title.
333
+ @private
334
+ @param horiz {boolean} <code>true</code> for x-axis, <code>false</code> for y-axis. */
335
+ _drawAxis: function(horiz) {
336
+ var id = (horiz ? 'x' : 'y') + 'Axis';
337
+ var axis = (horiz ? this.xAxis : this.yAxis);
338
+ var axis2 = (horiz ? this.yAxis : this.xAxis);
339
+ var dims = this._getDims();
340
+ var scales = this._getScales();
341
+ var gl = this._wrapper.group(this._plot, $.extend({class_: id}, axis._lineFormat));
342
+ var gt = this._wrapper.group(this._plot, $.extend({class_: id + 'Labels',
343
+ textAnchor: (horiz ? 'middle' : 'end')}, axis._labelFormat));
344
+ var zero = (horiz ? axis2._scale.max : -axis2._scale.min) *
345
+ scales[horiz ? 1 : 0] + (horiz ? dims[this.Y] : dims[this.X]);
346
+ this._wrapper.line(gl, (horiz ? dims[this.X] : zero), (horiz ? zero : dims[this.Y]),
347
+ (horiz ? dims[this.X] + dims[this.W] : zero), (horiz ? zero : dims[this.Y] + dims[this.H]));
348
+ if (axis._ticks.major) {
349
+ var size = axis._ticks.size;
350
+ var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
351
+ major = (major < axis._scale.min ? major + axis._ticks.major : major);
352
+ var minor = (!axis._ticks.minor ? axis._scale.max + 1 :
353
+ Math.floor(axis._scale.min / axis._ticks.minor) * axis._ticks.minor);
354
+ minor = (minor < axis._scale.min ? minor + axis._ticks.minor : minor);
355
+ var offsets = [(axis._ticks.position === 'nw' || axis._ticks.position === 'both' ? -1 : 0),
356
+ (axis._ticks.position === 'se' || axis._ticks.position === 'both' ? +1 : 0)];
357
+ while (major <= axis._scale.max || minor <= axis._scale.max) {
358
+ var cur = Math.min(major, minor);
359
+ var len = (cur === major ? size : size / 2);
360
+ var xy = (horiz ? cur - axis._scale.min : axis._scale.max - cur) *
361
+ scales[horiz ? 0 : 1] + (horiz ? dims[this.X] : dims[this.Y]);
362
+ this._wrapper.line(gl, (horiz ? xy : zero + len * offsets[0]), (horiz ? zero + len * offsets[0] : xy),
363
+ (horiz ? xy : zero + len * offsets[1]), (horiz ? zero + len * offsets[1] : xy));
364
+ if (cur === major && cur !== 0) {
365
+ this._wrapper.text(gt, (horiz ? xy : zero - size), (horiz ? zero - size : xy), '' + cur);
366
+ }
367
+ major += (cur === major ? axis._ticks.major : 0);
368
+ minor += (cur === minor ? axis._ticks.minor : 0);
369
+ }
370
+ }
371
+ if (axis._title) {
372
+ if (horiz) {
373
+ this._wrapper.text(this._plotCont, dims[this.X] - axis._titleOffset,
374
+ zero, axis._title, $.extend({textAnchor: 'end'}, axis._titleFormat || {}));
375
+ }
376
+ else {
377
+ this._wrapper.text(this._plotCont, zero, dims[this.Y] + dims[this.H] + axis._titleOffset,
378
+ axis._title, $.extend({textAnchor : 'middle'}, axis._titleFormat || {}));
379
+ }
380
+ }
381
+ },
382
+
383
+ /** Plot an individual function.
384
+ @private
385
+ @param fn {function} The function to plot.
386
+ @param cur {number} The current function index. */
387
+ _plotFunction: function(fn, cur) {
388
+ var dims = this._getDims();
389
+ var scales = this._getScales();
390
+ var path = this._wrapper.createPath();
391
+ var range = fn._range || [this.xAxis._scale.min, this.xAxis._scale.max];
392
+ var xScale = (range[1] - range[0]) / fn._points;
393
+ var first = true;
394
+ for (var i = 0; i <= fn._points; i++) {
395
+ var x = range[0] + i * xScale;
396
+ if (x > this.xAxis._scale.max + xScale) {
397
+ break;
398
+ }
399
+ if (x < this.xAxis._scale.min - xScale) {
400
+ continue;
401
+ }
402
+ var px = (x - this.xAxis._scale.min) * scales[0] + dims[this.X];
403
+ var py = dims[this.H] - ((fn._fn(x) - this.yAxis._scale.min) * scales[1]) + dims[this.Y];
404
+ path[(first ? 'move' : 'line') + 'To'](px, py);
405
+ first = false;
406
+ }
407
+ var p = this._wrapper.path(this._plot, path, $.extend({class_: 'fn' + cur, fill: 'none', stroke: fn._stroke,
408
+ strokeWidth: fn._strokeWidth}, fn._settings || {}));
409
+ this._showStatus(p, fn._name);
410
+ },
411
+
412
+ /** Draw the plot title - centred
413
+ @private */
414
+ _drawTitle: function() {
415
+ this._wrapper.text(this._plotCont, this._getValue(this._plotCont, 'width') / 2,
416
+ this._title.offset, this._title.value, this._title.settings);
417
+ },
418
+
419
+ /** Draw the chart legend.
420
+ @private */
421
+ _drawLegend: function() {
422
+ if (!this.legend._show) {
423
+ return;
424
+ }
425
+ var g = this._wrapper.group(this._plotCont, {class_: 'legend'});
426
+ var dims = this._getDims(this.legend._area);
427
+ this._wrapper.rect(g, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], this.legend._bgSettings);
428
+ var horiz = dims[this.W] > dims[this.H];
429
+ var numFn = this._functions.length;
430
+ var offset = (horiz ? dims[this.W] : dims[this.H]) / numFn;
431
+ var xBase = dims[this.X] + 5;
432
+ var yBase = dims[this.Y] + ((horiz ? dims[this.H] : offset) + this.legend._sampleSize) / 2;
433
+ for (var i = 0; i < numFn; i++) {
434
+ var fn = this._functions[i];
435
+ this._wrapper.rect(g, xBase + (horiz ? i * offset : 0),
436
+ yBase + (horiz ? 0 : i * offset) - this.legend._sampleSize,
437
+ this.legend._sampleSize, this.legend._sampleSize, {fill: fn._stroke});
438
+ this._wrapper.text(g, xBase + (horiz ? i * offset : 0) + this.legend._sampleSize + 5,
439
+ yBase + (horiz ? 0 : i * offset), fn._name, this.legend._textSettings);
440
+ }
441
+ },
442
+
443
+ /** Show the current value status on hover.
444
+ @private
445
+ @param elem {SVGElement} The current plot element.
446
+ @param label {string} The current status label. */
447
+ _showStatus: function(elem, label) {
448
+ var status = this._onstatus;
449
+ if (this._onstatus) {
450
+ $(elem).hover(function(evt) { status.apply(this, [label]); },
451
+ function() { status.apply(this, ['']); });
452
+ }
453
+ }
454
+ });
455
+
456
+ /** A plot function definition.
457
+ @module SVGPlotFunction */
458
+
459
+ /** Details about each plot function.
460
+ <p>Created through <code>plot.addFunction()</code>.</p>
461
+ @param plot {SVGPlot} The owning plot.
462
+ @param [name] {string} The name of this function.
463
+ @param fn {function} The function to be plotted.
464
+ @param [range] {number[]} The range of values to be plotted.
465
+ @param [points] {number} The number of points to plot within this range.
466
+ @param [stroke] {string} The colour of the (out)line for the plot.
467
+ @param [strokeWidth] {number} The width of the (out)line for the plot.
468
+ @param [settings] {object} Additional settings for the plotted values.
469
+ @return {SVGPlotFunction} The new plot function object. */
470
+ function SVGPlotFunction(plot, name, fn, range, points, stroke, strokeWidth, settings) {
471
+ if (typeof name !== 'string') {
472
+ settings = strokeWidth;
473
+ strokeWidth = stroke;
474
+ stroke = points;
475
+ points = range;
476
+ range = fn;
477
+ fn = name;
478
+ name = null;
479
+ }
480
+ if (!$.isArray(range)) {
481
+ settings = strokeWidth;
482
+ strokeWidth = stroke;
483
+ stroke = points;
484
+ points = range;
485
+ range = null;
486
+ }
487
+ if (typeof points !== 'number') {
488
+ settings = strokeWidth;
489
+ strokeWidth = stroke;
490
+ stroke = points;
491
+ points = null;
492
+ }
493
+ if (typeof stroke !== 'string') {
494
+ settings = strokeWidth;
495
+ strokeWidth = stroke;
496
+ stroke = null;
497
+ }
498
+ if (typeof strokeWidth !== 'number') {
499
+ settings = strokeWidth;
500
+ strokeWidth = null;
501
+ }
502
+ this._plot = plot; // The owning plot
503
+ this._name = name || ''; // Display name
504
+ this._fn = fn || identity; // The actual function: y = fn(x)
505
+ this._range = range; // The range of values plotted
506
+ this._points = points || 100; // The number of points plotted
507
+ this._stroke = stroke || 'black'; // The line colour
508
+ this._strokeWidth = strokeWidth || 1; // The line width
509
+ this._settings = settings || {}; // Any other settings
510
+ }
511
+
512
+ $.extend(SVGPlotFunction.prototype, {
513
+
514
+ /** Set or retrieve the name for this function.
515
+ @param name {string} The function's name.
516
+ @return {SVGPlotFunction|string} This plot function object or the function name (if no parameters). */
517
+ name: function(name) {
518
+ if (arguments.length === 0) {
519
+ return this._name;
520
+ }
521
+ this._name = name;
522
+ this._plot._drawPlot();
523
+ return this;
524
+ },
525
+
526
+ /** Set or retrieve the function to be plotted.
527
+ @param [name] {string} The function's name.
528
+ @param fn {function} The function to be ploted.
529
+ @return {SVGPlotFunction|function} This plot function object or the actual function (if no parameters). */
530
+ fn: function(name, fn) {
531
+ if (arguments.length === 0) {
532
+ return this._fn;
533
+ }
534
+ if (typeof name === 'function') {
535
+ fn = name;
536
+ name = null;
537
+ }
538
+ this._name = name || this._name;
539
+ this._fn = fn;
540
+ this._plot._drawPlot();
541
+ return this;
542
+ },
543
+
544
+ /** Set or retrieve the range of values to be plotted.
545
+ @param min {number} The minimum value to be plotted.
546
+ @param max {number} The maximum value to be plotted.
547
+ @return {SVGPlotFunction|number[]} This plot function object or the value range (if no parameters). */
548
+ range: function(min, max) {
549
+ if (arguments.length === 0) {
550
+ return this._range;
551
+ }
552
+ this._range = (min == null ? null : [min, max]);
553
+ this._plot._drawPlot();
554
+ return this;
555
+ },
556
+
557
+ /** Set or retrieve the number of points to be plotted.
558
+ @param value {number} The number of points to plot.
559
+ @return {SVGPlotFunction|number} This plot function object or the number of points (if no parameters). */
560
+ points: function(value) {
561
+ if (arguments.length === 0) {
562
+ return this._points;
563
+ }
564
+ this._points = value;
565
+ this._plot._drawPlot();
566
+ return this;
567
+ },
568
+
569
+ /** Set or retrieve the formatting for this function.
570
+ @param stroke {string} The (out)line colour.
571
+ @param [strokeWidth] {number} The line's width.
572
+ @param [settings] {object} Additional formatting settings for the function.
573
+ @return {SVGPlotFunction|object} This plot function object or formatting settings (if no parameters). */
574
+ format: function(stroke, strokeWidth, settings) {
575
+ if (arguments.length === 0) {
576
+ return $.extend({stroke: this._stroke, strokeWidth: this._strokeWidth}, this._settings);
577
+ }
578
+ if (typeof strokeWidth !== 'number') {
579
+ settings = strokeWidth;
580
+ strokeWidth = null;
581
+ }
582
+ this._stroke = stroke || this._stroke;
583
+ this._strokeWidth = strokeWidth || this._strokeWidth;
584
+ $.extend(this._settings, settings || {});
585
+ this._plot._drawPlot();
586
+ return this;
587
+ },
588
+
589
+ /** Return to the parent plot.
590
+ @return {SVGPlot} The parent plot. */
591
+ end: function() {
592
+ return this._plot;
593
+ }
594
+ });
595
+
596
+ /* Default function to plot.
597
+ @param x (number) the input value
598
+ @return (number) the same value */
599
+ function identity(x) {
600
+ return x;
601
+ }
602
+
603
+ /** A plot axis definition.
604
+ @module SVGPlotAxis */
605
+
606
+ /** Details about each plot axis.
607
+ <p>Accessed through <code>plot.xAxis</code> or <code>plot.yAxis</code>.</p>
608
+ @param plot {SVGPlot} The owning plot.
609
+ @param title {string} The title of the axis.
610
+ @param min {number} The minimum value displayed on this axis.
611
+ @param max {number} The maximum value displayed on this axis.
612
+ @param major {number} The distance between major ticks.
613
+ @param [minor] {number} The distance between minor ticks.
614
+ @return {SVGPlotAxis} The new plot axis object. */
615
+ function SVGPlotAxis(plot, title, min, max, major, minor) {
616
+ this._plot = plot; // The owning plot
617
+ this._title = title || ''; // The plot's title
618
+ this._titleFormat = {}; // Formatting settings for the title
619
+ this._titleOffset = 0; // The offset for positioning the title
620
+ this._labelFormat = {}; // Formatting settings for the labels
621
+ this._lineFormat = {stroke: 'black', strokeWidth: 1}; // Formatting settings for the axis lines
622
+ this._ticks = {major: major || 10, minor: minor || 0, size: 10, position: 'both'}; // Tick mark options
623
+ this._scale = {min: min || 0, max: max || 100}; // Axis scale settings
624
+ this._crossAt = 0; // Where this axis crosses the other one. */
625
+ }
626
+
627
+ $.extend(SVGPlotAxis.prototype, {
628
+
629
+ /** Set or retrieve the scale for this axis.
630
+ @param min {number} The minimum value shown.
631
+ @param max {number} The maximum value shown.
632
+ @return {SVGPlotAxis|object} This axis object or min and max values (if no parameters). */
633
+ scale: function(min, max) {
634
+ if (arguments.length === 0) {
635
+ return this._scale;
636
+ }
637
+ this._scale.min = min;
638
+ this._scale.max = max;
639
+ this._plot._drawPlot();
640
+ return this;
641
+ },
642
+
643
+ /** Set or retrieve the ticks for this axis.
644
+ @param major {number} The distance between major ticks.
645
+ @param minor {number} The distance between minor ticks.
646
+ @param [size] {number} The length of the major ticks (minor are half).
647
+ @param [position] {string} The location of the ticks: 'nw', 'se', 'both'.
648
+ @return {SVGPlotAxis|object} This axis object or major, minor, size, and position values (if no parameters). */
649
+ ticks: function(major, minor, size, position) {
650
+ if (arguments.length === 0) {
651
+ return this._ticks;
652
+ }
653
+ if (typeof size === 'string') {
654
+ position = size;
655
+ size = null;
656
+ }
657
+ this._ticks.major = major;
658
+ this._ticks.minor = minor;
659
+ this._ticks.size = size || this._ticks.size;
660
+ this._ticks.position = position || this._ticks.position;
661
+ this._plot._drawPlot();
662
+ return this;
663
+ },
664
+
665
+ /** Set or retrieve the title for this axis.
666
+ @param title {string} The title text
667
+ @param [offset] {number} The distance to offset the title position.
668
+ @param [colour] {string} How to colour the title.
669
+ @param [format] {object} Formatting settings for the title.
670
+ @return {SVGPlotAxis|object} This axis object or title, offset, and format values (if no parameters). */
671
+ title: function(title, offset, colour, format) {
672
+ if (arguments.length === 0) {
673
+ return {title: this._title, offset: this._titleOffset, format: this._titleFormat};
674
+ }
675
+ if (typeof offset !== 'number') {
676
+ format = colour;
677
+ colour = offset;
678
+ offset = null;
679
+ }
680
+ if (typeof colour !== 'string') {
681
+ format = colour;
682
+ colour = null;
683
+ }
684
+ this._title = title;
685
+ this._titleOffset = (offset != null ? offset : this._titleOffset);
686
+ if (colour || format) {
687
+ this._titleFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
688
+ }
689
+ this._plot._drawPlot();
690
+ return this;
691
+ },
692
+
693
+ /** Set or retrieve the label format for this axis.
694
+ @param [colour] {string} How to colour the labels.
695
+ @param [format] {object} Formatting settings for the labels.
696
+ @return {SVGPlotAxis|object} This axis object or format values (if no parameters). */
697
+ format: function(colour, format) {
698
+ if (arguments.length === 0) {
699
+ return this._labelFormat;
700
+ }
701
+ if (typeof colour !== 'string') {
702
+ format = colour;
703
+ colour = null;
704
+ }
705
+ this._labelFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
706
+ this._plot._drawPlot();
707
+ return this;
708
+ },
709
+
710
+ /** Set or retrieve the line formatting for this axis.
711
+ @param colour {string} The line's colour.
712
+ @param [width] {number} The line's width.
713
+ @param [settings] {object} Additional formatting settings for the line.
714
+ @return {SVGPlotAxis|object} This axis object or line formatting values (if no parameters). */
715
+ line: function(colour, width, settings) {
716
+ if (arguments.length === 0) {
717
+ return this._lineFormat;
718
+ }
719
+ if (typeof width !== 'number') {
720
+ settings = width;
721
+ width = null;
722
+ }
723
+ $.extend(this._lineFormat, {stroke: colour, strokeWidth: width || this._lineFormat.strokeWidth}, settings || {});
724
+ this._plot._drawPlot();
725
+ return this;
726
+ },
727
+
728
+ /** Return to the parent plot.
729
+ @return {SVGPlot} The parent plot. */
730
+ end: function() {
731
+ return this._plot;
732
+ }
733
+ });
734
+
735
+ /** A plot legend definition.
736
+ @module SVGPlotLegend */
737
+
738
+ /** Details about each plot legend.
739
+ <p>Accessed through <code>plot.legend</code>.</p>
740
+ @param plot {SVGPlot} The owning plot.
741
+ @param [bgSettings] {object} Additional formatting settings for the legend background.
742
+ @param [textSettings] {object} Additional formatting settings for the legend text.
743
+ @return {SVGPlotLegend} The new plot legend object. */
744
+ function SVGPlotLegend(plot, bgSettings, textSettings) {
745
+ this._plot = plot; // The owning plot
746
+ this._show = true; // Show the legend?
747
+ this._area = [0.9, 0.1, 1.0, 0.9]; // The legend area: left, top, right, bottom,
748
+ // > 1 in pixels, <= 1 as proportion
749
+ this._sampleSize = 15; // Size of sample box
750
+ this._bgSettings = bgSettings || {stroke: 'gray'}; // Additional formatting settings for the legend background
751
+ this._textSettings = textSettings || {}; // Additional formatting settings for the text
752
+ }
753
+
754
+ $.extend(SVGPlotLegend.prototype, {
755
+
756
+ /** Set or retrieve whether the legend should be shown.
757
+ @param show {boolean} <code>true</code> to display it, <code>false</code> to hide it.
758
+ @return {SVGPlotLegend|boolean} This legend object or show the legend? (if no parameters) */
759
+ show: function(show) {
760
+ if (arguments.length === 0) {
761
+ return this._show;
762
+ }
763
+ this._show = show;
764
+ this._plot._drawPlot();
765
+ return this;
766
+ },
767
+
768
+ /** Set or retrieve the legend area.
769
+ @param left {number|number[]} > 1 is pixels, <= 1 is proportion of width or array for left, top, right, bottom.
770
+ @param [top] {number} > 1 is pixels, <= 1 is proportion of height.
771
+ @param [right] {number} > 1 is pixels, <= 1 is proportion of width.
772
+ @param [bottom] {number} > 1 is pixels, <= 1 is proportion of height.
773
+ @return {SVGPlotLegend|number[]} This legend object or the legend area:
774
+ left, top, right, bottom (if no parameters). */
775
+ area: function(left, top, right, bottom) {
776
+ if (arguments.length === 0) {
777
+ return this._area;
778
+ }
779
+ this._area = ($.isArray(left) ? left : [left, top, right, bottom]);
780
+ this._plot._drawPlot();
781
+ return this;
782
+ },
783
+
784
+ /** Set or retrieve additional settings for the legend area.
785
+ @param [sampleSize] {number} The size of the sample box to display.
786
+ @param bgSettings {object} Additional formatting settings for the legend background.
787
+ @param [textSettings] {object} Additional formatting settings for the legend text.
788
+ @return {SVGPlotLegend|object} This legend object or
789
+ bgSettings and textSettings for the legend (if no parameters). */
790
+ settings: function(sampleSize, bgSettings, textSettings) {
791
+ if (arguments.length === 0) {
792
+ return {sampleSize: this._sampleSize, bgSettings: this._bgSettings, textSettings: this._textSettings};
793
+ }
794
+ if (typeof sampleSize === 'object') {
795
+ textSettings = bgSettings;
796
+ bgSettings = sampleSize;
797
+ sampleSize = null;
798
+ }
799
+ this._sampleSize = sampleSize || this._sampleSize;
800
+ this._bgSettings = bgSettings;
801
+ this._textSettings = textSettings || this._textSettings;
802
+ this._plot._drawPlot();
803
+ return this;
804
+ },
805
+
806
+ /** Return to the parent plot.
807
+ @return {SVGPlot} The parent plot. */
808
+ end: function() {
809
+ return this._plot;
810
+ }
811
+ });
812
+
813
+ })(jQuery)