├── README.md ├── img ├── gallery │ ├── S.png │ ├── continued-fraction.png │ ├── late-night.png │ └── treeline-1.png ├── header copy.png ├── header-old.png └── header.png ├── index.html ├── js ├── complex_operations.js ├── cool_functions.js ├── engine.js └── scheme_functions.js ├── latinmodernmath.otf ├── plotter.html ├── redirect.html └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # complex-function-plot 2 | -------------------------------------------------------------------------------- /img/gallery/S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/img/gallery/S.png -------------------------------------------------------------------------------- /img/gallery/continued-fraction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/img/gallery/continued-fraction.png -------------------------------------------------------------------------------- /img/gallery/late-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/img/gallery/late-night.png -------------------------------------------------------------------------------- /img/gallery/treeline-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/img/gallery/treeline-1.png -------------------------------------------------------------------------------- /img/header copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/img/header copy.png -------------------------------------------------------------------------------- /img/header-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/img/header-old.png -------------------------------------------------------------------------------- /img/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/img/header.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Complex Function Plot 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 45 | 46 |
47 |
48 |
49 | 54 | 56 |
57 |
58 | 59 |
60 | 61 |
62 |
63 |
64 |

About

65 |

Transformations from the complex plane \(\mathbb{C}\) to itself can create beautifiul patterns and images in a simple way. First let \(S\) be the set of all colors, and define two functions:

66 |
    67 |
  • a complex transformation \(f:\mathbb{C}\to\mathbb{C}\), and
  • 68 |
  • a domain coloring scheme \(g:\mathbb{C}\to S\).
  • 69 |
70 |

Then, color the plane with the new coloring map \(g\circ f\).

71 |

Different domain coloring schemes can drastically change the resulting image. 72 | Many use some combination of increments in phase, modulus, real, and imaginary coordinates. However, you can upload any image to use as a domain coloring scheme!

73 |
74 |
75 | 76 | 77 |
78 |
79 |
80 |

Gallery

81 |
82 |
83 | 84 |
85 |
86 |

\(f(z)=\frac{1}{z+\frac{1}{z^2+\frac{1}{\ddots+\frac{1}{z^{10}+\frac{1}{z^{11}}}}}}\)

87 |
88 |
89 |
90 |
91 |

\(f(z)=z^5\)

92 |
93 |
94 | 95 |
96 |
97 |
98 |
99 |

\(f(z)=z^{4z^{3z^{2z^{z}}}}\)

100 |
101 |
102 | 103 |
104 |
105 |
106 |
107 |

\(f(z)=\frac{i}{z}-\frac{z}{i}\)

108 |
109 |
110 | 111 |
112 |
113 | 114 | 115 |
116 |
117 |

Copyright © 2020 Peter E. Francis

