1 
  2 /**
  3 	A Class which serves to represent mathematical ranges
  4 	@class
  5 	@constructor
  6 	@param {String} rangestring Anything of the standard mathematical forms
  7 	[x,y],(x,y],[x,y),(x,y) where x,y in reals and square brackets are inclusive
  8 	and parens are exclusive 
  9 **/
 10 function Range(rangestring) {
 11 	/**
 12 		Private helper function to parse the range string
 13 		@private
 14 		@param {Range} The object being initialized
 15 		@param {String} The range as a string to be parsed on initalized for
 16 	**/
 17 	function evaluateRange(obj,rs)
 18 	{
 19 		var rangeRe = /([[]|[(])([-]*\d*(\.\d*)*),([-]*\d*(\.\d*)*)([\]]|[)])/; 
 20 		var result = rs.match(rangeRe);
 21 		if(result === null)
 22 		{
 23 			throw new Error("Range given not parseable.");
 24 		}else{
 25 			if(result[1] == '(')
 26 			{
 27 				obj.inclusivelower = false;
 28 			}
 29 
 30 			if(result[6] == ')')
 31 			{
 32 				obj.inclusiveupper = false;
 33 			}
 34 			obj.lowerbound = parseFloat(result[2]);
 35 			obj.upperbound = parseFloat(result[4]);
 36 		}
 37 
 38 	}
 39 
 40 	this.def = rangestring;
 41 	this.lowerbound = 0;
 42 	this.upperbound = 0;
 43 	this.inclusivelower = true;
 44 	this.inclusiveupper = true;
 45 	try{
 46 	evaluateRange(this,rangestring);
 47 	}catch(e) {
 48 		throw e;
 49 	}
 50 
 51 }
 52 /**
 53 	A static constant to help with de/serialization
 54 	@constant
 55 	@static
 56 **/
 57 Range.serializeName = "Range";	
 58 
 59 /**
 60 	A method to help with serializing and passing to web worker
 61 **/
 62 Range.prototype.toWebWorker = function() {
 63 	this.serializeName = Range.serializeName;
 64 };
 65 
 66 /**
 67 	Reattach methods after being passed to web worker
 68 	@param {Object} that A Range stripped of methods
 69 **/
 70 Range.fromWebWorker = function(that) {
 71 	reattachMethods(that,Range);
 72 };
 73 
 74 /**
 75 	Tests if a value is within a range
 76 	@param {Double} num The value to be tested
 77 	@return {Boolean} True or false depending on the range defined
 78 **/
 79 Range.prototype.inRange = function(num) {
 80 	if(this.inclusivelower)
 81 	{
 82 		if(this.inclusiveupper) 
 83 		{
 84 			return this.lowerbound <= num && this.upperbound >= num;
 85 		}else{
 86 			return this.lowerbound <= num && this.upperbound > num;
 87 		}
 88 	}else{
 89 		if(this.inclusiveupper) 
 90 		{
 91 			return this.lowerbound < num && this.upperbound >= num;
 92 		}else{
 93 			return this.lowerbound < num && this.upperbound > num;
 94 		}
 95 
 96 	}
 97 			
 98 }; 
 99 
