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