118 |
119 |
120 | 121 |
122 | 123 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /js/complex_operations.js: -------------------------------------------------------------------------------- 1 | 2 | // Thank you, https://github.com/infusion/Complex.js/blob/10d2a84bbf281b53ea4ce0993d0794922e214daf/complex.js 3 | 4 | // guppy AST: https://guppy.js.org/build/coverage/src/ast.js.html 5 | 6 | 7 | 8 | 9 | // real -> real functions 10 | 11 | function r_cosh(x) { 12 | return (Math.exp(x) + Math.exp(-x)) * 0.5; 13 | }; 14 | function r_sinh(x) { 15 | return (Math.exp(x) - Math.exp(-x)) * 0.5; 16 | }; 17 | 18 | 19 | 20 | 21 | // complex -> real functions 22 | 23 | function logHypot(a) { 24 | var _a = Math.abs(a.re); 25 | var _b = Math.abs(a.im); 26 | if (a.re === 0) { 27 | return Math.log(_b); 28 | } 29 | if (a.im === 0) { 30 | return Math.log(_a); 31 | } 32 | if (_a < 3000 && _b < 3000) { 33 | return Math.log(a.re * a.re + a.im * a.im) * 0.5; 34 | } 35 | return Math.log(a.re / Math.cos(Math.atan2(a.im, a.re))); 36 | } 37 | 38 | 39 | 40 | // complex -> boolean 41 | 42 | function isZero(a) { 43 | return a.re == 0 && a.im == 0; 44 | } 45 | 46 | function isInfinity(a) { 47 | return (a.re == Infinity || a.im == Infinity || isNaN(a.re) || isNaN(a.im)); 48 | } 49 | 50 | 51 | // complex -> complex functions 52 | 53 | 54 | function add(a,b) { 55 | return {re: a.re + b.re, 56 | im: a.im + b.im} 57 | } 58 | function multiply(a,b) { 59 | return {re: a.re * b.re - a.im * b.im, 60 | im: a.re * b.im + a.im * b.re} 61 | } 62 | function divide(a,b) { 63 | return {re: (a.re * b.re + a.im * b.im) / (b.re**2 + b.im**2), 64 | im: (a.im * b.re - a.re * b.im) / (b.re**2 + b.im**2)}; 65 | } 66 | function negate(a) { 67 | return {re:-a.re, im:-a.im}; 68 | } 69 | function subtract(a,b) { 70 | return {re: a.re - b.re, 71 | im: a.im - b.im}; 72 | } 73 | function exponential(a,b) { 74 | 75 | if (isZero(b)) { 76 | return {re:1, im:0}; 77 | } 78 | 79 | // If the exponent is real 80 | if (b.im === 0) { 81 | if (a.im === 0 && a.re >= 0) { 82 | return {re:Math.pow(a.re, b.re), im:0}; 83 | } else if (b.re === 0) { // If base is fully imaginary 84 | switch ((b.re % 4 + 4) % 4) { 85 | case 0: return {re:Math.pow(a.im, b.re), im:0}; 86 | case 1: return {re:0, im:Math.pow(a.im, b.re)}; 87 | case 2: return {re:-Math.pow(a.im, b.re), im:0}; 88 | case 3: return {re:0, im:-Math.pow(a.im, b.re)}; 89 | } 90 | } 91 | } 92 | 93 | if (a.re === 0 && a.im === 0 && b.re > 0 && b.im >= 0) { 94 | return {re:0, im:0}; 95 | } 96 | 97 | var arg = Math.atan2(a.im, a.re); 98 | var loh = logHypot(a); 99 | 100 | a = Math.exp(b.re * loh - b.im * arg); 101 | b = b.im * loh + b.re * arg; 102 | return {re:a * Math.cos(b), 103 | im:a * Math.sin(b)}; 104 | } 105 | function modulus(a) { 106 | return {re:Math.sqrt(a.re**2 + a.im**2), 107 | im:0}; 108 | } 109 | function conjugate(a) { 110 | return {re:a.re, 111 | im:-a.im}; 112 | } 113 | function sin(a) { 114 | return {re:Math.sin(a.re) * r_cosh(a.im), 115 | im:Math.cos(a.re) * r_sinh(a.im)}; 116 | } 117 | function cos(a) { 118 | return {re:Math.cos(a.re) * r_cosh(a.im), 119 | im:-Math.sin(a.re) * r_sinh(a.im)}; 120 | } 121 | function tan(a) { 122 | const d = Math.cos(a.re) + r_cosh(a.im); 123 | return {re:Math.sin(a.re) / d, 124 | im:r_sinh(a.im) / d}; 125 | } 126 | function log(a) { 127 | return {re: logHypot(a), 128 | im: Math.atan2(a.im, a.re)}; 129 | } 130 | function cot(a) { 131 | const d = Math.cos(2 * a.re) - r_cosh(2 * a.im); 132 | return {re:-Math.sin(2 * a.re) / d, 133 | im:r_sinh(2 * a.im) / d}; 134 | } 135 | function sec(a) { 136 | const d = 0.5 * (r_cosh(2 * a.im) + Math.cos(2 * a.re)); 137 | return {re: Math.cos(a.re) * r_cosh(a.im) / d, 138 | im: Math.sin(a.re) * r_sinh(a.im) / d}; 139 | } 140 | function csc(a) { 141 | const d = 0.5 * (r_cosh(2 * a.im) - Math.cos(2 * a.re)); 142 | return {re: Math.sin(a.re) * r_cosh(a.im) / d, 143 | im: -Math.cos(a.re) * r_sinh(a.im) / d}; 144 | } 145 | function root(a, b) { 146 | 147 | } 148 | 149 | 150 | 151 | var complex_operations = { 152 | "+":function(args){ 153 | return function(vars){ 154 | return add(args[0](vars),args[1](vars)); 155 | }; 156 | }, 157 | "*":function(args){ 158 | return function(vars){ 159 | return multiply(args[0](vars),args[1](vars)); 160 | }; 161 | }, 162 | "fraction":function(args){ 163 | return function(vars){ 164 | return divide(args[0](vars),args[1](vars)); 165 | } 166 | }, 167 | "/":function(args){ 168 | return function(vars){ 169 | return divide(args[0](vars),args[1](vars)); 170 | } 171 | }, 172 | "-":function(args){ 173 | return function(vars){ 174 | if (args.length == 1) { 175 | return negate(args[0](vars)); 176 | } 177 | return subtract(args[0](vars), args[1](vars)); 178 | } 179 | }, 180 | "neg":function(args){ 181 | return function(vars){ 182 | return negate(args[0](vars)); 183 | } 184 | }, 185 | "val":function(args){ 186 | return function(){ 187 | if (!isNaN(args[0])) { 188 | return {re:args[0], im:0}; 189 | } 190 | return args[0]; 191 | } 192 | }, 193 | "var":function(args){ 194 | return function(vars){ 195 | if(args[0] == "pi") return {re:Math.PI, im:0}; 196 | if(args[0] == "e") return {re:Math.E, im:0}; 197 | if(args[0] == "i") return {re:0, im:1}; 198 | return vars[args[0]]; 199 | } 200 | }, 201 | "exponential":function(args){ 202 | return function(vars){ 203 | return exponential(args[0](vars), args[1](vars)); 204 | } 205 | }, 206 | "conj":function(args){ 207 | return function(vars){ 208 | return conjugate(args[0](vars)); 209 | } 210 | }, 211 | "Re":function(args){ 212 | return function(vars){ 213 | return {re:args[0](vars).re,im:0}; 214 | } 215 | }, 216 | "Im":function(args){ 217 | return function(vars){ 218 | return {re:args[0](vars).im,im:0}; 219 | } 220 | }, 221 | "squareroot":function(args){ 222 | return function(vars){ 223 | return exponential(args[0](vars), {re:1/2, im:0}); 224 | } 225 | }, 226 | "root":function(args){ 227 | return function(vars){ 228 | return exponential(args[1](vars), divide({re:1,im:0}, args[0](vars))); 229 | } 230 | }, 231 | "absolutevalue": function(args) { 232 | return function(vars){ 233 | return modulus(args[0](vars)); 234 | } 235 | }, 236 | "sin": function(args) { 237 | return function(vars){ 238 | return sin(args[0](vars)); 239 | } 240 | }, 241 | "cos": function(args) { 242 | return function(vars){ 243 | return cos(args[0](vars)); 244 | } 245 | }, 246 | "tan": function(args) { 247 | return function(vars){ 248 | return tan(args[0](vars)); 249 | } 250 | }, 251 | "log": function(args) { 252 | return function(vars){ 253 | return log(args[0](vars)); 254 | } 255 | }, 256 | "cot": function(args) { 257 | return function(vars){ 258 | return cot(args[0](vars)); 259 | } 260 | }, 261 | "sec": function(args) { 262 | return function(vars){ 263 | return sec(args[0](vars)); 264 | } 265 | }, 266 | "csc": function(args) { 267 | return function(vars){ 268 | return csc(args[0](vars)); 269 | } 270 | }, 271 | }; 272 | -------------------------------------------------------------------------------- /js/cool_functions.js: -------------------------------------------------------------------------------- 1 | var z_squared = '{}^{}()^()z2'; 2 | 3 | var three = '{}^{}()^()e\\dfrac{}{}\\frac{}{}()/()z+{}^{}()^()\\left(\\right)(){}^{}()^()ei\\dfrac{}{}\\frac{}{}()/()2\\pi pi 30z-{}^{}()^()\\left(\\right)(){}^{}()^()ei\\dfrac{}{}\\frac{}{}()/()2\\pi pi 30{}^{}()^()e\\dfrac{}{}\\frac{}{}()/()z+{}^{}()^()\\left(\\right)(){}^{}()^()ei\\dfrac{}{}\\frac{}{}()/()2\\pi pi 31z-{}^{}()^()\\left(\\right)(){}^{}()^()ei\\dfrac{}{}\\frac{}{}()/()2\\pi pi 31{}^{}()^()e\\dfrac{}{}\\frac{}{}()/()z+{}^{}()^()\\left(\\right)(){}^{}()^()ei\\dfrac{}{}\\frac{}{}()/()2\\pi pi 32z-{}^{}()^()\\left(\\right)(){}^{}()^()ei\\dfrac{}{}\\frac{}{}()/()2\\pi pi 32'; 4 | 5 | 6 | var fractions = '\\dfrac{}{}\\frac{}{}()/()iz-\\dfrac{}{}\\frac{}{}()/()zi' 7 | -------------------------------------------------------------------------------- /js/engine.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var output_canvas = document.getElementById('output'); 4 | var output_ctx = output_canvas.getContext("2d"); 5 | 6 | var domain_canvas = document.getElementById('domain'); 7 | var domain_ctx = domain_canvas.getContext("2d"); 8 | 9 | var guppy_input = new Guppy('guppy-function-input'); 10 | 11 | var func; 12 | 13 | var iterations; 14 | 15 | var re_lb, re_ub; 16 | var im_lb, im_ub; 17 | var disc_re, disc_im; 18 | var zoom_re, zoom_im; // ratio: pixel / actual 19 | var depth_re, depth_im; 20 | var scheme; 21 | var resolution; 22 | 23 | 24 | var image_obj; 25 | var image_data = []; 26 | var image_width; 27 | var image_height; 28 | var image_center_x; 29 | var image_center_y; 30 | var image_pixel_data; 31 | 32 | var image_tiling; 33 | 34 | 35 | var mode = "guppy"; 36 | 37 | 38 | function get_settings() { 39 | 40 | iterations = Math.round(Number(document.getElementById('iterations').value)); 41 | 42 | re_lb = Number(document.getElementById('re-lb').value); 43 | re_ub = Number(document.getElementById('re-ub').value); 44 | im_lb = Number(document.getElementById('im-lb').value); 45 | im_ub = Number(document.getElementById('im-ub').value); 46 | 47 | disc_re = Number(document.getElementById('disc-re').value); 48 | output_canvas.width = disc_re; 49 | disc_im = Number(document.getElementById('disc-im').value); 50 | output_canvas.height = disc_im; 51 | 52 | zoom_re = disc_re / (re_ub - re_lb); 53 | zoom_im = disc_im / (im_ub - im_lb); 54 | 55 | depth_re = Math.pow(10, Math.round(Math.log(zoom_re) / Math.log(10))); 56 | depth_im = Math.pow(10, Math.round(Math.log(zoom_im) / Math.log(10))); 57 | 58 | set_scheme(document.querySelector('input[name="scheme"]:checked').value); 59 | resolution = Number(document.getElementById('resolution').value); 60 | 61 | tile = document.getElementById('image-tiling').checked; 62 | 63 | } 64 | 65 | 66 | function get_output_canvas_point(p) { 67 | // given a point in C, get the output_canvas coordinate point to plot 68 | return {x: (p.re - re_lb) * zoom_re, 69 | y: (im_ub - p.im) * zoom_im}; 70 | } 71 | 72 | function get_C_point(p) { 73 | // given the output_canvas coordinate point, get a point in C 74 | return {re: p.x / zoom_re + re_lb, 75 | im: im_ub - p.y / zoom_im}; 76 | } 77 | 78 | function round(p, depth_r, depth_i) { 79 | return {re: (Math.round(p.re * depth_r) / depth_r), 80 | im: (Math.round(p.im * depth_i) / depth_i) 81 | }; 82 | } 83 | 84 | function toString(p) { 85 | return (p.re < 0 ? "−" + Math.abs(p.re) : p.re) + (p.im >= 0 ? " + " : " − " ) + Math.abs(p.im) + "i"; 86 | } 87 | 88 | output_canvas.addEventListener('mouseout', function() { 89 | document.getElementById('hover-loc').innerHTML = " "; 90 | }); 91 | 92 | output_canvas.addEventListener('mousemove', function(e) { 93 | try { 94 | var rect = output_canvas.getBoundingClientRect(); 95 | var user_x = (e.clientX - rect.left) * (output_canvas.width / output_canvas.clientWidth); 96 | var user_y = (e.clientY - rect.top) * (output_canvas.height / output_canvas.clientHeight); 97 | var p = get_C_point({x:user_x, y:user_y}); 98 | document.getElementById('hover-loc').innerHTML = "ƒ(" + toString(round(p, depth_re, depth_im)) + ") = " + toString(round(func(p), depth_re, depth_im)); 99 | } catch(e) { 100 | // if no image is loaded, this wont work, so this is ok 101 | } 102 | }); 103 | 104 | function switch_mode() { 105 | if (mode == "guppy") { // turn plain text on 106 | document.getElementById('plain-input-group').style.display = "table"; 107 | document.getElementById('switch-mode').innerHTML = 'Switch back to formatted input.'; 108 | document.getElementById('plain-text-error').innerHTML = ""; 109 | document.getElementById('plain-text-error').style.display = "block"; 110 | mode = "plain"; 111 | } else { // turn off 112 | document.getElementById('plain-input-group').style.display = "none"; 113 | document.getElementById('switch-mode').innerHTML = '\(\LaTeX\) input not working? Use plain text.'; 114 | document.getElementById('plain-text-error').style.display = "none"; 115 | mode = "guppy"; 116 | 117 | } 118 | } 119 | 120 | 121 | function load_plain_text() { 122 | document.getElementById('plain-text-error').innerHTML = ""; 123 | try { 124 | guppy_input.import_text(document.getElementById('plain-text-input').value); 125 | guppy_input.engine.end(); 126 | guppy_input.render(true); 127 | } catch (e) { 128 | document.getElementById("plain-text-error").innerHTML = "Parsing Error"; 129 | } 130 | } 131 | 132 | 133 | function render() { 134 | 135 | 136 | output_ctx.clearRect(0, 0, output_canvas.width, output_canvas.height); 137 | 138 | try { 139 | 140 | document.getElementById('error').innerHTML = " "; 141 | 142 | // parse with guppy 143 | try { 144 | const f = guppy_input.func(complex_operations); 145 | func = function(z) { 146 | let ret = {'z':z}; 147 | for (let i = 0; i < iterations; i++) { 148 | ret = {'z': f(ret)}; 149 | } 150 | return ret['z']; 151 | } 152 | } catch(e) { 153 | throw "Error parsing the input f(z)"; 154 | } 155 | 156 | 157 | get_settings(); 158 | 159 | // iterate over points in the output display, and plot the color associated with their output of f 160 | for (var i = 0; i < disc_re; i++) { 161 | for (var j = 0; j < disc_im; j++) { 162 | output_ctx.fillStyle = get_color(func(get_C_point({x:i,y:j}))); 163 | this.output_ctx.fillRect(i, j, 1, 1); 164 | } 165 | } 166 | 167 | } catch(e) { 168 | document.getElementById('error').innerHTML = e; 169 | } 170 | 171 | 172 | } 173 | 174 | function download_output() { 175 | var link = document.createElement('a'); 176 | link.download = 'complex-function-phase-plot.png'; 177 | link.href = output_canvas.toDataURL(); 178 | link.click(); 179 | // window.location.href = output_canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); 180 | } 181 | 182 | 183 | function loadFile(event) { 184 | if (event.target.files.length == 0) { 185 | return; 186 | } 187 | image_obj = new Image(); 188 | image_obj.src = URL.createObjectURL(event.target.files[0]); 189 | image_obj.onload = function() { 190 | var c = document.createElement('canvas'); 191 | c.width = image_obj.width; 192 | c.height = image_obj.height; 193 | var ct = c.getContext('2d'); 194 | ct.drawImage(image_obj, 0, 0, c.width, c.height); 195 | image_data = ct.getImageData(0,0,c.width,c.height); 196 | image_width = image_data.width; 197 | image_height = image_data.height; 198 | image_center_x = Math.floor(image_width / 2); 199 | image_center_y = Math.floor(image_height / 2); 200 | set_scheme('image'); 201 | document.getElementById('image').checked = true; 202 | } 203 | } 204 | 205 | 206 | 207 | 208 | function set_scheme(scheme_name) { 209 | scheme = eval(scheme_name); 210 | preview_domain(); 211 | } 212 | 213 | 214 | function set_resolution(res) { 215 | resolution = Number(res); 216 | preview_domain(); 217 | } 218 | 219 | 220 | function set_image_tiling(bool) { 221 | image_tiling = bool; 222 | preview_domain(); 223 | } 224 | 225 | 226 | function preview_domain() { 227 | domain_ctx.clearRect(0,0,domain_canvas.width, domain_canvas.height); 228 | // always between -1, and 1 in both directions 229 | for (var i = 0; i < domain_canvas.width; i++) { 230 | for (var j = 0; j < domain_canvas.height; j++) { 231 | var re = i / (domain_canvas.width / 2) - 1; 232 | var im = 1 - j / (domain_canvas.width / 2); 233 | domain_ctx.fillStyle = get_color({re:re, im:im}); 234 | domain_ctx.fillRect(i, j, 1, 1); 235 | } 236 | } 237 | } 238 | 239 | 240 | 241 | 242 | 243 | guppy_input.engine.add_symbol("conj", {"output": {"latex":"\\overline{{$1}}", "text":"conj($1)"}, "attrs": { "type":"conj", "group":"function"}}); 244 | guppy_input.engine.add_symbol("Re", {"output": {"latex":"\\text{Re}({$1})", "text":"Re($1)"}, "attrs": { "type":"Re", "group":"function"}}); 245 | guppy_input.engine.add_symbol("Im", {"output": {"latex":"\\text{Im}({$1})", "text":"Re($1)"}, "attrs": { "type":"Im", "group":"function"}}); 246 | guppy_input.engine.set_content(fractions); 247 | guppy_input.engine.end(); 248 | guppy_input.render(true); 249 | 250 | 251 | get_settings(); 252 | preview_domain(); 253 | 254 | var radios = document.getElementsByName('scheme'); 255 | for (var i = 0; i < radios.length; i++) { 256 | radios[i].onclick = function() { 257 | set_scheme(this.value); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /js/scheme_functions.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function get_color(p) { 4 | return scheme(p, resolution); 5 | } 6 | 7 | 8 | 9 | // p is a point in C, and r is a resolution measure (sometimes not needed) 10 | 11 | 12 | function phase(p, r) { 13 | // get polar coord. 14 | // theta = Hue 15 | var theta = Math.atan(p.im/p.re) + 2 * Math.PI; 16 | if (p.re < 0) { 17 | theta += Math.PI; 18 | } 19 | theta *= 180 / Math.PI; 20 | const hue = theta % 360; 21 | 22 | return 'hsl(' + hue + ',100%, 50%)' 23 | } 24 | 25 | function standard_domain_coloring(p, r) { 26 | // get polar coord. 27 | // theta = Hue 28 | var theta = Math.atan(p.im/p.re) + 2 * Math.PI; 29 | if (p.re < 0) { 30 | theta += Math.PI; 31 | } 32 | theta *= 180 / Math.PI; 33 | const hue = theta % 360; 34 | 35 | const mod = Math.sqrt(p.re**2 + p.im**2); 36 | // const l = (2/Math.PI) * Math.atan(mod); 37 | const a = .4; 38 | const l = (mod**a)/(mod**a + 1); 39 | // const l = 1 - (0.5)**(mod) 40 | return 'hsl(' + hue + ',100%, ' + l * 100 + '%)' 41 | } 42 | 43 | 44 | function phase_and_modulus(p, r) { 45 | // get polar coord. 46 | // theta = Hue 47 | // mod = saturation 48 | var theta = Math.atan(p.im/p.re) + 2 * Math.PI; 49 | if (p.re < 0) { 50 | theta += Math.PI; 51 | } 52 | theta *= 180 / Math.PI; 53 | const hue = theta % 360; 54 | 55 | const mod = Math.sqrt(p.re**2 + p.im**2); 56 | const l = (1/(((Math.log(mod)/Math.log(r/16)) % 1)**2 + .085)) + 38; 57 | 58 | return 'hsl(' + hue + ',100%, ' + l + '%)' 59 | } 60 | 61 | 62 | function cartesian_checkerboard(p, r) { 63 | return (Math.floor(p.re * r) + Math.ceil(p.im * r)) % 2 == 0 ? "black" : "white"; 64 | } 65 | 66 | function real_stripes(p, r) { 67 | return Math.floor(p.re * r) % 2 == 0 ? "black" : "white"; 68 | } 69 | 70 | function imaginary_stripes(p, r) { 71 | return Math.ceil(p.im * r) % 2 == 0 ? "black" : "white"; 72 | } 73 | 74 | 75 | function alternating_modulus(p, r) { 76 | const mod = Math.sqrt(p.re**2 + p.im**2); 77 | const f = Math.floor(Math.log(mod)/Math.log(r/16)); 78 | return f % 2 == 0 ? "white" : "black"; 79 | } 80 | 81 | function alternating_phase(p, r) { 82 | var theta = Math.atan(p.im/p.re) + 2 * Math.PI; 83 | if (p.re < 0) { 84 | theta += Math.PI; 85 | } 86 | const sect = Math.PI/r; 87 | return Math.ceil(theta / sect) % 2 == 0 ? "white" : "black"; 88 | } 89 | 90 | function polar_chessboard(p, r) { 91 | if (alternating_phase(p,r) != alternating_modulus(p, r)) { 92 | return "white"; 93 | } 94 | return "black"; 95 | } 96 | 97 | 98 | 99 | 100 | 101 | 102 | // relies on vars in engine.js 103 | 104 | function proper_mod(a, m) { 105 | return (((a % m) + m) % m); 106 | } 107 | 108 | function image(p, r) { 109 | 110 | // i and j are the pixel coordinates 111 | var i = Math.round(p.re * r**3) + image_center_x; 112 | var j = image_center_y - Math.round(p.im * r ** 3); 113 | 114 | if (image_tiling) { 115 | i = proper_mod(i, image_width); 116 | j = proper_mod(j, image_height); 117 | } else if (!image_obj || i >= image_width || i < 0 || j >= image_height || j < 0) { 118 | return "black"; 119 | } 120 | 121 | const red = image_data.data[j * image_width * 4 + i * 4]; 122 | const green = image_data.data[j * image_width * 4 + i * 4 + 1]; 123 | const blue = image_data.data[j * image_width * 4 + i * 4 + 2]; 124 | const alpha = image_data.data[j * image_width * 4 + i * 4 + 3]; 125 | 126 | return "rgba(" + red + "," + green + "," + blue + "," + alpha + ")"; 127 | 128 | } 129 | -------------------------------------------------------------------------------- /latinmodernmath.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterEFrancis/complex-function-plot/a73504eeb2bbe624b2da699d038dddd0070fb9fb/latinmodernmath.otf -------------------------------------------------------------------------------- /plotter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Complex Function Plot 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 | 49 | 50 | 57 | 58 |
59 |
60 |
61 | 68 |
69 | \(f(z)=\) 70 |
71 |
72 |