100 /**
101 	Creates a string representation
102 	@return {string} 
103 **/
104 Range.prototype.toString = function() {
105 	return this.def;
106 };
107 
108 /**
109 	Object representing mathematical piecewise functions
110 	@class
111 	@constructor
112 	@param {Resolveable[]} functs An array of objects implementing the resolve method
113 	@param {Range[]} ranges An array of Range objects for each of the functs provided
114 **/
115 function PiecewiseFunction(functs, ranges) {
116 	if(!(functs instanceof Array) || !(ranges instanceof Array))
117 	{
118 		throw new Error("Parameters expect Arrays.");
119 	}
120 	if(functs.length != ranges.length)
121 	{
122 		throw new Error("Number of functions and ranges must match.");
123 	}
124 	this.functs = functs;
125 	this.ranges = ranges;
126 
127 	
128 }
129 
130 /**
131 	Applies the function on the value and returns the result
132 	@param {Double} value The value to be used in the function
133 	@return {Double} The result
134 	@todo Perhaps inplement a binary search for the correct range
135 **/
136 PiecewiseFunction.prototype.resolve = function(value) {
137 	for(var i =0; i < this.ranges.length; i++) 
138 	{
139 		if(this.ranges[i].inRange(value)) {
140 			return this.functs[i].resolve(value);
141 		}
142 	}
143 };
144 
145 /**
146 	Creates a simple string representation of the piecewise function
147 	@return {string} The string representation
148 **/
149 PiecewiseFunction.prototype.toString = function() {
150 	var output = "{";
151 	for(var i=0; i < this.functs.length; i++) {
152 		this.functs[i].sort();
153 		output += "f"+i+"(x)="+this.functs[i]+" on range: "+this.ranges[i]+", ";	
154 	}
155 	return output+"}";
156 };
157 /**
158 	A static constant to help with de/serialization
159 	@constant
160 	@static
161 **/
162 PiecewiseFunction.serializeName = "PiecewiseFunction";
163 
164 /**
165 	Prepares an Object ready to be passed to a web worker	
166 	@return {Object}
167 **/
168 PiecewiseFunction.prototype.toWebWorker = function() {
169 	for(var i = 0; i < this.ranges.length; i++)
170 	{
171 		this.ranges[i].toWebWorker();
172 	}
173 	var serializedfuncts = new Array(this.functs.length);
174 	for(var j=0; j < this.functs.length; j++)
175 	{
176 		serializedfuncts[j] = this.functs[j].toWebWorker();
177 	}
178 	return {
179 		"serializeName":PiecewiseFunction.serializeName,
180 		"ranges":this.ranges,
181 		"functs":serializedfuncts
182 	};
183 };
184 
185 /**
186 	Reconstitutes a PiecewiseFunction object and its data members after passed to web worker
187 	@static
188 	@param {Object} that A PiecewiseFunction strippd of its methods
189 **/
190 PiecewiseFunction.fromWebWorker = function(that) {
191 	reattachMethods(that, PiecewiseFunction);
192 	for(var i=0; i < that.ranges.length; i++)
193 	{
194 		Range.fromWebWorker(that.ranges[i]);
195 	}
196 	var fromHelper = {}; //Probably should centralize this somewhere...
197 	fromHelper[Term.serializeName] = Term.fromWebWorker;
198 	fromHelper[Polynomial.serializeName] = Polynomial.fromWebWorker;
199 	fromHelper[PiecewiseFunction.serializeName] = PiecewiseFunction.fromWebWorker; //MADNESS, but legal
200 	for(var j = 0; j < that.functs.length; j++) 
201 	{
202 		fromHelper[that.functs[j].serializeName](that.functs[j]);
203 	}
204 };
205 
206 /**
207 	Given a list of points generates a piece-wise spline function of the first degree.
208 	@param {Point[]} points An array of point objects sorted by x-value
209 	@return {PiecewiseFunction} The linear spline interpolation
210 	@example	
211 	Roughly based on Page 374
212 	test code:
213 	var Q = [new Point(0,8),new Point(1,12),new Point(3,2),new Point(4,6),new Point(8,0)];
214 	var Y = [new Point(0,8),new Point(1,6),new Point(3,5),new Point(4,3),new Point(6,2),new Point(8,0)];
215 	ourGraph.points = Y;
216 	var yspline = createFirstDegSpline(Y);
217 	ourGraph.drawPoints();
218 	ourGraph.drawFunction(yspline);
219 **/
220 PiecewiseFunction.prototype.createFirstDegSpline = function(points) {
221 	if(points.length < 2)
222 	{
223 		throw new Error("At least 2 points are required");
224 	}
225 	var sortedpoints = points.slice();
226 	sortedpoints.sort(function(a,b) {
227 		if(a.x < b.x) {
228 			return -1;
229 		}else if(a.x > b.x) {
230 			return 1;
231 		}else{
232 			return 0;
233 		}
234 
235 	});
236 	var functionarray = [];
237 	var rangearray = [];
238 	for(var i =0; i < sortedpoints.length-1; i++) {
239 		rangearray.push(new Range("["+sortedpoints[i].x+","+sortedpoints[i+1].x+")"));
240 		var newspline = new Term(1,1,'x');
241 		newspline = newspline.subtract(new Term(sortedpoints[i].x,0,'x'));
242 		newspline = newspline.multiply((sortedpoints[i+1].y-sortedpoints[i].y)/(sortedpoints[i+1].x-sortedpoints[i].x));
243 		newspline = newspline.add(sortedpoints[i].y);
244 		functionarray.push(newspline);
245 	}
246 	//functionarray.reverse();
247 	//rangearray.reverse();
248 	return new PiecewiseFunction(functionarray,rangearray);
249 };
250 
251 /**
252 	Given a list of points generates a piece-wise spline function of the second degree.
253 	@param {Point[]} points An array of point objects sorted by x-value
254 	@param {Double} zzero 0 by default, otherwise the slope of the second derivative of the initial function 
255 	@return {PiecewiseFunction} The quadratic spline interpolation
256 	@example	
257 	Roughly based on Page 380
258 	test code:
259 	var Q = [new Point(0,8),new Point(1,12),new Point(3,2),new Point(4,6),new Point(8,0)];
260 	var Y = [new Point(0,8),new Point(1,6),new Point(3,5),new Point(4,3),new Point(6,2),new Point(8,0)];
261 	var Z = [new Point(-1,2),new Point(0,1),new Point(0.5,0),new Point(1,1),new Point(2,2),new Point(5/2.0,3)];
262 	ourGraph.points = Z;
263 	var yspline = createSecondDegSpline(Z);
264 	ourGraph.drawPoints();
265 	ourGraph.drawFunction(yspline);
266 **/
267 PiecewiseFunction.prototype.createSecondDegSpline = function(points, zzero) {
268 	if(points.length < 2)
269 	{
270 		throw new Error("At least 2 points are required");
271 	}
272 	var sortedpoints = points.slice();
273 	sortedpoints.sort(function(a,b) {
274 		if(a.x < b.x) {
275 			return -1;
276 		}else if(a.x > b.x) {
277 			return 1;
278 		}else{
279 			return 0;
280 		}
281 
282 	});
283 	var z = [];
284 	if(zzero !== undefined)
285 	{
286 		z[0] = zzero;
287 	}else{
288 		z[0] = 0;
289 	}
290 	var functionarray = [];
291 	var rangearray = [];
292 	for(var i =0; i < sortedpoints.length-1; i++) {
293 		if(i == sortedpoints.length-2) {
294 			rangearray.push(new Range("["+sortedpoints[i].x+","+sortedpoints[i+1].x+"]"));
295 		}else{
296 			rangearray.push(new Range("["+sortedpoints[i].x+","+sortedpoints[i+1].x+")"));
297 		}
298 		z[i+1]=2*((sortedpoints[i+1].y-sortedpoints[i].y)/(sortedpoints[i+1].x-sortedpoints[i].x))-z[i];
299 		var newspline = new Term(1,1,'x');
300 		var ns1 = newspline.subtract(new Term(sortedpoints[i].x,0,'x'));
301 		//console.log(ns1);
302 		var newquadratic = ns1.exponentiate(2);
303 		var nq = newquadratic.multiply((z[i+1]-z[i])/(2*(sortedpoints[i+1].x-sortedpoints[i].x)));
304 		//console.log(nq);
305 		var ns2 = ns1.multiply(z[i]);
306 		var nq2 = nq.add(ns2);
307 		var f = nq2.add(sortedpoints[i].y);
308 		functionarray.push(f);
309 	}
310 		//console.log(z);
311 	//functionarray.reverse();
312 	//rangearray.reverse();
313 	return new PiecewiseFunction(functionarray,rangearray);
314 };
315 
316 /**
317 	Creates a Natural Cubic Spline given a series of points
318 	@author Sahil Diwan
319 	@param {Point[]} points An array of points to interpolate
320 	@return {PiecewiseFunction} A piecewise function using polynomials of degree three
321 	IMPLEMENTED algorithm on page 392
322 **/
323 PiecewiseFunction.prototype.createThirdDegSpline = function(points) {
324 	if(points.length < 2)
325 	{
326 		throw new Error("At least 2 points are required");
327 	}
328 	var sortedpoints = points.slice();
329 	sortedpoints.sort(function(a,b) {
330 		if(a.x < b.x) {
331 			return -1;
332 		}else if(a.x > b.x) {
333 			return 1;
334 		}else{
335 			return 0;
336 		}
337 
338 	});
339 	var h = [];
340 	var b = [];
341 	for(var i = 0; i < sortedpoints.length - 1; i++) {
342 		h[i] = sortedpoints[i + 1].x - sortedpoints[i].x;
343 		b[i] = ((sortedpoints[i + 1].y - sortedpoints[i].y)/h[i]);
344 	}
345 	var u = [];
346 	var v = [];
347 	for(var i = 1; i < sortedpoints.length - 1; i++) {
348 		if(i == 1) {
349 			u[1] = ((h[0] + h[1]) * 2);
350 			v[1] = (((b[1] - b[0])) * 6);
351 		} 
352 		else {
353 			u[i] = (((h[i] + h[i - 1]) * 2) - ((Math.pow(h[i - 1], 2) / u[i - 1])));
354 			v[i] = (((b[i] - b[i - 1]) * 6) - ((h[i - 1] * v[i - 1]) / u[i - 1]));
355 		}
356 	}
357 	var z = [0];
358 	z[sortedpoints.length-1]=0;
359 	for(i = sortedpoints.length - 2; i >= 1; i--) {
360 		z[i] = ((v[i] - (h[i] * z[i + 1])) / u[i]);
361 	}
362 
363 	var functionarray = [];
364 	var rangearray = [];
365 
366 	for(var i = 0; i < sortedpoints.length - 1; i++) {
367 		var tmp1 = (new Term(1, 1, "x")).subtract(sortedpoints[i].x).exponentiate(3).multiply(z[i + 1] / (6 * h[i]));
368 		/*console.log(tmp1.toString());
369 		console.log(sortedpoints[i].x)
370 		console.log(z[i + 1] / (6 * h[i]))*/
371 		var tmp2 = (new Term(sortedpoints[i + 1].x, 0, "x").subtract(new Term(1, 1, "x")).exponentiate(3).multiply(z[i] / (6 * h[i])));
372 		var tmp3 = (new Term(1, 1, "x").subtract(sortedpoints[i].x).multiply((sortedpoints[i + 1].y / h[i]) - ((h[i]/6) * z[i+1])));
373 		var tmp4 = (new Term(sortedpoints[i + 1].x, 0, "x").subtract(new Term(1, 1, "x")).multiply((sortedpoints[i].y / h[i]) - ((h[i]/6) * z[i])));
374 
375 		functionarray[i] = tmp1.add(tmp2).add(tmp3).add(tmp4); 
376 		rangearray.push(new Range("["+sortedpoints[i].x+","+sortedpoints[i+1].x+"]"));
377 	}
378 
379 	return new PiecewiseFunction(functionarray,rangearray);
380 };
381 
382 /**
383 	Generate points for SVG paths/curves
384 	@return {Point[]} Path control points in order
385 **/
386 PiecewiseFunction.prototype.generateBezierPaths = function() {
387 	var paths = [];
388 	for(var i = 0; i < this.functs.length; i++) {
389 		var pointset = [];
390 		var fn =this.functs[i];
391 		var fnrange = this.ranges[i];
392 		var startp = new Point(fnrange.lowerbound,fn.resolve(fnrange.lowerbound));
393 		var endp = new Point(fnrange.upperbound,fn.resolve(fnrange.upperbound));
394 		pointset.push(startp);
395 		switch(fn.degree()) {
396 			case 1: 
397 				pointset.push(endp);
398 				break;
399 			case 2:
400 				var xcoeff = (endp.x-startp.x);
401 				var parax = (new Term(xcoeff,1,'t')).add(new Term(startp.x,0,'t'));
402 				//.log(parax+"");
403 				//.log(parax);
404 				var paray = fn.resolve({'x':parax});
405 				//.log(paray+"");
406 				var quadbezier = new Matrix(3,3,[[1,0,0],[-2,2,0],[1,-2,1]]);
407 				var xhalf = quadbezier.scaledPartialPivotGaussian(Matrix.columnVector([startp.x,xcoeff,0]));
408 				//.log(xhalf);
409 				paray.simplify();
410 				paray.sort();
411 				//.log(paraycoeffs);
412 				//.log(paray);
413 				var paraycoeffs = [paray.degreeCoeff(0),paray.degreeCoeff(1),paray.degreeCoeff(2)];
414 				//.log(paraycoeffs);
415 				var yhalf = quadbezier.scaledPartialPivotGaussian(Matrix.columnVector(paraycoeffs));
416 				//.log(yhalf);
417 				pointset.push(new Point(xhalf.values[1][0],yhalf.values[1][0]));
418 
419 
420 				pointset.push(endp);
421 				break;
422 			case 3: 
423 				pointset.push(endp);
424 				break;
425 			default: 
426 				throw new Error("Function degree out of bounds.");
427 				break;	
428 		}
429 
430 		paths.push(pointset);
431 	}
432 	this.paths = paths;
433 	return paths;
434 }
435