//  file: loess.js
//
//  Purpose: Calculate LOESS smoothed line values 
//           a la Cleveland JASA 1979 for interactive 
//           charting applications.  Uses R's lowess command's 
//           parameterization.  
//          
//  Author: Jeff Lewis (jblewis@ucla.edu) 
//         
//
//  Example:
//     var x = [1,3,4,5,10,14]; // Must be numeric
//     var y = [3,5,4,3,23,24]; // Must be numeric
//     var loess = new pollster_Loess(x,y,alpha=0.5,iter=3,delta=0.01)
//     yhat = loess.fit()
//  
//  where (x[i],y[i]) for i in 0..(x.length-1) are points that define the 
//  loess smoothed line.  alpha is a smoothing paramter in (0,infinity) where
//  larger values indicate more smoothing (2/3 is R's default value). iter is the 
//  number of "robustness iterations" (Three is the default value for R's lowess 
//  command). See http://www.r-project.org for information on R and for 
//  R documentation for a complete description of the paramterization. 
//
//  Notes:
//     * Computation time is roughly proportional to iter.  Larger values of
//       iter when combined with hundreds of data points can cause firefox to
//       throw user warnings.
//    
//     * Randomly breaks ties rather than including ties on the boundaries.
//
//     * Delta is a fraction of the range of the x variable (in R's lowess
//       it is a distance in the units of x).
//
//     * pollster_Loess sort the x values and all returned values are relative
//       to the _sorted_ x values (which may not be the natural order in which 
//       the data were passed in).  The sorted x values can be accessed as loess.x
//       (assuming your pollster_Loess instance is called 'loess').
//
//  To do:
//
//     * Add method to ensure that local linear regressions will not fail due  
//       to a lack of variation in the data (all points with positive weight
//       located at the same x position.
//    
//  Version:
//     0.01 -- Initial version (16 July 2008 (c))
//
//     0.02 -- Added delta parameter (11 September 2008)
//

//  ---------------------------------------------------------------
//  pollster_sortpair:  Function to take pair of vector of the same 
//                      length and return them in the order of the
//                      values of the first vector
//  ---------------------------------------------------------------
function pollster_sortpair(a,b) {
    this.pairsort = function(a,b) {
	return a[0] - b[0];
    }
    var pairs = new Array();
    var res  = new Array();
    for (var i=0;i<a.length;i++) {
	pairs[i] = [a[i],b[i]];
    }
    pairs.sort(this.pairsort);
    for (var i=0;i<a.length;i++) {
        a[i] = pairs[i][0];
        b[i] = pairs[i][1];
    }    
    return [a,b];
}
  
//  ---------------------------------------------------------------
//  pollster_median: A simple function to get the median value of 
//                   a vector
//  ---------------------------------------------------------------
function pollster_median(dat) {
    this.sortnum = function(a,b) {return a - b}
    var sdat = dat.slice(); //force a deep copy!!
    sdat.sort(sortnum);
    var middle = Math.floor(sdat.length/2);
    if ((sdat.length % 2) != 0) {
        var median = sdat[middle];
    }
    else {
        var median = (sdat[middle - 1] + sdat[middle]) / 2.0;
    }
    return median;
}


//  ---------------------------------------------------------------
//  pollster_WLS: Class preforms weighted least squares bivariate 
//                regression.
//  ---------------------------------------------------------------
function pollster_WLS(x,y,w) {
    this.predict = function(xpred) {    
	var yhat = new Array();
	for (var i=0;i<xpred.length;i++) {
	    yhat[i] = this.par[0] + this.par[1]*xpred[i];
	}
        return yhat;
    }
    var sumx = 0.0;
    var sumy = 0.0; 
    var sumxy = 0.0; 
    var sumxx = 0.0;
    var denom = 0.0;
    for (i=0;i<x.length;i++) {
	sumx += x[i]*w[i];
        sumy += y[i]*w[i];
	sumxx += x[i]*x[i]*w[i];
	sumxy += x[i]*y[i]*w[i];
	denom += w[i];
    }
    b = (sumxy-sumx*sumy/denom)/(sumxx-sumx*sumx/denom);
    a = sumy/denom - b*sumx/denom;
    this.par = [a,b];
}


