Domkol is a Javascript library I wrote to visualize complex functions in a web browser.
The basic problem with visualizing a complex function is that the graph of a complex function exists in a 2-dimensional complex space, which is equivalent to a four dimensional real number space.
Whereas, one's computer screen only has two dimensions.
One way to solve the problem of not enough dimensions is to use a two-dimensional colour space to represent the value of a function f, where the complex domain is represented by the two dimensional screen, and the value of f(z) is represented by the colour value at the position representing z. This is called domain colouring.
One problem with domain colouring is that it does not give the viewer a strong feel for the shape of a function. We can distinguish different colours, and we can perceive graduations in colour, but it is hard to get a good feel for the rate at which colour is changing at any particular location in the domain.
On the other hand, it is relatively easy to show the shape of a function from a one dimensional space to another one-dimensional space. It is even easy to show a function from a one dimensional space to a two-dimensional space if one represents the graph as two separate graphs, one for each component dimension of the range (i.e. the target space).
So, I hit on the idea of showing the value of a complex function on a one-dimensional subset of the complex domain, with the intention that the user could interatively move the one-dimensional subset around.
I could have chosen a straight line as my subset. But there are some interesting theorems in complex analysis about the values of a complex function on, inside or outside a closed curve, so I decided it would be interesting to choose a circle. (To be precise, the circumference of a circle. Also, one could easily have the option of either a line or a circle, but so far I have only implemented a circle.)
I decided to program the visualization so that there was an initial circle showing the graph of f over the circle, and then the user could choose to manipulate the circle by moving it around and by changing its radius.
To display the value of the function f on the circle, it would be necessary to show each of the real and imaginary components of the value of f as a graph that somehow related to the circle.
The simplest way to do this is to use a form of polar coordinates where the angle coordinate corresponds to the location of z on the circle, and the radial coordinate corresponds to the real or imaginary component of f(z), where zero is on the circle, positive is outside the circle and negative is inside the circle.
There is a problem with this method of displaying the graph when overly negative values want to be a negative distance from the centre of the circle, and they "come out the other side". But this can be mitigated by providing an option to scale the graph size.
To emphasize that polar coordinates are being used, a polar coordinate grid should also be shown (and this adjusts accordingly when the graph is scaled).
I wrote Javascript code to make all this happen, but I wasn't completedly satisfied with the result. A consequence of the residue theorem is that the number of phase rotations of f(z) as z goes round the circle is equal to the number of simple zeroes inside the circle. You can sort of see this looking at two separate graphs for the real and imaginary components of f(z), but it requires a certain amount of thinking to interpret the in and out motion of the two graphs.
I realised it would be much easier to see the phase rotation if I combined the two component graphs into a single three-dimensional graph where, for example, the real component of f(z) is represented by radial distance, as before, and the imaginary component is represented by height above the screen.
Now I was back to not having enough dimensions. I needed at least one more dimension than the two dimensions of the screen. Did I need to rewrite everything from scratch as a WebGL 3D application? Or, could I get away with some kind of "poor man's 3D"?
Inspired by the example of "3D-wiggle" GIF files, I decided to see what I could do.
So, I added some Javascript animation to my page script, and I set it to alternate between two views of the intended 3D graph (and without particularly altering the accompanying domain colouring bitmap).
Unfortunately the result was not very convincing. There was movement, but I could not really "see" the 3D the way it was meant to be.
I tried a number of 3D hints, including rendering the path as a dashed path, to emphasize the actual direction of motion, and shadows, to give a sense of height.
In the end I found one simple but very critical hint, which is to correctly display whether or not the graph of f was going under or over the circle, and to thicken both the circle and the graph line to emphasize which was on top of the other.
To enable this, it was necessary to split the graph of f(z), represented as an SVG <path> element, into two distinct <path> elements – one for the "over" part of the graph, and one for the "under" part. Once that is done, CSS "z-index" ordering is all that is required to display the "over" and "under" correctly.
Also, once the path was split into two parts it was easy use opacity to place the "over" and "under" graphs over and under the HTML canvas element displaying the domain colouring, and set the canvas to be slightly transparent – not enough to make the domain colouring too faded, but enough to see the "under" part of the graph as if looking at it through a murky piece of coloured glass.
So, finally, I made the 3D graph look 3D using these mechanisms:
The original intention with the "wiggling" was to wiggle the viewpoint, as typically happens with wiggling GIF's, however what I did in practice was wiggle the graph itself (at least that is what you perceive when you look at it), so that it looks like the graph is moving, but the domain coloured canvas is still.
I also programmed the wiggle to be a harmonic vibration back and forth, as opposed to a wiggle between two positions, or a back-and-forth motion at constant speed in both directions. A harmonic vibration feels smoother and more natural when you watch it, presumably because it is a more physically plausible motion.
Although the 3D view of the graph of f on the circle looks cool, there is slightly greater precision in the 2D view. To allow for maximum user choice, Domkol incorporates several options for controlling the display of the value of f on the circle:
Using Domkol to explore just one complex function gets a bit boring after a while. More interesting is to be able to explore different functions. Even more interesting is the ability to interactively alter the complex function currently being explored.
For a complex polynomial, such as f(z) = z3, a natural interaction is to be able to alter the zeroes of the polynomial. It is particularly instructive to observe what happens when zeroes are moved inside or outside of the circle on which the graph of f is displayed.
This interaction can be achieved by creating some draggable "handles", one for each zero of the polynomial.
We can rewrite the function z3 = (z-a)(z-b)(z-c), where a, b and c are the zeroes of the polynomial, initially all set to 0. Represented as draggable handles, initially the zeroes all sit on top of each other at the origin (I use a small circular dot next to a label to pinpoint the precise location of each zero), and they can be dragged one by one to be somewhere else.
Domkol has two external library dependencies:
These were the latest stable versions as at the time of writing Domkol (since then, JQuery UI has moved on to 1.10.3).
In JQuery UI, sliders are configured so that page up and page down keys move 20% of the slider length, i.e. there are 5 "pages" from one end of the slider to the other. This value is hard-coded, and there is no option to change it.
For the "rotate function" slider, I found that it would be best to have 4 "pages" in the slider, as these could then naturally correspond to 90 degrees of rotation per page, with the centre point being 0 degrees of rotation.
So I have included (in my web page examples) an altered version of JQuery UI which changes the hard-coded 5 pages to 4 pages.
An ideal solution would be to add this as a configurable option to JQuery UI sliders, and then make a pull request to have this added into the JQuery UI. But I have not done this.
There is no major consequence to using the standard un-altered JQuery UI – one just loses the ability to do easy 90 degree rotations.
As an application in a web page, Domkol has two major components:
As DOM components, the control dialog is constructed in HTML, and there isn't too much to say about that. Except I will say that if you want to achieve a tabular style layout without actually using <table>, then the CSS property display:inline-block is your friend, because you can have a <div> with block-like display properties inside the div, but inline-style layout relative to its siblings in the parent HTML element that contains it.
The complex plane includes the following functional components:
These functional components are implemented within the following DOM elements:
The first working version of Domkol consisted of a mixture of static HTML, SVG, CSS and Javascript.
It worked like it was meant to, but I realised that if I wanted to make the Javascript reuseable as a library, most of what was in the static HTML and SVG had to be moved into the Javascript.
Which means I had to write Javascript to create the HTML and the SVG elements.
There are three ways that come to mind to create DOM elements in Javascript:
I was already depending on JQuery, but I didn't want to add any more unnecessary dependencies, so I decided to see how far I could get just using JQuery to construct and populate the DOM elements.
As it happens, JQuery doesn't actually do construction of SVG elements, so I had to use plain Javascript for that, with the help of a convenience createSvgElement function.
Domkol uses two different graphical features of HTML5: Canvas and SVG.
When you look at the web markup, both Canvas and SVG look like they are fully part of HTML, in the sense that they use plain tags <canvas> and <svg>. In particular, for SVG, there is no XML namespace.
However, this is partly an illusion, as actually SVG elements really are embedded XML elements with their own special namespace. It's just that HTML5 hides this detail from the user. Also SVG elements are somewhat different from HTML DOM elements, for example they do not support innerHTML, so JQuery methods that use innerHTML to construct or edit DOM elements just don't work with SVG.
Also, the JQuery UI draggable method does not work on SVG elements, because it depends on attributes which SVG elements do not respond to (or what is actually worse, they may or may not respond to them, depending on the browser).
So a few work-arounds were required to get SVG to work with JQuery and JQuery UI, some of them which I found by Googling and surfing StackOverflow. And for cases where one wants to apply draggable to an SVG <g> element, for example with the "number handles" that consist of a small circular dot grouped together with a text label, I could not find a way to get draggable to work cross-browser, so I gave up and reimplemented the number handles in pure HTML, as separate <div> elements.
Javascript does not have any built-in representation of complex numbers. It is possible to define a class Complex with properties real and imaginary. However this creates a lot of overhead for each complex number.
A more lightweight approach, which I have used in Domkol, is to represent a complex number as an array of two real numbers, so that z = x + iy is represented by the Javascript array [x,y]. Even this approach creates overhead because there will be constant creation and garbage collection of two element arrays, but one would expect that to be much cheaper than creating full Javascript objects (ideally I should do some performance testing to verify that assumption).
If I felt the need to optimize further, I would consider using parallel arrays to represent arrays of complex numbers, so that for example, z1, z2, z3 = x1+y1, x2+y2, x3+y3 would be represented by [[x1,x2,x3],[y1,y2,y3]]. Using parallel arrays would be more efficient, for example creating an array of 1000 complex numbers would require the creation of three Javascript arrays, i.e. an array of 2 arrays, rather than one array of 1000 arrays, which is 1001 Javascript arrays altogether.
Another more exciting optimisation possibility is asm.js. This is a new technology currently only available in "Firefox nightly", but it is a technology which promises to allow Javascript code doing a lot of arithmetic to run at a speed not that many times slower than "native" speed. (As I understand it, you would have to write the code you want optimised according to the special rules of asm.js, and all your data arrays would have to be constructed as strongly typed data arrays, but you could separate the code that needs to be optimized from the rest of the code, and have the rest of your code call the optimised code just like it was calling any other Javascript code.)
In the implementation of Domkol I also used arrays to represent other pairs and triples of numbers that represent 2D and 3D values, such as:
ControlDialogElements is responsible for the construction of the DOM tree representing the control dialog. It does not implement the functionality of the control dialog, however it does save references to those elements to which functionality will be attached. These references are saved as the following properties on the object: div (the top-level div), windowTopBar, formulaText, functionInstructionsText, functionScaleSlider, functionScaleText, colourScaleSlider,colourScaleText, showCircleGraphCheckbox, show3DGraphCheckbox, wiggleCheckbox, rotateGraphSlider, graphRotationText, showCoordinateGridCheckbox and repaintContinuouslyCheckbox. The constructor function for ControlDialogElements takes one argument which is the pre-existing <div> element that the control dialog DOM tree will be inserted into.
ControlDialog represents the active functionality of the control dialog. Its constructor function has one argument which is an object with the DOM element properties that ControlDialogElements has. Configuration of ControlDialog occurs by invoking two major methods. First there is initialize which is called in the constructor. initialize calls "initialize" methods for each individual component that requires initialization. The "initialize" method for each control does two things – it finishes constructing the control, and it saves initial values represented by the initial states of the controls. For example, for each slider, the "initialize" method constructs the actual JQueryUI slider, and it configures the slider to trigger a corresponding event when the slider is moved. To give a specific example, the graph rotation slider is configured to trigger "graphRotationChanged" events. The initial value for the graph rotation (based on the initial state of the slider) is saved into the graphRotation property of the ControlDialog.values object.
The second major method of ControlDialog is the connect method, which is used to connect the control dialog to the ComplexFunctionExplorerView object (which I have not yet described). This calls individual "connect" methods to connect individual components to the corresponding components of the ComplexFunctionExplorerView object.
ExplorerViewElements is similar to ControlDialogElements in that it is responsible for constructing a DOM tree and saving references to the elements providing functionality, in this case for the "explorer view" (or "complex function explorer view") which consists of the view into the complex function consisting of the domain colouring, the complex plane coordinate grid, the graph of the function on the circle, and optionally, any number handles that may be used to manipulate the function. The constructor function for ControlDialogElements takes four arguments:
The supplied dimensions do not fully define the view, in particular the "pixels per unit" value has not yet been specified. However these are all the dimensions required to initially construct the DOM tree.
There is an initialize method, which calls four "initialize" methods which construct the four major components of the DOM tree, that is: the canvas, the SVG element for the "under" part of the 3D path, the SVG element for everything else "above" the canvas, and the <div> element which will contain any number handles that are created. References to elements saved as properties are canvas, realPathUnder, coordinates, coordinatesGroup, axes, unitCoordinateGrid, fineCoordinateGrid, circleGraph, bigCircle, polarGrid, polarGridCoarse, realPath, realPathShadow, realPathShadow2, imaginaryPath, centreHandle, edgeHandle and handlesDiv.
(Note: because the explorer view is mostly SVG elements, whereas the control dialog is entirely HTML, and JQuery works well with HTML but not so well with SVG, I have adopted different conventions for storing element references in ControlDialogElements and ExplorerViewElements – in ControlDialogElements the references are all JQuery wrappers, whereas in ExplorerViewElements the references are all actual DOM objects.)
DomainCircle represents the model of the circle in the domain for which the value of the function f will be displayed as two 2D graphs or one 3D graph. The model includes the actual location of the circle, as well as other attributes that affect the calculation and display of the function graph.
Attributes of the domain circle include:
ComplexFunctionExplorerModel represents a model of the state of exploration of the complex function.
Its attributes are:
The ComplexFunctionExplorerModel object contains all the information required to render the view of the complex function, including the domain colouring and the circle graph, but the view is itself defined by view objects which contain the references to the required DOM elements, as well as references to the model objects.
DomainCircleView is the view object for the circle graph. It includes attributes for the DOM elements representing the circle, the centre and edge handles, the polar grid, and all components of the 2D and 3D graphs (both "over" and "under"). It is responsible for managing the user's interaction with the centre and edge handles.
ComplexFunction is an object that represents the complex function being visualized. It contains the function itself, and an attribute for the function formula. It has methods to trigger "formulaChanged" and "functionChanged" events for the benefit of objects that need to update themselves when the function is changed interactively by the user.
PolynomialFunction is a specialization of ComplexFunction that defines a polynomial function in terms of an array of zeroes, which supports interaction where the function can be changed by changing the values of the zeroes.
CoordinatesView represents the real and imaginary axes and coordinate grid of the complex plane, with associated labels.
ComplexNumberHandle represents a draggable "number handle", which allows the user to control the value of a particular number by dragging its handle around the complex plane. This is currently used for exploring polynomial functions by allowing the user the drag the polynomial zeroes around. But it could be used to control parameters for any type of complex function.
It is constructed from four parameters:
When the number handle is dragged, it triggers "numberChanged" events with three additional parameters:
ComplexFunctionExplorerView is the main view object representing the view of the function in the complex plane, including the domain colouring, the circle graph, the coordinate axes and grid, and any number handles.
createExplorerView is the top-level function which should be called from within a web page in order to create the ComplexFunctionExplorerView object.
It takes seven parameters, which are:
Note that pixel locations follow the convention that [0,0] is the left-most and top-most point. It follows that the "x" coordinate in pixel coordinates increases when the real component of the complex number increases, whereas the "y" coordinate in pixel coordinates decreases when the imaginary component of the complex number increases.
In Javascript there are two basic ways of connecting objects.
The first is where object A wants to tell object B what to do. In this case a method call is sufficient:
A={}; B={}; B.doSomething = function() { alert("B is doing something"); }; A.makeBDoSomething = function() { B.doSomething(); }; A.makeBDoSomething();
The second case is where object A wants object B to tell it (object A) to do something. This is a bit more indirect, because the object initiating the connection (object A) is not the object initiating the action (object B).
But there is a way to do this based on events, and as it happens, Javascript provides event handling as a basic feature of the language.
Actually, creating your own events (as opposed to builtin browser DOM events) is a little tricky in Javascript, so I'll give an example of how to do it in JQuery. Like this:
A={}; B={}; A.doSomething = function(stuff) { alert("A is doing some stuff: " + stuff); }; $(B).on("peopleNeedToDoStuff", function(event, stuff) { A.doSomething(stuff); }); B.getStuffDone = function() { $(B).trigger("peopleNeedToDoStuff", ["wash the dishes"]); }; B.getStuffDone();
Here it is B which initiates the action, when it triggers the "peopleNeedToDoStuff" event, but it is A which initiates the connection, i.e. A decides that it should let B tell it (A) what to do.
We could do the first example using events, however in that case it would not make the code particularly easier to read or maintain, so I wouldn't do it that way myself. (I have seen code where someone has taken a "do everything in events" approach, and one ends up with code that's harder to read, and it can degenerate into incomprehensible and overly verbose "event spaghetti".)
There is a simple rule for deciding between events and direct method calls in Model/View architectures. Which is that if the model is not allowed to "know" about the view, then any time that the model needs to invoke an action on the view, the invocation has to occur via an event. That is, the model triggers the event, but the view initiates the connection which causes it to be registered as a handler for the model's event.
Conversely, if the view needs to invoke an action on the model, a simple method call will probably suffice.
One can also understand event handling in terms of "what", "how" and "who". A basic feature of object-oriented programming, or even procedural programming, is that when you invoke a method or function, the caller is specifying what to do, but the callee is the one that knows how to do it. When events are used to manage the flow of control, the caller knows what is to be done, but it doesn't even know (at least not in advance) who is going to do it. Also, the caller doesn't even have to know if anyone is going to do it, and, in some cases, there may be more than one "who".
There are five different sources of custom events in Domkol, which are:
When you create a Javascript library which can be called from other Javascript files, you have to put at least one object into a global variable of the web page being executed.
It is considered good practice not to create more than one such global value per library, and this value should represent the library itself.
This library object should be populated with properties representing the high-level classes and functions which you intend the user of your library to call.
There are various ways to do this. For Domkol I used the relatively straightforward approach of:
var DOMKOL = {}; (function(lib) { ... lots of internal definitions // exports lib.ControlDialogElements = ControlDialogElements; lib.ControlDialog = ControlDialog; lib.PolynomialFunction = PolynomialFunction; lib.ComplexFunction = ComplexFunction; lib.createExplorerView = createExplorerView; })(DOMKOL);
Here DOMKOL is the global variable holding the library object, and ControlDialogElements, ControlDialog, PolynomialFunction, ComplexFunction and createExplorerView are the top-level classes and functions that I wish to export.
(In the actual domkol.js I export various other definitions just in case the user wants greater access to the internals of the library. You can export a whole lot of stuff from a library if you want to – the overheads of doing so are minimal, and it's between you and your library's users whether you are giving them enough rope to hang themselves.)
Examples of how to configure Domkol can be found in the Javascript files included in the example pages for cubic polynomial, quintic polynomial and exponential function.
The basic steps required are:
And that's it.
The easiest way to get your own example working using domkol.js is probably to cut and paste one of the existing examples, and then adjust accordingly (that's how I'd do it).