├── README.md ├── LICENSE ├── jmat_test.js ├── jmat_demo.html └── jmat_plot.js /README.md: -------------------------------------------------------------------------------- 1 | # Jmat.js 2 | 3 | Complex special functions, numerical linear algebra and statistics in JavaScript 4 | 5 | See demo at: 6 | http://lodev.org/jmat/jmat_demo.html 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, lvandeve 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /jmat_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Jmat.js 3 | 4 | Copyright (c) 2011-2014, Lode Vandevenne 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 3. The name of the author may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | // Unit tests for Jmat.js 30 | 31 | // constructor 32 | Jmat.Test = function() { 33 | // empty, it's a namespace 34 | }; 35 | 36 | // Works both for Complex or Matrix objects. 37 | Jmat.Test.expectNear = function(e, a, epsilon) { 38 | if(Jmat.isNaN(e) && Jmat.isNaN(a)) return; //both NaN is ok for test 39 | if(!Jmat.near(e, a, epsilon)) throw 'fail: expected ' + Jmat.toString(e) + ' got ' + Jmat.toString(a); 40 | }; 41 | 42 | // Expect that the result of the mathematical function f with the arguments of var_arg, is near the expected result. Some numerical intolerance is allowed. 43 | Jmat.Test.testFunction = function(expected, epsilon, f, var_arg) { 44 | var result = f.apply(this, Array.prototype.slice.call(arguments).slice(3) /*var_arg*/); 45 | Jmat.Test.expectNear(expected, result, epsilon); 46 | }; 47 | 48 | //u,s,v = expected values 49 | //m = input matrix 50 | Jmat.Test.testSVD = function(u, s, v, epsilon, m) { 51 | var svd = Matrix.svd(Matrix.cast(m)); 52 | Jmat.Test.expectNear(u, svd.u, epsilon); 53 | Jmat.Test.expectNear(s, svd.s, epsilon); 54 | Jmat.Test.expectNear(v, svd.v, epsilon); 55 | }; 56 | 57 | // throws on fail, prints 'success' on success 58 | Jmat.doUnitTest = function() { 59 | // check that the test framework itself can actually fail 60 | var thrown = false; 61 | try { 62 | Jmat.Test.testFunction(3, eps, Jmat.add, 1, 1); 63 | } catch(error) { 64 | thrown = true; // this is expected 65 | } 66 | if(!thrown) throw 'that should have thrown error!'; 67 | 68 | var eps = 1e-10; 69 | // basic operators 70 | Jmat.Test.testFunction(5, eps, Jmat.add, 2, 3); 71 | Jmat.Test.testFunction(6, eps, Jmat.mul, 2, 3); 72 | 73 | // advanced functions 74 | Jmat.Test.testFunction(0, eps, Jmat.sin, Math.PI); 75 | 76 | // special functions 77 | Jmat.Test.testFunction(24, eps, Jmat.gamma, 5); 78 | Jmat.Test.testFunction('-0.15494982830181-0.498015668118356i', eps, Jmat.gamma, 'i'); 79 | Jmat.Test.testFunction('0.9303796037430951+0.0389361908951213i', 1e-5, Jmat.erf, '5+5i'); 80 | 81 | // distributions 82 | Jmat.Test.testFunction(0.274997, 1e-2, Jmat.qf_chi_square, 0.4, 1); // This one is very imprecise currently :( 83 | Jmat.Test.testFunction(0.198964, 1e-6, Jmat.pdf_studentt, 0.5, 0.5); // This one is very imprecise currently :( 84 | 85 | // matrix basic operators 86 | Jmat.Test.testFunction([[6,8],[10,12]], eps, Jmat.add, [[1,2],[3,4]], [[5,6],[7,8]]); 87 | Jmat.Test.testFunction([[19,22],[43,50]], eps, Jmat.mul, [[1,2],[3,4]], [[5,6],[7,8]]); 88 | 89 | // matrix advanced operators 90 | Jmat.Test.testFunction([[-2,1],[1.5,-0.5]], eps, Jmat.inv, [[1,2],[3,4]]); 91 | Jmat.Test.testFunction([[5,-1],[-2,0]], eps, Jmat.fft, [[1,2],[3,4]]); 92 | Jmat.Test.testSVD([[0.404554, 0.914514], [0.914514, -0.404554]], 93 | [[5.46499, 0],[0,0.365966]], 94 | [[0.576048, -0.817416],[0.817416, 0.576048]], 95 | 1e-5, [[1,2],[3,4]]); 96 | Jmat.Test.testSVD([[1]], 97 | [[2.23607, 0]], 98 | [[0.447214, -0.894427], [0.894427, 0.447214]], 99 | 1e-5, [[1,2]]); 100 | Jmat.Test.testSVD([[0.447214, -0.894427], [0.894427, 0.447214]], 101 | [[2.23607], [0]], 102 | [[1]], 103 | 1e-5, [[1],[2]]); 104 | 105 | // matrix parsing 106 | Jmat.Test.testFunction([[1,2],[3,4]], eps, Jmat.Matrix.parse, '[[1,2],[3,4]]'); 107 | Jmat.Test.testFunction([[1,2,3,4]], eps, Jmat.Matrix.parse, '[[1,2,3,4]]'); 108 | Jmat.Test.testFunction([[1],[2],[3],[4]], eps, Jmat.Matrix.parse, '[1,2,3,4]'); 109 | 110 | // numerical algorithms 111 | Jmat.Test.testFunction(333.33333333333, eps, Jmat.integrate, 0, 10, function(z) { return z.mul(z); }); 112 | 113 | 114 | console.log('success'); 115 | return 'success'; 116 | }; 117 | -------------------------------------------------------------------------------- /jmat_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jmat.js Demo 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /jmat_plot.js: -------------------------------------------------------------------------------- 1 | /* 2 | Jmat.js 3 | 4 | Copyright (c) 2011-2014, Lode Vandevenne 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 3. The name of the author may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | // For documentation, see jmat.js 30 | // NOTE: requires charset="utf-8" to render navigation arrow characters correctly 31 | 32 | //////////////////////////////////////////////////////////////////////////////// 33 | //////////////////////////////////////////////////////////////////////////////// 34 | //////////////////////////////////////////////////////////////////////////////// 35 | // Plotting - public API 36 | //////////////////////////////////////////////////////////////////////////////// 37 | //////////////////////////////////////////////////////////////////////////////// 38 | 39 | // TODO: support any size rather than always 320x320 pixels 40 | 41 | // e.g.: new Jmat.PlotParams({p: 1}) 42 | // constructor 43 | Jmat.PlotParams = function(o) { 44 | if(!o) o = {}; 45 | 46 | this.p = o.p != undefined ? o.p : 2; //pixel size (==> resolution). 1 = highest resolution, 2 = half resolution, etc... 47 | 48 | this.xsize = o.xsize != undefined ? o.xsize : 20; // e.g. an xsize of 10 makes it go from x=-5 to +5 if shift is 0 49 | this.ysize = o.ysize != undefined ? o.ysize : 20; 50 | if(o.s && !o.xsize && !o.ysize) this.xsize = this.ysize = o.s; // 's' is shortcut to make both that size 51 | this.xshift = o.xshift != undefined ? o.xshift : 0; // point which you want in the center of the plot 52 | this.yshift = o.yshift != undefined ? o.yshift : 0; 53 | 54 | // The value at which the complex color wheel has highest saturation (pure red for positive real). Higher value gives more white color, lower gives darker color. 55 | // Not used by real plot. 56 | this.v = o.v != undefined ? o.v : 1; 57 | }; 58 | 59 | //fun = mathematical function taking 1 Jmat.Complex arguments, e.g. Jmat.sin 60 | //params = parameter object with plot size, resolution, etc.... See Jmat.PlotParams. 61 | //parent = HTML parent element, best of type div. All necessary elements (e.g. canvas) will be created inside of it. 62 | Jmat.plotReal = function(fun, parent, params, label) { 63 | if(!params) params = new Jmat.PlotParams(); 64 | if(!(params instanceof Jmat.PlotParams)) { 65 | params = new Jmat.PlotParams(params); 66 | } 67 | if(!parent) parent = document.body; 68 | Jmat.Plot.plotReal_(fun, params, parent, label); 69 | }; 70 | 71 | //fun = mathematical function taking 1 Jmat.Complex arguments, e.g. Jmat.gamma 72 | //params = parameter object with plot size, resolution, etc.... See Jmat.PlotParams. 73 | //parent = HTML parent element, best of type div. All necessary elements (e.g. canvas) will be created inside of it. 74 | Jmat.plotComplex = function(fun, parent, params, label) { 75 | if(!params) params = new Jmat.PlotParams({p: 4}); 76 | if(!(params instanceof Jmat.PlotParams)) { 77 | params = new Jmat.PlotParams(params); 78 | } 79 | if(!parent) parent = document.body; 80 | Jmat.Plot.plot2D_(function(x, y) { 81 | return fun(Jmat.Complex(x.re, y.re)); 82 | }, params, parent, label, 're', 'im'); 83 | }; 84 | 85 | //fun = mathematical function taking 2 Jmat.Complex arguments, e.g. Jmat.besselj 86 | //params = parameter object with plot size, resolution, etc.... See Jmat.PlotParams. 87 | //parent = HTML parent element, best of type div. All necessary elements (e.g. canvas) will be created inside of it. 88 | Jmat.plot2D = function(fun, parent, params, label) { 89 | if(!params) params = new Jmat.PlotParams(); 90 | if(!(params instanceof Jmat.PlotParams)) { 91 | params = new Jmat.PlotParams(params); 92 | } 93 | if(!parent) parent = document.body; 94 | Jmat.Plot.plot2D_(fun, params, parent, label); 95 | }; 96 | 97 | // Stop plot2D or plotComplex, if they are taking a very long time. May still 98 | // calculate several pixels before actually stopping: they do several lines at the time. 99 | Jmat.stopPlotting = function() { 100 | Jmat.Plot.stopIndex_ = (Jmat.Plot.stopIndex_ ? Jmat.Plot.stopIndex_ + 1 : 1); 101 | }; 102 | 103 | //////////////////////////////////////////////////////////////////////////////// 104 | //////////////////////////////////////////////////////////////////////////////// 105 | //////////////////////////////////////////////////////////////////////////////// 106 | // Graphics (color & DOM) helper functions 107 | //////////////////////////////////////////////////////////////////////////////// 108 | //////////////////////////////////////////////////////////////////////////////// 109 | 110 | 111 | // Jmat graphics, graphing and plotting library 112 | // internal functions are grouped in here 113 | Jmat.Plot = function() { 114 | }; 115 | 116 | //input and output in range [0-255] 117 | Jmat.Plot.hslToRgb = function(h, s, l) { 118 | var r, g, b; 119 | var temp1, temp2, tempr, tempg, tempb; 120 | h /= 256.0; 121 | s /= 256.0; 122 | l /= 256.0; 123 | if(s == 0) { 124 | r = g = b = l; 125 | } else { 126 | if(l < 0.5) temp2 = l * (1 + s); 127 | else temp2 = (l + s) - (l * s); 128 | temp1 = 2 * l - temp2; 129 | tempr = h + 1.0 / 3.0; 130 | if(tempr > 1) tempr--; 131 | tempg = h; 132 | tempb = h - 1.0 / 3.0; 133 | if(tempb < 0) tempb++; 134 | 135 | //Red 136 | if(tempr < 1.0 / 6.0) r = temp1 + (temp2 - temp1) * 6.0 * tempr; 137 | else if(tempr < 0.5) r = temp2; 138 | else if(tempr < 2.0 / 3.0) r = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempr) * 6.0; 139 | else r = temp1; 140 | //Green 141 | if(tempg < 1.0 / 6.0) g = temp1 + (temp2 - temp1) * 6.0 * tempg; 142 | else if(tempg < 0.5) g = temp2; 143 | else if(tempg < 2.0 / 3.0) g = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempg) * 6.0; 144 | else g = temp1; 145 | //Blue 146 | if(tempb < 1.0 / 6.0) b = temp1 + (temp2 - temp1) * 6.0 * tempb; 147 | else if(tempb < 0.5) b = temp2; 148 | else if(tempb < 2.0 / 3.0) b = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempb) * 6.0; 149 | else b = temp1; 150 | } 151 | return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]; 152 | }; 153 | 154 | //input and output in range [0-255] 155 | Jmat.Plot.hsvToRgb = function(h, s, v) { 156 | var r, g, b; 157 | h /= 256.0; 158 | s /= 256.0; 159 | v /= 256.0; 160 | if(s == 0) { 161 | r = g = b = v; 162 | } else { 163 | h *= 6; 164 | var i = Math.floor(h); 165 | var f = h - i; 166 | var p = v * (1 - s); 167 | var q = v * (1 - (s * f)); 168 | var t = v * (1 - (s * (1 - f))); 169 | if(i == 0) { r = v; g = t; b = p; } 170 | if(i == 1) { r = q; g = v; b = p; } 171 | if(i == 2) { r = p; g = v; b = t; } 172 | if(i == 3) { r = p; g = q; b = v; } 173 | if(i == 4) { r = t; g = p; b = v; } 174 | if(i == 5) { r = v; g = p; b = q; } 175 | } 176 | return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]; 177 | }; 178 | 179 | // rgb: [r, g, b], each in range 0-255 180 | Jmat.Plot.rgbToCss = function(rgb) { 181 | var r = rgb[0].toString(16); 182 | var g = rgb[1].toString(16); 183 | var b = rgb[2].toString(16); 184 | 185 | if(r.length < 2) r = '0' + r; 186 | if(g.length < 2) g = '0' + g; 187 | if(b.length < 2) b = '0' + b; 188 | if(r.length > 2) r = 'ff'; 189 | if(g.length > 2) g = 'ff'; 190 | if(b.length > 2) b = 'ff'; 191 | 192 | return '#' + r + g + b; 193 | }; 194 | 195 | Jmat.Plot.complexColorFormula_ = 0; //0 = "tweaked wikipedia", 1 = "wikipedia", 2 = "original" 196 | 197 | // For "Color wheel graphs of complex functions" (not for "Domain coloring" which has repeating pattern of lightness rather than black for 0, white for infinity) 198 | //maxvalue matches the halfway brightness of the HSL color model. Higher values go towards white, lower values go towards black, but are capped. 199 | //hue is the argument. Positive real values are red, negative real values are cyan, positive imag values are grassgreen, negative imag values are purple, other colors are complex. 200 | Jmat.Plot.getComplexColor = function(y, maxval) { 201 | var rgb; 202 | 203 | if(Jmat.Complex.isNaN(y)) { 204 | rgb = [128, 128, 128]; 205 | } else { 206 | // Intention of the color (if maxval = 1): 207 | // +1 = pure red (255,0,0) 208 | // -1 = pure cyan (0,255,255) 209 | // +-Infinity = white (small mod applied to that below: set to lightness 254 instead of 255, to show complex argument of it) 210 | // 0 = black 211 | // hue = complex argument: red for 0 deg, yellow/green for 90 deg, cyan for 180 deg, purple for 270 deg. 212 | // |z| = 1: best possible saturation, lightness of hsv model == 128 213 | // |z| > 1: brigher than that 214 | // |z| < 1: darker than that 215 | 216 | var h = Jmat.Complex.argr1(y) * 255; 217 | var s = 255; 218 | var l; 219 | var a = Jmat.Complex.absr(y) / maxval; 220 | 221 | var m = 254; //max lightness for non-infinity (e.g. 240 or 250) 222 | 223 | // Original formula. Use with Jmat.Plot.hslToRgb 224 | if(Jmat.Plot.complexColorFormula_ == 2) { 225 | l = (m / 255) * a / (a + 1); 226 | s = 255; 227 | } 228 | 229 | // The Wikipedia formula. Use with hsvToRgb 230 | if(Jmat.Plot.complexColorFormula_ == 1) { 231 | l = 1 - 1 / (1.1 + 5*Math.log(a + 1)); 232 | s = 255 / (1 + 0.3*Math.log(a + 1)); 233 | } 234 | 235 | // Tweaked version of the Wikipedia formula. Use with hsvToRgb 236 | // Advantage over "original" formula: prettier, less visible "transition lines" 237 | // Disadvantage over "original" formula: less clear, less difference between magnitudes, value 1 is not perfectly #ff0000 238 | if(Jmat.Plot.complexColorFormula_ == 0) { 239 | var mm = 1 - m/255; 240 | l = (1 - 4*mm) - (1 - 8*mm) / (1 + 15*Math.log(a + 1)); 241 | s = 255 / (1 + 0.3*Math.log(a + 1)); 242 | } 243 | 244 | l *= 255; 245 | if(l < (255-m) & a > 0) l = (255-m); 246 | if(l > m) l = m; 247 | 248 | if(Jmat.Plot.complexColorFormula_ == 2) rgb = Jmat.Plot.hslToRgb(h, s, l); 249 | else rgb = Jmat.Plot.hsvToRgb(h, s, l); 250 | } 251 | 252 | return rgb; 253 | }; 254 | 255 | Jmat.Plot.makeSizedDiv = function(parent, x, y, w, h) { 256 | var el = document.createElement('div'); 257 | el.style.position = 'absolute'; 258 | el.style.left = '' + Math.floor(x) + 'px'; 259 | el.style.top = '' + Math.floor(y) + 'px'; 260 | el.style.width = Math.floor(w) + 'px'; 261 | el.style.height = Math.floor(h) + 'px'; 262 | parent.appendChild(el); 263 | return el; 264 | }; 265 | 266 | //'align' meaning: 0 = left/top, 1 = center, 2 = right/bottom 267 | // if width is given (> 0), it is multiline text. Else it is single line text 268 | // fontSize is a CSS value like 'small' 269 | Jmat.Plot.makeAlignedText = function(parent, text, width, x, y, alignx, aligny, fontSize) { 270 | var div = document.createElement('div'); 271 | if(fontSize) div.style.fontSize = fontSize; 272 | div.innerHTML = text; 273 | div.style.position = 'absolute'; 274 | div.style.textAlign = alignx == 0 ? 'left' : alignx == 1 ? 'center' : 'right'; 275 | if(!width) div.style.overflow = 'hidden'; 276 | if(!width) div.style.whiteSpace = 'nowrap'; 277 | 278 | if(width) div.style.width = width + 'px'; 279 | parent.appendChild(div); 280 | var finalwidth = width; 281 | var h = div.clientHeight; 282 | var w = div.clientWidth; 283 | if(!width) finalwidth = w; 284 | 285 | if(aligny == 0) div.style.top = '' + Math.floor(y) + 'px'; 286 | if(aligny == 1) div.style.top = '' + Math.floor(y - h / 2) + 'px'; 287 | if(aligny == 2) div.style.top = '' + Math.floor(y - h) + 'px'; 288 | 289 | if(alignx == 0) div.style.left = '' + Math.floor(x) + 'px'; 290 | if(alignx == 1) div.style.left = '' + Math.floor(x - finalwidth / 2) + 'px'; 291 | if(alignx == 2) div.style.left = '' + Math.floor(x - finalwidth) + 'px'; 292 | 293 | return div; 294 | }; 295 | 296 | //adds text vertically and horizontally centered, multiline depending on width. 297 | //returns the text element 298 | // if width is given (> 0), it is multiline text. Else it is single line text 299 | Jmat.Plot.makeCenteredText = function(parent, text, width, x, y, fontSize) { 300 | return Jmat.Plot.makeAlignedText(parent, text, width, x, y, 1, 1, fontSize) 301 | }; 302 | 303 | Jmat.Plot.useHTML5canvas_ = true; 304 | 305 | // Plot a pixel or rectangle to the given element. E.g. if w and h are 2, it's a 2x2 pixel. 306 | // Position: x, y 307 | // Size: w, h 308 | // Color: [r, g, b], each in range 0-255 309 | Jmat.Plot.rect = function(parent, x, y, w, h, rgb) { 310 | // NON-canvas version (slower) 311 | if(!Jmat.Plot.useHTML5canvas_) { 312 | var el = Jmat.Plot.makeSizedDiv(parent, x, y, w, h); 313 | el.style.backgroundColor = Jmat.Plot.rgbToCss(rgb); 314 | return el; 315 | } 316 | 317 | // canvas version (faster) 318 | var data = parent.data; 319 | var id = parent.idd; 320 | var ctx = parent.ctx; 321 | if(!data) { 322 | var canvas = document.createElement('canvas'); 323 | canvas.style.position = 'absolute'; 324 | canvas.style.left = 0; 325 | canvas.style.top = 0; 326 | canvas.width = parseInt(parent.style.width); 327 | canvas.height = parseInt(parent.style.height); 328 | ctx = canvas.getContext("2d"); 329 | id = ctx.createImageData(1,1); 330 | data = id.data; 331 | parent.idd = id; 332 | parent.data = data; 333 | parent.ctx = ctx; 334 | parent.appendChild(canvas); 335 | } 336 | 337 | if(w == 1 && h == 1) { 338 | data[0] = rgb[0]; 339 | data[1] = rgb[1]; 340 | data[2] = rgb[2]; 341 | data[3] = 255; 342 | ctx.putImageData(id, x, y); 343 | } else { 344 | ctx.fillStyle = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ",1)"; 345 | ctx.fillRect(x, y, w, h); 346 | } 347 | }; 348 | 349 | //////////////////////////////////////////////////////////////////////////////// 350 | //////////////////////////////////////////////////////////////////////////////// 351 | //////////////////////////////////////////////////////////////////////////////// 352 | // Plotting - Internal implementation 353 | //////////////////////////////////////////////////////////////////////////////// 354 | //////////////////////////////////////////////////////////////////////////////// 355 | 356 | Jmat.Plot.addPlotLabels_ = function(xlabel, ylabel, x0, x1, y0, y1, parent) { 357 | var plotx0 = 32; 358 | var plotx1 = plotx0 + 322; 359 | var ploty0 = 32; 360 | var ploty1 = ploty0 + 322; 361 | 362 | var yc = (y0 + y1) / 2; 363 | Jmat.Plot.makeSizedDiv(parent, plotx0 - 8, ploty0, 8, 1).style.backgroundColor = 'black'; 364 | Jmat.Plot.makeSizedDiv(parent, plotx0 - 8, (ploty0 + ploty1) / 2, 8, 1).style.backgroundColor = 'black'; 365 | Jmat.Plot.makeSizedDiv(parent, plotx0 - 8, ploty1 - 1, 8, 1).style.backgroundColor = 'black'; 366 | Jmat.Plot.makeAlignedText(parent, '' + y1, 0, plotx0 - 8 - 2, ploty0, 2, 1, 'small'); 367 | Jmat.Plot.makeAlignedText(parent, '' + yc, 0, plotx0 - 8 - 2, (ploty0 + ploty1) / 2, 2, 1, 'small'); 368 | Jmat.Plot.makeAlignedText(parent, '' + y0, 0, plotx0 - 8 - 2, ploty1, 2, 1, 'small'); 369 | Jmat.Plot.makeAlignedText(parent, ylabel, 0, plotx0 - 2, (ploty0 + ploty1 * 3) / 4, 2, 1, 'small'); 370 | 371 | var xc = (x0 + x1) / 2; 372 | Jmat.Plot.makeSizedDiv(parent, plotx0, ploty1, 1, 8).style.backgroundColor = 'black'; 373 | Jmat.Plot.makeSizedDiv(parent, (plotx0 + plotx1) / 2, ploty1, 1, 8).style.backgroundColor = 'black'; 374 | Jmat.Plot.makeSizedDiv(parent, plotx1 - 1, ploty1, 1, 8).style.backgroundColor = 'black'; 375 | Jmat.Plot.makeAlignedText(parent, '' + x0, 0, plotx0, ploty1 + 8, 1, 0, 'small'); 376 | Jmat.Plot.makeAlignedText(parent, '' + xc, 0, (plotx0 + plotx1) / 2, ploty1 + 8, 1, 0, 'small'); 377 | Jmat.Plot.makeAlignedText(parent, '' + x1, 0, plotx1, ploty1 + 8, 1, 0, 'small'); 378 | Jmat.Plot.makeAlignedText(parent, xlabel, 0, (plotx0 * 3 + plotx1) / 4, ploty1, 1, 0, 'small'); 379 | }; 380 | 381 | //////////////////////////////////////////////////////////////////////////////// 382 | 383 | Jmat.Plot.makeRealPixel_ = function(div, width, height, params, px, y, prevy, rgb, title) { 384 | var p = params.p; 385 | var ysize = params.ysize; 386 | 387 | var py = Math.floor(height / 2 - ((y+params.yshift) / ysize * height) - 1); 388 | var prevpy = Math.floor(height / 2 - ((prevy+params.yshift) / ysize * height) - 1); 389 | 390 | if(py < 0 && prevpy < 0) return; 391 | if(py > height && prevpy > height) return; 392 | 393 | if(isNaN(y)) { 394 | var d = Jmat.Plot.rect(div, px, 0, p, height, [160,160,160]); 395 | if(d) d.title = title; 396 | return; 397 | } 398 | 399 | if(py >= 0 && py < height) { 400 | var d = Jmat.Plot.rect(div, px, py, p, p, rgb); 401 | if(d) d.title = title; 402 | } 403 | 404 | if(py < 0) py = 0; 405 | if(py > height) py = height; 406 | if(prevpy < 0) prevpy = 0; 407 | if(prevpy > height) prevpy = height; 408 | 409 | if(prevpy + p < py) { 410 | var d = Jmat.Plot.rect(div, px, prevpy + p, p, (py - prevpy), rgb); 411 | if(d) d.title = title; 412 | } else if(prevpy > py + p) { 413 | var d = Jmat.Plot.rect(div, px, py, p, (prevpy - py), rgb); 414 | if(d) d.title = title; 415 | } 416 | }; 417 | 418 | Jmat.Plot.plotReal_ = function(fun, params, parent, label) { 419 | //parent.style.backgroundColor = 'white'; // TODO: make element inside it instead to alter style 420 | var plotfun = function() { 421 | var width = 320; 422 | var height = 320; 423 | var xsize = params.xsize; 424 | var ysize = params.ysize; 425 | var p = params.p; 426 | 427 | var L = 32; 428 | var steps = Math.floor(width / p); 429 | parent.innerHTML = ''; 430 | var div = Jmat.Plot.makeSizedDiv(parent, L, L, width, height); 431 | div.style.backgroundColor = '#eee'; 432 | div.style.border = '1px solid black'; 433 | 434 | // axis lines 435 | var d = Jmat.Plot.makeSizedDiv(div, 0, width / 2, height, 2); 436 | d.style.backgroundColor = '#ccc'; 437 | d = Jmat.Plot.makeSizedDiv(div, width / 2, 0, 2, height); 438 | d.style.backgroundColor = '#ccc'; 439 | 440 | Jmat.Plot.addPlotLabels_('x', 'y', xsize/2+params.xshift, -xsize/2+params.xshift, -ysize/2-params.yshift, ysize/2-params.yshift, parent); 441 | if(label) Jmat.Plot.makeAlignedText(parent, label, 0, L + width, L, 2, 2); 442 | 443 | d = Jmat.Plot.makeCenteredText(parent, '←', 0, L + width / 2 - 35, L - 10); 444 | d.onclick = function() { params.xshift -= params.xsize / 8; plotfun(); }; 445 | d.style.color = '#ddd'; 446 | d = Jmat.Plot.makeCenteredText(parent, '→', 0, L + width / 2 - 15, L - 10); 447 | d.onclick = function() { params.xshift += params.xsize / 8; plotfun(); }; 448 | d.style.color = '#ddd'; 449 | 450 | d = Jmat.Plot.makeCenteredText(parent, '[+]', 0, L + width / 2 + 15, L - 10); 451 | d.onclick = function() { params.xsize /= 2; params.ysize /= 2; plotfun(); }; 452 | d.style.color = '#ddd'; 453 | d = Jmat.Plot.makeCenteredText(parent, '[-]', 0, L + width / 2 + 35, L - 10); 454 | d.onclick = function() { params.xsize *= 2; params.ysize *= 2; plotfun(); }; 455 | d.style.color = '#ddd'; 456 | 457 | d = Jmat.Plot.makeCenteredText(parent, '+', 0, L + width + 8, L + 70); 458 | d.onclick = function() { params.ysize /= 2; plotfun(); }; 459 | d.style.color = '#ddd'; 460 | d = Jmat.Plot.makeCenteredText(parent, '-', 0, L + width + 8, L + 90); 461 | d.onclick = function() { params.ysize *= 2; plotfun(); }; 462 | d.style.color = '#ddd'; 463 | 464 | d = Jmat.Plot.makeCenteredText(parent, '↑', 0, L + width + 8, L + 50); 465 | d.onclick = function() { params.yshift -= params.ysize / 8; plotfun(); }; 466 | d.style.color = '#ddd'; 467 | d = Jmat.Plot.makeCenteredText(parent, '↓', 0, L + width + 8, L + 110); 468 | d.onclick = function() { params.yshift += params.ysize / 8; plotfun(); }; 469 | d.style.color = '#ddd'; 470 | 471 | var prevy; 472 | 473 | for(var i = 0; i < steps; i++) { 474 | var px = i * p; 475 | var re = -xsize / 2 + (i / steps * xsize) + params.xshift; 476 | 477 | var x = Jmat.Complex(re, 0); 478 | var y = fun(x); 479 | if(!prevy) prevy = y; 480 | if(y.re == Infinity && y.im == Infinity) y = Jmat.Complex(NaN); // plot undirected infinity as NaN (should show up as vertical line) 481 | 482 | if(y.im == 0 || (Math.abs(y.im) < Math.abs(y.re) * 1e-10 /*imag likely due to numerical imprecisions*/)) { 483 | Jmat.Plot.makeRealPixel_(div, 320, 320, params, px, y.re, prevy.re, [0,0,0], Jmat.Complex.toString(x) + ': ' + Jmat.Complex.toString(y)); 484 | } else { 485 | // Abs and arg-color-wheel, always in positive zone 486 | var h = Jmat.Complex.argr1(y) * 255; 487 | var rgb = Jmat.Plot.hslToRgb(h, 255, 128); 488 | var a = Jmat.Complex.abs(y).re; 489 | var pa = Jmat.Complex.abs(prevy).re; 490 | Jmat.Plot.makeRealPixel_(div, 320, 320, params, px, a, pa, rgb, Jmat.Complex.toString(x) + ': ' + Jmat.Complex.toString(y)); 491 | } 492 | 493 | prevy = y; 494 | } 495 | }; 496 | 497 | plotfun(); 498 | }; 499 | 500 | //p = pixel cell size 501 | // For "Color wheel graphs of complex functions" (not for "Domain coloring" which has repeating pattern of lightness rather than black for 0, white for infinity) 502 | Jmat.Plot.plotColorPixel = function(y, maxval, p, px, py, div) { 503 | var rgb = Jmat.Plot.getComplexColor(y, maxval); 504 | var d = Jmat.Plot.rect(div, px * p, py * p, p, p, rgb); 505 | return d; 506 | }; 507 | 508 | //////////////////////////////////////////////////////////////////////////////// 509 | 510 | //p = pixel cell size 511 | Jmat.Plot.plot2DPixel_ = function(fun, size, steps, params, px, py, div) { 512 | var x = -size + (px / steps * size * 2); 513 | var y = size - (py / steps * size * 2); 514 | 515 | var z = fun(Jmat.Complex(x + params.xshift), Jmat.Complex(y + params.yshift)); 516 | 517 | var d = Jmat.Plot.plotColorPixel(z, params.v, params.p, px, py, div); 518 | if(d) d.title = x + ', ' + y + ': ' + Jmat.Complex.toString(z); 519 | }; 520 | 521 | 522 | Jmat.Plot.plot2DLineTimeout_ = function(fun, size, steps, params, py, div) { 523 | var stopindex = Jmat.Plot.stopIndex_; 524 | 525 | // This is for first rendering fast, and only then at full resolution 526 | var stage = 1; 527 | var params1 = params; 528 | var steps1 = steps; 529 | 530 | if(params.p <= 2) { 531 | stage = 0; 532 | params = JSON.parse(JSON.stringify(params)); 533 | params.p *= 4; 534 | steps /= 4; 535 | } 536 | 537 | var linefun = function(py) { 538 | window.setTimeout(function() { 539 | for(var i = 0; i < 4; i++) { 540 | if(py == steps) { stage++; params = params1; steps = steps1; py = 0; } 541 | if(stage == 2) return; 542 | if(py == steps) return; 543 | if(stage == 2) return; 544 | for(var px = 0; px < steps; px++) { 545 | Jmat.Plot.plot2DPixel_(fun, size, steps, params, px, py, div); 546 | } 547 | py++; 548 | } 549 | if(stopindex != Jmat.Plot.stopIndex_) return; 550 | linefun(py); 551 | }, 0); 552 | }; 553 | linefun(py); 554 | }; 555 | 556 | Jmat.Plot.plot2DNonBlocking_ = function(fun, size, steps, params, div) { 557 | Jmat.Plot.plot2DLineTimeout_(fun, size, steps, params, 0, div); 558 | }; 559 | 560 | Jmat.Plot.plot2D_ = function(fun, params, parent, label, xlabel, ylabel) { 561 | //parent.style.backgroundColor = 'white'; // TODO: make element inside it instead to alter style 562 | if(!xlabel) xlabel = 'x'; 563 | if(!ylabel) ylabel = 'y'; 564 | var plotfun = function() { 565 | var width = 320; 566 | var height = 320; 567 | var size = params.xsize / 2; 568 | // TODO: support x and y size 569 | //var xsize = params.xsize; 570 | //var ysize = params.ysize; 571 | var p = params.p; 572 | var maxval = params.v; 573 | 574 | var L = 32; 575 | var steps = Math.floor(width / p); 576 | parent.innerHTML = ''; 577 | var div = Jmat.Plot.makeSizedDiv(parent, L, L, steps * p, steps * p); 578 | div.style.backgroundColor = '#888888'; 579 | div.style.border = '1px solid black'; 580 | 581 | Jmat.Plot.addPlotLabels_(xlabel, ylabel, -size+params.xshift, size+params.xshift, -size+params.yshift, size+params.yshift, parent); 582 | if(label) Jmat.Plot.makeAlignedText(parent, label, 0, L + width, L, 2, 2); 583 | 584 | d = Jmat.Plot.makeCenteredText(parent, '←', 0, L + width / 2 - 35, L - 10); 585 | d.onclick = function() { params.xshift -= params.xsize / 2; Jmat.stopPlotting(); plotfun(); }; 586 | d.style.color = '#ddd'; 587 | d = Jmat.Plot.makeCenteredText(parent, '→', 0, L + width / 2 - 15, L - 10); 588 | d.onclick = function() { params.xshift += params.xsize / 2; Jmat.stopPlotting(); plotfun(); }; 589 | d.style.color = '#ddd'; 590 | 591 | d = Jmat.Plot.makeCenteredText(parent, '[+]', 0, L + width / 2 + 15, L - 10); 592 | d.onclick = function() { params.xsize /= 2; params.ysize /= 2; Jmat.stopPlotting(); plotfun(); }; 593 | d.style.color = '#ddd'; 594 | d = Jmat.Plot.makeCenteredText(parent, '[-]', 0, L + width / 2 + 35, L - 10); 595 | d.onclick = function() { params.xsize *= 2; params.ysize *= 2; Jmat.stopPlotting(); plotfun(); }; 596 | d.style.color = '#ddd'; 597 | 598 | d = Jmat.Plot.makeCenteredText(parent, '↑', 0, L + width + 8, L + 70); 599 | d.onclick = function() { params.yshift += params.ysize / 2; Jmat.stopPlotting(); plotfun(); }; 600 | d.style.color = '#ddd'; 601 | d = Jmat.Plot.makeCenteredText(parent, '↓', 0, L + width + 8, L + 90); 602 | d.onclick = function() { params.yshift -= params.ysize / 2; Jmat.stopPlotting(); plotfun(); }; 603 | d.style.color = '#ddd'; 604 | 605 | Jmat.Plot.plot2DNonBlocking_(fun, size, steps, params, div); 606 | } 607 | plotfun(); 608 | }; 609 | 610 | --------------------------------------------------------------------------------