Commits

Chris Leonello committed 3bf0782

Added a couple of hooks for plugins.
Created a basic trend line plugin as proof of concept.

  • Participants
  • Parent commits c6b41c3

Comments (0)

Files changed (3)

     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
     <script language="javascript" type="text/javascript" src="../jquery-1.3.2.js"></script>
     <script language="javascript" type="text/javascript" src="jquery.jqplot.js"></script>
+<!--     <script language="javascript" type="text/javascript" src="jqplot.trendLines.js"></script> -->
     <script language="javascript">
 		$(document).ready(function(){
 			// chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,5], [6,12]], [[32,18],[46,32],[79,47]]], {series:[{}, {xaxis:'x2axis', yaxis:'y2axis'}], axes:{y2axis:{scale:1.5}}});
 			// chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,5], [6,12]], [[32,18],[46,14],[79,55]]]);
 			// chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,1], null, [6,12]], [[32,18],[46,14],[79,55]]], {series:[{}, {xaxis:'x2axis', yaxis:'y2axis'}], axesDefaults:{ticks:{fontSize:'0.85em'}}, axes:{yaxis:{min:0, max:15}}, seriesDefaults:{lineWidth:2.5, shadowOffset:1, shadowDepth:3, shadowAlpha:0.07}});
-        chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,1], null, [6,12]]]);
+        chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,1], null, [6,12]]], {series:[{trendLines:{show:true}}]});
 		});
 		</script>
 		<style type="text/css">

jqplot.trendLines.js

+(function($) {
+    var debug = 1;
+	// Convienence function that won't hang IE.
+	function log(something) {
+	    if (window.console && debug) {
+	        console.log(something);
+	    }
+	};
+    
+    $.jqplot.postParseOptionsHooks.push(parseTrendLineOptions);
+    $.jqplot.postDrawSeriesHooks.push(drawTrendLines);
+
+    // called within scope of ._jqPlot
+    function parseTrendLineOptions () {
+        var s, i;
+        var series = this.series;
+        var options = this.options;
+        var defaults = {
+            show: false,
+            color: '#666666',
+            lineWidth:2.5,
+            shadows:true,
+            // shadow angle in degrees
+            shadowAngle:45,
+            // shadow offset in pixels
+            shadowOffset:1,
+            shadowDepth:3,
+            shadowAlpha:'0.07'
+        };
+        for (i = 0; i < series.length; ++i) {
+            s = series[i];
+        	// merge user supplied trendline options with defaults.
+        	if (!s.hasOwnProperty('trendLines')) s.trendLines = {};
+        	s.trendLines = $.extend(true, {}, defaults, options.trendLines, s.trendLines);
+    	}
+    };
+    
+    // called within scope of series object
+    function drawTrendLines(grid, ctx) {
+        var fit;
+        if (this.trendLines.show) {
+            fit = fitData(this.data, 'linear');
+            // make a trendline series
+            var tl = $.extend(true, {}, this, this.trendLines);
+            tl.data = fit.data;
+            tl.gridData = [];
+            var lineRenderer = new $.jqplot.lineRenderer();
+            lineRenderer.draw.call(tl, grid, ctx); 
+        }  
+    }
+	
+	function regression(x, y, type)  {
+		var type = (type == null) ? 'linear' : type;
+	    var N = x.length;
+	    var slope;
+	    var intercept;	
+		var SX = 0;
+		var SY = 0;
+		var SXX = 0;
+		var SXY = 0;
+		var SYY = 0;
+		var Y = [];
+		var X = [];
+	
+		if (type == 'linear') {
+			X = x;
+			Y = y;
+		}
+		else if (type == 'exp' || type == 'exponential') {
+			for ( var i=0; i<y.length; i++) {
+				// ignore points <= 0, log undefined.
+				if (y[i] <= 0) {
+					N--;
+				}
+				else {
+					X.push(x[i]);
+			    	Y.push(Math.log(y[i]));
+				}
+			}
+		}
+
+		for ( var i = 0; i < N; i++) {
+		    SX = SX + X[i];
+		    SY = SY + Y[i];
+		    SXY = SXY + X[i]* Y[i];
+		    SXX = SXX + X[i]* X[i];
+		    SYY = SYY + Y[i]* Y[i];
+		}
+
+		slope = (N*SXY - SX*SY)/(N*SXX - SX*SX);
+		intercept = (SY - slope*SX)/N;
+
+	    return [slope, intercept];
+	}
+
+	function linearRegression(X,Y) {
+		var ret;
+		ret = regression(X,Y,'linear');
+		return [ret[0],ret[1]];
+	}
+
+	function expRegression(X,Y) {
+		var ret;
+		var x = X;
+		var y = Y;
+		ret = regression(x, y,'exp');
+		var base = Math.exp(ret[0]);
+		var coeff = Math.exp(ret[1]);
+		return [base, coeff];
+	}
+
+	function fitData(data, type) {
+		var type = (type == null) ?  'linear' : type;
+		var ret;
+		var res;
+		var x = [];
+		var y = [];
+		var ypred = [];
+		
+		for (i=0; i<data.length; i++){
+			if (data[i] != null && data[i][0] != null && data[i][1] != null) {
+				x.push(data[i][0]);
+				y.push(data[i][1]);
+			}
+		}
+		
+		if (type == 'linear') {
+			ret = linearRegression(x,y);
+			for ( var i=0; i<x.length; i++){
+			    res = ret[0]*x[i] + ret[1];
+			    ypred.push([x[i], res]);
+			}
+		}
+		else if (type == 'exp' || type == 'exponential') {
+			ret = expRegression(x,y);
+			for ( var i=0; i<x.length; i++){
+			    res = ret[1]*Math.pow(ret[0],x[i]);
+			    ypred.push([x[i], res]);
+			}
+		}
+		return {data: ypred, slope: ret[0], intercept: ret[1]};
+	};    
+
+})(jQuery);
         this._xaxis = new Axis(this.xaxis);
         this.yaxis = 'yaxis';
         this._yaxis = new Axis(this.yaxis);