\(\LaTeX\) input not working? Use plain text.

73 | 74 |
75 |
76 |
77 |
78 | Number of iterations 79 | 80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 | \(\leq \text{Re}(z)\leq\) 88 | 89 |
90 |
91 |
92 |
93 | 94 | \(\leq \text{Im}(z)\leq\) 95 | 96 |
97 |
98 |
99 |
100 | Discretize with 101 | 102 | \(\times\) 103 | 104 | pixels 105 |
106 |
107 |
108 |

Domain Color Schemes

109 | 110 |
111 | 112 |
113 | 114 |
115 | 116 |
117 | 118 |
119 | 120 |
121 | 122 |
123 | 124 |
125 | 126 |
127 | 128 |
129 | 130 |
131 |
132 |
133 | 134 |
135 |
136 | 137 | 138 | 139 |
140 |
141 | 142 |

 

143 | 144 |
145 |
146 | 147 |

 

148 | 149 |
150 |
151 | 152 | 160 | 161 |



162 |
163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Complex Function Plot: Redirecting... 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 |

39 | 40 |

41 |

Sorry, but this page doesn't work on your device!

42 |

You can try on a different device, and you can also head back to the homepage to chcek out some examples.

43 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --lightblue: rgb(73, 140, 160); 3 | --green: rgb(0, 86, 32); 4 | --darkblue: rgb(30, 62, 91) 5 | } 6 | 7 | @import url('https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap'); 8 | 9 | @font-face { 10 | font-family: "Latin Modern Math"; 11 | src: url("latinmodernmath.otf"); 12 | } 13 | 14 | body { 15 | font-family: 'Do Hyeon', sans-serif; 16 | } 17 | 18 | br { 19 | user-select: none; 20 | } 21 | 22 | 23 | .btn-primary { 24 | background-color: var(--darkblue); 25 | transition: 0.5s ease; 26 | 27 | } 28 | 29 | .btn-primary:hover { 30 | transition: 0.5s ease; 31 | } 32 | 33 | 34 | p, li { 35 | font-size: 14pt; 36 | } 37 | 38 | 39 | #output { 40 | border: 1px solid black; 41 | max-height: 1000px; 42 | max-width: 100%; 43 | min-width: 50%; 44 | } 45 | 46 | 47 | #header { 48 | background-image: url(img/header.png); 49 | color: white;; 50 | } 51 | 52 | #gallery-table { 53 | width: 100%; 54 | } 55 | 56 | 57 | .math-lg { 58 | margin-top: 40%; 59 | margin-bottom: 40%; 60 | } 61 | --------------------------------------------------------------------------------