// ---------------------------------------------------------------
// pollster_Loess: Class performs simple Loess (locally linear bivariate)
//                 smoothing.
// ---------------------------------------------------------------
function pollster_Loess(x,y,alpha,iters,delta) {
    this.alpha=alpha;
    var xy = pollster_sortpair(x,y);
    this.x=xy[0];
    this.y=xy[1];
    this.iters=iters;
    this.xlen = x.length;
    this.delta = delta*(this.x[this.xlen-1]-this.x[0]);

    //
    // mkweights: Function to make local weights for a given point
    //
    this.mkweights = function() {
	//Sort array of ordered pair by second elements
        this.pairsort = function(a,b) {
	    return a[1] - b[1];
	}
	var weights = new Array();
        var lastx = this.x[0]-2*this.delta;
	for (var j=0;j<this.xlen;j++) {

	    // Only need weights at points where local regressions will be run
	    if (((this.x[j]-lastx)<this.delta) && (j!=(this.xlen-1))) continue;

	    var dist = new Array();
	    var weight = new Array();
            var cp = this.x[j];
	    for (i=0;i<this.xlen;i++) weight[i]=0.0;
	    for (var i=0; i<this.xlen; i++) {
		dist[i] = [i,Math.abs(this.x[i]-cp)];
	    }
	    var distances = dist.sort(this.pairsort);
	    
	    //Similar to R's loess parameterization when a>1 
	    if (alpha>1.0) {
		var numtouse = this.xlen;
		var mxdist = distances[numtouse-1][1]*alpha;
	    }
	    else {
		var numtouse = Math.floor(this.xlen*alpha);
		if (numtouse<3) numtouse=3;
		var mxdist = distances[numtouse-1][1];
	    }	    
	    for (var i=0; i<this.xlen && mxdist >= distances[i][1]; i++) { 	 
		var pos = distances[i][0];
		var dist = distances[i][1];
		weight[pos] = Math.pow(1.0-Math.pow(dist/mxdist,3.0),3.0);
	    }
	    weights[j] = weight;
            lastx = this.x[j];
	}
	return weights;
    }

    //
    // biweights: Function to calculate biweights for "robustness" iterations 
    //
    this.biweights = function(y,ystar) {
        var e = new Array();
        var weights = new Array();       
        var ylen = y.length;
	for (i=0;i<ylen;i++) {
	    e[i] = Math.abs(y[i]-ystar[i]);
	}
	var sixs = 6.0*pollster_median(e);      
	for (i=0;i<ylen;i++) {
	    if (e[i]<sixs) {
		weights[i] = Math.pow(1.0-Math.pow(e[i]/sixs,2.0),2.0);
	    }
	    else {
		weights[i] = 0.0;
	    }
	}
	return weights
    }

    //
    //  fit: Function to get loess smoothed points
    // 
    this.fit = function() {
	var res = new Array();	
        var basicweights = this.mkweights() 

        // Initiatize pointweights (for robustness iters, if any)
        var pointweights = new Array();
        for (var i=0;i<this.xlen;i++) pointweights[i] = 1.0;
        for (var j=-1;j<this.iters;j++) {
	    var lastx = this.x[0]-2*this.delta;
	    var lastres = -99.0;
	    for (var i=0;i<this.xlen;i++) {	
		var xp = this.x[i];
                if (((xp-lastx) > this.delta) || (i==(this.xlen-1))) {
		    var weight = new Array();
		    for (k=0;k<this.xlen;k++) {
			weight[k] = basicweights[i][k]*pointweights[k];
		    }
		    var wls = new pollster_WLS(this.x,this.y,weight);
		    res[i] = wls.predict([xp])[0];
		    for (var k=(i-1);k>0;k--) {
			if (res[k] != null) break;  		     
			res[k] = lastres +((this.x[k]-lastx)/(xp-lastx+1e-16))*(res[i]-lastres);
		    }
                    lastres = res[i];
		    lastx = xp;
		}
		else {
		    res[i] = null;
		}
	    }
	    pointweights = this.biweights(this.y,res);
	}
	return res;
    }    
}

function createXMLHttpRequest() {
	try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
	try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
	try { return new XMLHttpRequest(); } catch (e) {}
	alert("XMLHttpRequest not supported");
	return null;
}

function what(nameofpage, nameofdiv) {

var xhreq =  createXMLHttpRequest();

xhreq.onreadystatechange = function() {
 if (xhreq.readyState==4) {
  if (xhreq.status==200) {
   document.getElementById(nameofdiv).innerHTML = xhreq.responseText;
               //document.getElementById("footer").style.display = "none";
  }
  else {
	huffwhat(nameofpage, nameofdiv);
   //alert("AJAX call returned an error");
               //alert(xhreq.status);
  }
 }
}

// xhreq.open("GET", "http://www.pollster.com/blogs/remote.php?nameofpage=" + nameofpage, true);
xhreq.open("GET", nameofpage, true);
xhreq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhreq.send(null);
}

function huffwhat (nameofpage, nameofdiv) {
	nameofpage = nameofpage.replace("http://pollster.com", "http://huffingtonpost.com/pollster/pollster");
//alert(nameofpage);
var xhreq =  createXMLHttpRequest();

xhreq.onreadystatechange = function() {
 if (xhreq.readyState==4) {
  if (xhreq.status==200) {
   document.getElementById(nameofdiv).innerHTML = xhreq.responseText;
               //document.getElementById("footer").style.display = "none";
  }
  else {
   alert("AJAX call returned an error");
   //            alert(xhreq.status);
  }
 }
}
xhreq.open("GET", nameofpage, true);
xhreq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhreq.send(null);
}

function what2(nameofpage2, nameofdiv2) {

var xhreq =  createXMLHttpRequest();

xhreq.onreadystatechange = function() {
 if (xhreq.readyState==4) {
  if (xhreq.status==200) {
   document.getElementById(nameofdiv2).innerHTML = xhreq.responseText;
               //document.getElementById("footer").style.display = "none";
  }
  else {
   alert("AJAX call returned an error");
               //alert(xhreq.status);
  }
 }
}

// xhreq.open("GET", "http://www.pollster.com/blogs/remote.php?nameofpage=" + nameofpage, true);
xhreq.open("GET", nameofpage2, true);
xhreq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhreq.send(null);
}


 
function insertChart(chart,w,h)
{
	document.write('<object id="voxpop" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'
	    + 'codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="' + w + '" height="' + h + '">'
	    + '<param name="movie" value="' + chart + '" />'
		+ '<param name="quality" value="high" />'
		+ '<param name="allowScriptAccess" value="sameDomain" />'		
	    + '<embed  class="flashClass" src="' + chart + '" quality="high" '
	    + 'quality="high" width="' + w + '" height="' + h + '"'		
	    + 'allowScriptAccess="sameDomain"'
	    + 'type="application/x-shockwave-flash"'
	    + 'pluginspage="http://www.macromedia.com/go/getflashplayer">'
	    + '<\/embed>'
	    + '<\/object>');							
}

