-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.html
More file actions
289 lines (279 loc) · 15 KB
/
Copy pathindex.html
File metadata and controls
289 lines (279 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style/normalize.css" />
<link rel="stylesheet" href="style/main.css" />
<link rel="stylesheet" href="style/tutorial.css"/>
<link rel="manifest" href="SimplyComplex.webmanifest" />
<link rel="icon" type="image/x-icon" href="icons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
<link rel="manifest" href="site.webmanifest" />
<title>SimplyComplex</title>
<script type="module" src="js/luajs.mjs"></script>
<!-- <script type="text/javascript"> -->
<!-- if (typeof WebAssembly === "object") { -->
<!-- WebAssembly.instantiateStreaming(fetch("js/luajs.wasm")).then( -->
<!-- (results) => { -->
<!-- console.log(results, LuaJS); -->
<!-- } -->
<!-- ); -->
<!-- } -->
<!-- </script> -->
</head>
<body>
<script type="module" defer>
const LuaJS = {};
import emscriptenInit from "./js/luajs.mjs";
await emscriptenInit(LuaJS);
LuaJS.newState().then(async (L) => {
await L.enableLuaScriptTags(document);
});
</script>
<header>
<h1>Simply<math display="inline"><mi>ℂ</mi></math>omplex</h1>
</header>
<main>
<div id="app">
<div id="boards">
<div class="board-container">
<div id="toolbar" class="toolbar">
<toolcool-color-picker color="#a0a0f0" button-width="2rem" button-height="2rem" id="strokeColor"></toolcool-color-picker>
<div id="penSizeButtons">
<button class="toolbar-button darken-on-hover" id="smallPen" value="3"><canvas></canvas></button>
<button class="toolbar-button darken-on-hover" id="medPen" value="5"><canvas></canvas></button>
<button class="toolbar-button darken-on-hover" id="largePen" value="8"><canvas></canvas></button>
</div>
<button id="clear" class="toolbar-button darken-on-hover"></button>
<button id="undo" class="toolbar-button darken-on-hover"></button>
</div>
<canvas id="inputBoard"></canvas>
</div>
<div id="arrowParent">
<form id="funcForm">
<label for="func">𝑓(𝑧) = </label>
<div id="textFieldPlusErrorMessage">
<input type="text" name="func" id="func">
<span id="errorText"></span>
</div>
</form>
<div id="arrow">
<canvas id="arrowCanvas"></canvas>
</div>
</div>
<div class="board-container">
<div id="outputToolbar" class="toolbar">
<button id="dummyButtonSoCanvasesAlign" class="toolbar-button hidden"></button>
</div>
<canvas id="outputBoard"></canvas>
</div>
</div>
</div>
<!-- TODO: add loading animation with dynamic SVG of a Limaçon -->
<script type="text/javascript" src="js/resize-app-canvases.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toolcool-color-picker/dist/toolcool-color-picker.min.js"></script>
<script type="text/lua" src="lua/ui.lua" async></script>
<script type="text/lua" src="lua/arrow.lua" async></script>
<article id="tutorial">
<!-- TODO: video tutorial -->
<h2>Tutorial</h2>
<section id="whatAmILookingAt">
<h3>What am I even looking at?</h3>
<p>
The axes above represent two copies of the complex plane.
Both are linked by a function typed in the center text field:
the first axes can be drawn on and is used as input, while the
output is represented in the second one. But wait, you might say,
how can squiggles be used as inputs to a complex function?
I thought functions operated on numbers… or whatever complex numbers even are.
</p>
<p>
The rest of the tutorial is here to explain <em>why</em> and <em>how</em> squiggles
in the first axes are being mapped to squiggles in the second axes.
</p>
</section>
<section id="whyDoThis">
<h3>Why would anyone make this?</h3>
<p>
Complex function visualization is… well… complex. "Regular"
<math display="inline">
<mi>ℝ</mi>
<mo>→</mo>
<mi>ℝ</mi>
</math>
functions
are easy: just graph the output against the input. 2 dimensions, easily fits in our 3‑dimensional
world. Even
<math display="inline">
<msup>
<mi>ℝ</mi>
<mn>2</mn>
</msup>
<mo>→</mo>
<mi>ℝ</mi>
</math>
and
<math display="inline">
<mi>ℝ</mi>
<mo>→</mo>
<msup>
<mi>ℝ</mi>
<mn>2</mn>
</msup>
</math>
functions can be visualized like this.
</p>
<p>
The complex plane, however, is 2‑dimensional itself. If we wanted to graph output against input,
we'd need 4 dimensions, so we have to be more creative with visualization. One well estabilished
technique is <a href="https://en.wikipedia.org/wiki/Domain_coloring" target="_blank" title="Domain coloring Wikipedia article">domain coloring</a>,
where you have a 2‑dimensional output image whose pixels are mapped to points in the complex plane.
The color of each pixel is then determined by the function value at that pixel's corresponding complex point,
with the hue being mapped to the function value's principal argument, and color intensity (or "value") being mapped
to its absolute value.
</p>
<p>
While domain coloring is useful, it has its limitations. For one, people with colorblindness
can have some difficulty distinguishing regions in the image. For another, it's difficult to
see structures that aren't centered around the origin. Sure, you can see if a path contains all
hues and therefore makes a loop around the origin, but try to determine whether a path makes a
loop around
<math display="inline">
<mn>1</mn>
<mo>+</mo>
<mi>i</mi>
</math>
and you're in for a very bad time.
</p>
<p>
With the limitations of domain coloring, I sought a different way to visualize what a function
does to its inputs. And this is when Welch Labs comes in.
</p>
</section>
<section id="howDoesItWork">
<h3>How does it work?</h3>
<p>
This project uses a different approach than domain coloring. I first saw this type of visualization in the following Welch Labs YouTube video.
</p>
<div class="centered-youtube-vid" id="welchlabsYoutubeEmbed">
</div>
<p>
Pixels in the input plane are mapped to complex numbers, just like in domain coloring, but instead of using
fixed colors for every output pixel, we use colors defined in the input plane. So if a red pixel at
<math display="inline">
<mn>1</mn>
<mo>+</mo>
<mi>i</mi>
</math>
gets mapped to
<math display="inline">
<mn>2</mn>
<mi>i</mi>
</math>,
the point
<math display="inline">
<mn>2</mn>
<mi>i</mi>
</math>
in the output plane will be colored red.
</p>
<p>
This process distorts the input image in a unique way depending on the chosen function.
The whole video (really, the whole series) is worth a watch, even if you are already very familiar
with complex numbers.
</p>
<p>
Instead of pixels, however, Simply Complex uses paths as primitves: every squiggle you draw on the first
axes is broken into a series of line segments, whose endpoints are mapped according to the given function.
After the mapping is complete, the new points in the output axes are connected with the same color as the
input squiggle, creating a new morphed path. There's some extra care taken for rapidly changing regions
and discontinuities, but those are special case details.
</p>
<p>
This more abstract approach allows Simply Complex to have essentially infinite resolution. While in
the pixels approach, regions with large derivative absolute values will be spread out, leaving uncolored
pixels inbetween. With paths, this is not a problem: we simply* calculate the derivative at that point
and scale the path width by its absolute value.
</p>
<p class="small-text">
*Of course, calculating the derivative of an arbitrary, user-defined function isn't exactly a simple task,
but it is possible and I have done so with my <a href="https://github.com/wqferr/SymDiff" target="_blank" title="SymDiff GitHub page">SymDiff</a>
(for "symbolic differentiation") library.
</p>
<p>
You can test the path width scaling yourself: try making a path that starts off far from the origin, and goes
towards it and eventually reaches it. If the function is set to
<math display="inline">
<mi>f</mi>
<mo>(</mo>
<ci>z</ci>
<mo>)</mo>
<mo>=</mo>
<msup>
<ci>z</ci>
<mn>2</mn>
</msup>
</math>, the output
path width will visibly decrease when it gets closer to the origin, eventually disappearing.
</p>
</section>
<section id="usage">
<h3>Usage</h3>
<p>
Internally, the function text field is evaluated in a Lua context with some variables predefined.
This means you are essentially creating a very very short piece of Lua code, that is run for
every point in the paths you draw.
</p>
<p>
The variables you are allowed to use are:
<ul class="dash-list">
<li><math display="inline"><ci>z</ci></math>: a placeholder for whichever point is currently being evaluated;</li>
<li><math display="inline"><mi>i</mi></math>: the imaginary unit;</li>
<li><math display="inline"><mi>e</mi></math>: the base of the natural logarithm;</li>
<li>pi: the circle constant;</li>
<li>tau: the superior circle constant.</li>
</ul>
</p>
<p>
The Lua environment is also equipped with several functions. They are:
<ul class="dash-list">
<li>real: real part of its argument;</li>
<li>imag: imaginary part of its argument;</li>
<li>conj: conjugate of its argument;</li>
<li>exp: exponential function, base <math display="inline"><mi>e</mi></math>;</li>
<li>ln: natural logarithm (principal branch);</li>
<li>sqrt: square root (principal value);</li>
<li>abs: absolute value;</li>
<li>arg: argument or phase;</li>
<li>sin, cos, tan: circular trigonometric functions;</li>
<!-- <li>asin, acos, atan: inverse circular trigonometric functions;</li> -->
<li>sinh, cosh, tanh: hyperbolic trigonometric functions;</li>
<!-- <li>arsinh, arcosh, artanh: inverse hyperbolic trigonometric functions.</li> -->
</ul>
</p>
</section>
</article>
<script type="text/javascript" src="js/embed-youtube-on-scroll.js" async></script>
<footer>
<div id="logos">
<img id="poweredByLua" alt="Powered by Lua" title="Powered by Lua" src="img/powered-by-lua.png" />
<a href="https://github.com/wqferr/simplycomplex" title="Hosted on GitHub"><img id="gitHubMark" alt="Hosted on GitHub" title="Hosted on GitHub" src="svg/github-mark.svg" /></a>
</div>
<ul id="credits">
<li>SimplyComplex v1.1.0 © 2024 <a href="https://github.com/wqferr" target="_blank" title="Check out my other projects on GitHub">William Quelho Ferreira</a> <span class="credits-mdash">—</span> wqferr@gmail.com</li>
<li>Toolbar UX by <a href="https://linktr.ee/anayagf" target="_blank" title="Check out her portfolio on linktr.ee">Anayã Gimenes Ferreira</a> <span class="credits-mdash">—</span> anayagimenes@gmail.com</li>
<li>
Code help and rubber duckying by
<a href="https://github.com/gwenthekween" target="_blank" title="Check out her work on GitHub">Guinevere Larsen</a><!--
--><a href="https://en.wikipedia.org/wiki/Serial_comma" target="_blank">,</a> and
<a href="https://github.com/JJSax" target="_blank" title="Check out his work on GitHub">Jared Augerot</a>
</li>
<li>JavaScript Lua VM by <a href="https://github.com/Doridian/LuaJS" target="_blank" title="Check out their work on GitHub">Doridian's LuaJS</a></li>
</ul>
</footer>
</main>
</body>
</html>