-        this.renderer = new lineRenderer();
+        this.renderer = new $.jqplot.lineRenderer();
         // raw user data points.  These should never be altered!!!
         this.data = [];
         // data in grid coordinates.  User data transformed for plotting on grid.
         this.equalYTicks = true;
         // borrowed colors from Flot.
         this.seriesColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"];
-        // hooks for plugins
-        this.preDrawHooks = [];
-        this.postDrawHooks = [];
+        // container to hold all of the merged options.  Convienence for plugins.
+        this.options = {};
             
         this.init = function(target, data, options) {
             this.targetId = target;
         // parse the user supplied options and override defaults, then
         // populate the instance properties
         this.parseOptions = function(options){
-            var opts = $.extend(true, {}, this.defaults, options);
+            this.options = $.extend(true, {}, this.defaults, options);
             for (var n in this.axes) {
                 var axis = this.axes[n];
-                $.extend(true, axis, opts.axesDefaults, opts.axes[n]);
+                $.extend(true, axis, this.options.axesDefaults, this.options.axes[n]);
                 switch (n) {
                     case 'xaxis':
                         axis.style = {position:'absolute', left:'0px', bottom:'0px'};
             }
             
             for (var i=0; i<this.data.length; i++) {
-                var temp = $.extend(true, new Series(), opts.seriesDefaults, opts.series[i]);
+                var temp = $.extend(true, new Series(), this.options.seriesDefaults, this.options.series[i]);
                 temp.data = this.data[i];
                 switch (temp.xaxis) {
                     case 'xaxis':
             
             // copy the grid and title options into this object.
             for (var opt in ['grid', 'title']) {
-                this[opt] = $.extend(true, {}, opts[opt]);
+                this[opt] = $.extend(true, {}, this.options[opt]);
+            }
+            for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
+                $.jqplot.postParseOptionsHooks[i].call(this);
             }
         };
     
     
         // create the plot and add it do the dom
         this.draw = function(){
-            for (var i=0; i<this.preDrawHooks.length; i++) {
-                this.preDrawHooks[i].call(this);
-            }
             this.drawTitle();
             this.drawAxes();
             this.pack();
             this.makeCanvas();
             this.drawGrid();
             this.drawSeries();
-            for (var i=0; i<this.postDrawHooks.length; i++) {
-                this.postDrawHooks[i].call(this);
+            for (var i=0; i<$.jqplot.postDrawHooks.length; i++) {
+                $.jqplot.postDrawHooks[i].call(this);
             }
         };
     
         this.drawSeries = function(){
             for (var i=0; i<this.series.length; i++) {
                 this.series[i].renderer.draw.call(this.series[i], this.grid, this.sctx);
+                for (var j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
+                    $.jqplot.postDrawSeriesHooks[j].call(this.series[i], this.grid, this.sctx);
+                }
             }
         };
     
         plot.draw();
         return plot;
     };
+    
+    $.jqplot.postParseOptionsHooks = [];
+    $.jqplot.postDrawHooks = [];
+    $.jqplot.postDrawSeriesHooks = [];
            
     function linearAxisRenderer() {
     };
         
     };
     
-    function lineRenderer(){
+    $.jqplot.lineRenderer = function(){
     };
 
-    lineRenderer.prototype.draw = function(grid, ctx) {
+    // called within scope of an individual series.
+    $.jqplot.lineRenderer.prototype.draw = function(grid, ctx) {
         var i;
         ctx.save();
         ctx.beginPath();
         ctx.lineWidth = this.lineWidth;
         ctx.strokeStyle = this.color;
         var pointx, pointy;
+        // recalculate the grid data
+        this.gridData = [];
         this.gridData.push([xp.call(this._xaxis, this.data[0][0]), yp.call(this._yaxis, this.data[0][1])]);
         ctx.moveTo(this.gridData[0][0], this.gridData[0][1]);
         for (var i=1; i<this.data.length; i++) {
         ctx.restore();
     };
     
-    lineRenderer.prototype.processData = function() { 
+    $.jqplot.lineRenderer.prototype.processData = function() { 
         // don't have axes conversion functions yet, all we can do is look for bad
         // points and set the axes bounds.  
         var d = this.data;