viernes, 25 de noviembre de 2016

Dibujar funciones en un canvas javascript


 
 
 
 

Abstract

Interactive math tutorials, often called mathlets, are designed to provide a more visceral learning experience than traditional textbook methods and to enhance intuitive understanding of complex ideas by allowing users to alter parameters that influence visual scenes. We describe methods for creating such tutorials using the HTML5 canvas element. First, we discuss some motivations for writing such mathlets, then walk-through the process of creating a mathlet with canvas. Then, we compare canvas to alternatives, explaining our decision to use it, and provide links to other demonstrations and resources.

Keywords

  • Javascript
  • html5
  • canvas
  • tutorial
  • mathlet

1. Preliminaries

In this paper, we describe a method that can be used for developing interactive, web-based math tutorials using HTML, Javascript and the new HTML5 canvas element.
We have previously developed a series of interactive web modules describing the basic mathematical ideas behind Metric Pattern Theory. Developed by three undergraduate interns with strong mathematical skills, the modules were targeted at undergraduates from under-represented communities including the hearing impaired as well as non-mathematicians such as neuroscientists and clinicians.
The modules were developed using Mathwright and could be viewed offline using Internet Explorer with publicly available ActiveX controls. From 1995 to the untimely death of its developer in 2004, Mathwright was at the forefront of web-based mathematical pedagogy (White, 2002, 2004; Hare, 1997; Kalman, 1999). Paraphrasing White, the philosophy was to invite the interested reader to come into the world of mathematics and science through structured microworlds that allowed them to ask their own questions, to read at their own pace, and to experiment and play with those topics that interest them.
Now the workbooks are being updated and made portable across multiple platforms forming the basis of an online course on "An Interactive Introduction to Computational Medicine", the goal being to create interactive mathematical tutorials on Metric Pattern Theory to supplement in-class learning with a more visceral and intuitive understanding of complex material. Metric Pattern Theory forms the foundation of the emerging discipline of Computational Anatomy, which takes the view of anatomy as the orbit of images under the orbits of group actions of diffeomorphisms. Using these techniques, one can analyze shape and images of hearts, brain structures (hippocampus, planum temporale, etc.) and theoretically any anatomical shape, and compare differing images to find the "distance" between the two. This can be used to potentially diagnose or locate assorted illnesses and disorders.
In this paper we focus on using the canvas element for a variety of reasons which will be outlined in section 3. First, we will present an example demonstration about the 2D Osculating Circle that was developed for the workbooks. Then, we will walk through the development of this demo in a step-by-step fashion to give an idea of what it's like developing with canvas and because this demo introduces many of the key components needed for developing mathlets. Following this walk-through, we will explain why we chose to develop using canvas, compare it to other options and then make some notes about browser compatibility. Following said comparisons, we will show examples of the more powerful capabilities of canvas, including 3D rendering, and then provide links to more resources, including the actual specification and API of the element.
Note: Due to a quirk in Internet Explorer, scaled arcs currently fail to render. This affects the usefulness of the demos in this paper in IE, so it is better to use a browser such as Firefox, Safari or Chrome to read this paper. The issue at hand is a known bug with the developer's of explorercanvas and should be resolved.

2. Case Study: Osculating Circle

The osculating circle of a curve at a point is a very usefool tool for understanding curves because it has the same tangent and curvature as the curve at the respective point. Suppose we have a curve \(c\left(t\right) = \left(f\left(t\right),g\left(t\right)\right)\). The curvature of c at time t is inversely proportional to the radius of the osculating circle: \(r = \frac{1}{|\kappa\left(t\right)|}\). The center of the circle is given by the normal vector of length r anchored at the point on the curve. To calculate the x,y value of the center, we have:
\[ \begin{align} x\left(t\right) = f - \frac{\left({f^\prime}^2 + {g^\prime}^2\right)g^\prime}{f^\prime g^{\prime\prime} - f^{\prime\prime}g^\prime}\\ y\left(t\right) = g + \frac{\left({f^\prime}^2 + {g^\prime}^2\right)f^\prime}{f^\prime g^{\prime\prime} - f^{\prime\prime}g^\prime} \end{align} \] For more information, see the Osculating Circle page on MathWorld.

2.1 The Mathlet

Example 1: The Osculating Circle
To use this mathlet, grab the square slider, and move it around. The location of the square on the slider corresponds to the value on the x-axis at which the curvature is being calculated and the osculating circle drawn.
Notice how which side of the function the osculating circle is on depends on whether the curvature is positive or negative.
Current curvature at x = 3.53333: 0.031419996756543236
Below is the full source code for this demonstration. It may appear overhwelming, but the rest of this section will involve dissecting this code and explaining it piece-by-piece. This full source code is included to make it easier for one to deploy on their own and to give a sense of the overall style of a mathlet in canvas.
001.var canvas = document.getElementById("osculating");
002.var ctx = canvas.getContext("2d");
003. 
004.var scaleFactor = 50;
005. 
006.//move origin to center, make y ascend up
007.//window: -5 to 5
008.ctx.translate(canvas.width / 2, canvas.height / 2);
009.ctx.scale(scaleFactor, -scaleFactor);
010. 
011.var X_STEPS = 75;
012.var X_STEP = 10 / X_STEPS;
013.var XMIN = -5, XMAX = 5;
014.var XRANGE = XMAX - XMIN;
015. 
016.//define functions and their derivatives
017.//curve is 'parametrization' (x, f(x))
018.var f = function f(x) {
019.return 1/20 * (Math.pow(x,4) + Math.pow(x,3) - 13*Math.pow(x,2) - x);
020.};
021. 
022.var df = function df(x) {
023.return 1/20 * (4*Math.pow(x,3) + 3*Math.pow(x,2) - 26*x - 1);
024.};
025. 
026.var d2f = function d2f(x) {
027.return 1/20 * (12*Math.pow(x,2) + 6*x -26);
028.};
029. 
030.//calculate curvature at a point
031.var curvature = function curvature(x) {
032.return d2f(x) / Math.pow(1 + Math.pow(df(x),2), 1.5);
033.};
034. 
035.//variables representing current values, non-static
036.var cur_x = 0;
037.var cur_curv = curvature(cur_x);
038. 
039.function draw() {
040.$("#cur_curv").text(cur_curv); //display current curvature
041.drawAxes();
042.plotF();
043.drawPoint();
044.drawOsculating();
045.}
046. 
047.function clear() {
048.ctx.clearRect(XMIN, XMIN, XRANGE, XRANGE);
049.}
050. 
051.function drawAxes() {
052.ctx.strokeStyle = "rgba(0,0,0,.5)";
053.ctx.lineWidth = 1 / scaleFactor;
054.ctx.beginPath();
055.ctx.moveTo(XMIN,0);
056.ctx.lineTo(XMAX,0);
057.ctx.moveTo(0,XMIN); //y range is same as x range
058.ctx.lineTo(0,XMAX);
059.ctx.stroke();
060.ctx.closePath();
061.}
062. 
063.function drawPoint() {
064.var y = f(cur_x);
065.ctx.fillStyle = "#FF0000";
066.ctx.beginPath();
067.ctx.arc(cur_x, y, .1, 0, 2*Math.PI, false);
068.ctx.fill();
069.ctx.closePath();
070.}
071. 
072.function plotF() {
073.ctx.strokeStyle = "rgba(0,0,0,1)";
074.ctx.beginPath();
075.ctx.moveTo(XMIN, f(XMIN));
076.for(var x = XMIN; x <= XMAX; x += X_STEP) {
077.ctx.lineTo(x, f(x));
078.}
079.ctx.stroke();
080.ctx.closePath();
081.}
082. 
083.//for explanation of functions for center and radius of circle, see
085.function drawOsculating() {
086.var radius = 1 / Math.abs(cur_curv);
087.//cx, cy use the fact that x(t) = t in this parameterization
088.var cx = cur_x - (1 + Math.pow(df(cur_x),2))*df(cur_x)/d2f(cur_x);
089.var cy = f(cur_x) + (1 + Math.pow(df(cur_x),2))/d2f(cur_x);
090.ctx.strokeStyle = "#0000FF";
091.ctx.beginPath();
092.ctx.arc(cx, cy, radius, 0, 2*Math.PI, false);
093.ctx.stroke();
094.ctx.closePath();
095.}
096. 
097.draw();
098. 
099.$("#xslide").slider({
100.min: XMIN,
101.max: XMAX,
102.value: (XMAX + XMIN) / 2,
103.step: X_STEP,
104.animate: true,
105.slide: function(event, ui) {
106.cur_x = ui.value;
107.cur_curv = curvature(cur_x);
108.clear();
109.draw();
110.}
111.});

2.2 Setting up the HTML Document

Obviously, to use an HTML element such as canvas, there must be an HTML document in which to embed the element. To run the osculating circle demo, a bare minimum setup will look something like:
01.<html>
02.<head>
03.<title>Your Title Here</title>
04.<link type="text/css" ref="stylesheet" href="/path/to/your/style.css" /> 
05.<script type="text/javascript" src="/path/to/your/osculating.js"></script>
06. 
07.<!-- insert other stylesheets and javascripts (i.e. jQuery, which I use but is not necessary in Osculating demo) here -->
08.</head>
09.<body>
10.<div id="canv">
11.<p>Current curvature: <span id="cur_curv"></span></p>
12.<div id="xslide"></div>
13.<canvas id="osculating" width="500" height="500">
14. 
15.Sorry, your browser does not support canvas.
16.</canvas>
17.</div>
18.</body>
19.</html>
Here, the key component is the canvas element on lines 12-14. The text on line 13 is what will be displayed to users who do not have support for the element (see Section 3.2). The other elements in the code serve other functions specific to this demo:
  • <div id="canv">: This is a wrapper element for the canvas. It is convenient to use such a wrapping element because it makes positioning/styling the canvas much easier with Cascading Style Sheets (CSS) and also allows one to group other elements, such as the slider, with the canvas.
  • <span id="cur_curv">: This element is where the value of the current curvature will be displayed. I gave it an easily identifiable id both for semantic purposes and to be able to easily select it in the Javascript.
  • <div id="xslide">: Inside this div rests the slider. The slider is generated purely in Javascript (I use jQuery's slider for convenience), as explained more fully in section 2.8.

2.3 Getting the 2D Context

Now let us focus on the meat of developing a mathlet: the Javascript driver program. The first step to using a canvas element is to grab the element itself and then get a 2D rendering context. This is handled in lines 1 and 2:
1.var canvas = document.getElementById("osculating");
2.var ctx = canvas.getContext("2d");
In line 1, we store the actual canvas element, which has id "osculating", in the variable named canvas. Line 2 calls the method getContext on this element in order to give us the 2D rendering context in which the actual drawing will be done. Currently this can only be called as getContext("2d") although there are initiatives to give canvas a 3D context as well. This second line of code is the single most important line in working with canvas. The interface given by this method call provides all the drawing and coloring functions that one can use with a canvas.

2.4 Coordinate Systems and Transformations

By default, the rendering context starts with orgin (0, 0) in the top-left corner. The x-axis values increase to the right and the y-axis values increase down. While these coordinates may be convenient for computer vision, one usually wants to change to a more standard coordinate system when developing mathlets. Luckily, there are easy methods to handle this. In the osculating circle demo, the coordinate system transformation is handled by:
1.var scaleFactor = 50;
2. 
3.//move origin to center, make y ascend up
4.//window: -5 to 5
5.ctx.translate(canvas.width / 2, canvas.height / 2); //evaluates to ctx.translate(250, 250)
6.ctx.scale(scaleFactor, -scaleFactor);
Line 5 moves the origin to the center of the canvas element. Using canvas.width / 2 and canvas.height / 2 will always move the origin to the center, regardless of what specific values are set in the HTML document. Of course, one can use translate to move the coordinate system anywhere on (or off!) the canvas. Line 6 scales the coordinate system, so that every (x,y) value used in drawing functions gets mapped to (scaleFactor * x, -scaleFactor * y). The negative scale value for the y-axis creates a coordinate system where the y-axis increases up instead of down. Because the height and width of the canvas were both set at 500 in the HTML document, these two lines move the origin to the center and make the x and y axes range from -5 to 5.
Example 2: Scale and Translate
This animation shows the vectors (0, 5) and (5, 0) being drawn, first after calling translate and then after also calling scale
The scale and translate methods do not actually "move" anything, but rather are specific instances of the more general ability of canvas to perform 2D transformations via matrix multiplication. The 2D rendering context always has a current transformation matrix, the identity matrix by default. Whenever an x or y value is passed to a drawing function, it is first multiplied by this matrix and then rendered. So the ctx.translate(250, 250) call changes the current transformation matrix to the affine transformation
\[ \left[\begin{matrix} 1 & 0 & 250 \\ 0 & 1 & 250 \\ 0 & 0 & 1 \end{matrix}\right] \] The built-in transformations are translate(dx, dy), scale(sx, sy), and rotate(theta). When more than one of these are called, they are performed in reverse order. For example, since our scale transformation is represented by the matrix
\[ \left[\begin{matrix} 50 & 0 & 0 \\ 0 & -50 & 0 \\ 0 & 0 & 1 \end{matrix}\right] \] when any coordinate (x,y) is passed to a drawing function, it is converted as:
\[ \left[\begin{matrix} 1 & 0 & 250 \\ 0 & 1 & 250 \\ 0 & 0 & 1 \end{matrix}\right] \left( \left[\begin{matrix} 50 & 0 & 0 \\ 0 & -50 & 0 \\ 0 & 0 & 1 \end{matrix}\right] \left[\begin{matrix} x \\ y \\ 1 \end{matrix}\right] \right) \] Besides the three transformations mentioned above, there are two general transformation methods built-in: transform(m11, m12, m21, m22, dx, dy) and setTransform(m11, m12, m21, m22, dx, dy). As can be guessed from their names, transform adds the transformation matrix to the chain of transformations and setTransform resets the current transformation matrix. The parameters correspond to the affine transformation matrix
\[ \left[\begin{matrix} m11 & m12 & dx \\ m21 & m22 & dy \\ 0 & 0 & 1 \end{matrix}\right] \] These transformation methods are very powerful, representing every 2D coordinate transformation. When graphing a function, it is nearly always useful to use scale and translate to re-orient the coordinate space. A demo showing a more complex use of these functions can be seen online.

2.5 Drawing Lines and Functions

Basic line drawing functionality in canvas is handled by paths. One starts a path by calling beginPath() on the respective context. The basic functions needed to draw lines are moveTo(x, y) and lineTo(x, y), which take as parameters x- and y-coordinates in the coordinate space defined by the current transformation matrix, as discussed above. Note, however, that a line does not get drawn until either stroke() or fill() is called on the context. stroke draws a line (a path more generally) only whereas fill fills in the area between the start and end points of the path. (With some trickery, fill can be used to make nice demos of area under a curve.) While we will go into more detail about the full power of paths, the methods listed above are all the tools needed to plot a function.
The basic algorithm for plotting a function \(y = f(x)\) from xmin to xmax runs as follows (assuming the 2D context is stored in a variable named ctx and the function you want to plot is named f):
  1. Call ctx.beginPath().
  2. Move to starting point: ctx.moveTo(xmin, f(xmin))
  3. Having a variable called step (usually equal to (xmax - xmin) / numsteps), loop from x = xmin to x = xmax, incrementing by step.
  4. At each iteration, call ctx.lineTo(x, f(x)).
  5. Once done iterating, call ctx.stroke() (or fill if desired) to actually draw the lines.
  6. Call ctx.closePath() to end the path of the function.
This algorithm is carried out by plotF in the osculating demo:
01.function plotF() {
02.ctx.strokeStyle = "rgba(0,0,0,1)"; //will discuss this in later section
03.ctx.beginPath();
04.ctx.moveTo(XMIN, f(XMIN));
05.for(var x = XMIN; x <= XMAX; x += X_STEP) { //X_STEPS and X_STEP defined earlier in program
06.ctx.lineTo(x, f(x));
07.}
08.ctx.stroke();
09.ctx.closePath();
10.}
The for loop syntax should look familiar to anyone with experience in programming. One thing to note, however, is that by declaring var x in the beginning of the loop, x can only be accessed from inside that for loop. In some use cases, this may not be desirable, so plan accordingly.
Aside from moveTo and lineTo, other path methods include: quadraticCurveTo(cpx, cpy, x, y), bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) and arcTo(x1, y1, x2, y2, radius). For more information, see the WHATWG specification as linked in the Resources section of this paper.

2.5.1 Notes on Functions in Javascript

In Javascript, everything, including functions, is an Object. Thus, besides declaring functions the way that plotF was delcared above, one can also store functions in variables, as in the actual mathematical function declarations var f = function f(x) { ... } in this demo. While this choice of declaration makes debugging easier and imposes a semantic distinction between the procedural and mathematical functions, there is no distinct difference between how the two declarations operate.
Because functions are Objects, they can be passed to functions as parameters! This can be very useful, especially when coding mathlets that deal with displaying more than one function. For example, plotF could be rewritten to plot an arbitrary function only by redeclaring it function plot(f) { ...same code here... } and calling it with the function to be plotted as the lone parameter.
Another byproduct of functions being Objects is that functions can have properties and variables inside them, including other functions. Therefore, one can nest helper functions inside a larger function. Functions can also be declared anonymously; that is, one can declare a function without naming it and still pass it to other functions or store it in an array. For example, if we wanted to plot multiple functions at once, we could write a plotAll method as follows:
01.function plotAll() {
02.function plot(f) {
03.//same code as before
04.}
05.function f(x) { //our "base" function
06.return x;
07.}
08.var funcs = []; //empty array, will store functions in here
09.for(var i = 0; i < 5; i++)
10.funcs.push(function(x) { //add an anonymous function
11.return f(x) + i; //translated up 1
12.});
13.for(var i = 0; i < funcs.length; i++) //plot all the generated functions
14.plot(funcs[i]);
15.}
16.plotAll(); //call this from anywhere to plot all the functions defined above
One last comment specifically on mathematical functions in Javascript. If you noticed earlier, the function f and its derivatives are defined explicitly:
01.//define functions and their derivatives
02.//curve is 'parametrization' (x, f(x))
03.var f = function f(x) {
04.return 1/20 * (Math.pow(x,4) + Math.pow(x,3) - 13*Math.pow(x,2) - x);
05.};
06. 
07.var df = function df(x) {
08.return 1/20 * (4*Math.pow(x,3) + 3*Math.pow(x,2) - 26*x - 1);
09.};
10. 
11.var d2f = function d2f(x) {
12.return 1/20 * (12*Math.pow(x,2) + 6*x -26);
13.};
This was done because there are no built-in functions for calculating derivatives. One can parametrize the coefficients of the polynomial f to make the example a bit more dynamic, but the same problem essentially remains. It is possible to write a function that takes a function as a parameter and returns another function that represents the derivative by numerical approximation of \(\frac{f\left(x+h\right)-f\left(x\right)}{h}\). Such a numerical differntiator would be a great way to exploit the dynamic nature of functions in Javascript.

2.6 Drawing the Initial Osculating Circle

To draw a circle, one uses a specific case of the arc(x, y, radius, startAngle, endAngle, anticlockwise) method for paths. Anticlockwise is a boolean value representing what direction to draw the arc in. To see an example of drawing a circle, examine the method plotOsculating:
01.//for explanation of functions for center and radius of circle, see
03.function drawOsculating() {
04.var radius = 1 / Math.abs(cur_curv);
05.//cx, cy use the fact that x(t) = t in this parameterization
06.var cx = cur_x - (1 + Math.pow(df(cur_x),2))*df(cur_x)/d2f(cur_x);
07.var cy = f(cur_x) + (1 + Math.pow(df(cur_x),2))/d2f(cur_x);
08.ctx.strokeStyle = "#0000FF";
09.ctx.beginPath();
10.ctx.arc(cx, cy, radius, 0, 2*Math.PI, false);
11.ctx.stroke();
12.ctx.closePath();
13.}
The highlighted line represents the actual drawing of the circle, with the rest of the snippet being devoted mostly to calculation. If many circles need to be drawn, it might be convenient to write a simple wrapper method as such:
01.//ctx = 2D context to draw in
02.//fill = boolean, to fill or not to fill
03.function drawCircle(ctx, cx, cy, radius, fill) {
04.ctx.beginPath();
05.ctx.arc(cx, cy, radius, 0, 2*Math.PI, false);
06.if(fill) ctx.fill(); //whether or not to fill
07.else ctx.stroke();
08.ctx.closePath();
09.}
Wrapper methods such as drawCircle can help greatly reduce code redundancy when coding with the sometimes-verbose canvas element.

2.6.1 Rectangles and Redrawing

In addition to the complex path API, the 2D context features the ability to easily draw 3 types of rectangles, with coordinates (x,y), (x+w, y), (x+w, y+h), (x,y+h), to which the current transformation matrix will be applied:
  • strokeRect(x, y, w, h): generates an out-lined rectangle, according to strokeStyle
  • fillRect(x, y, w, h): generates a solid rectangle, according to fillStyle
  • clearRect(x, y, w, h): clears all the pixels on the canvas in the area inside the rectangle
Because canvas is an immediate-mode rendering layer, the only way to "undo" changes is to clear them via clearRect, as seen in the clear method in the osculating demo:
1.function clear() {
2.ctx.clearRect(XMIN, XMIN, XRANGE, XRANGE); //all defined earlier in program
3.}
Thus, every time one wants to change a drawing, the entire thing must be cleared and then re-drawn. This again makes it very important to wrap the actual drawing in functions that can be called again later.

2.7 Colors and Styles

The rendering context has two important properties that determine how objects get drawn on the canvas: fillStyle and strokeStyle. These two properties define how paths/shapes are filled and stroked, respectively. By default, they are assigned the string "#000000", the hexadecimal representation for black, but can take on a range of values. Most of the time, one uses either a hexadecimal string as in CSS or a string of the form "rgba(r, g, b, a)" that represents an RGBA color value. For example, in plotF I call ctx.strokeStyle = "rgba(0,0,0,1)"; which is equivalent to ctx.strokeStyle = "#000000" whereas in drawOsculating I set ctx.strokeStyle = "#0000FF"; (equivalent to ctx.strokeStyle = "rgba(0,0,1,1)") so that the circle is drawn in blue.
In the hexadecimal representation, there are three pairs of digits in the range 0 to F that correspond to values for red, green and blue. Thus, 000000 is the absence of color, i.e. black, and FFFFFF represents white. RGBA colors have 3 values also corresponding to red, green, and blue, only instead of a hexadecimal representation, each value is a number between 0 and 255. The big difference between the two representations is the "A" in RGBA: alpha. This third value, with range 0 to 1, represents the transparency of the color, with 1 being fully opaque and 0 being fully transparent. Thus, in drawAxes, we draw the axes as semi-transparent so that they do not dominate the scene.
01.function drawAxes() {
02.ctx.strokeStyle = "rgba(0,0,0,.5)";
03.ctx.lineWidth = 1 / scaleFactor;
04.ctx.beginPath();
05.ctx.moveTo(XMIN,0);
06.ctx.lineTo(XMAX,0);
07.ctx.moveTo(0,XMIN); //y range is same as x range
08.ctx.lineTo(0,XMAX);
09.ctx.stroke();
10.ctx.closePath();
11.}
Besides, however, colors and semi-transparent colors (in RGBA), strokeStyle and fillStyle can also be assigned a more complex CanvasGradient or CanvasPattern object, the latter being able to take on values of an image or another canvas element. While these may be useful for creating more aesthetically pleasing drawings on canvas, they are rarely necessary for the pedagogical purposes of mathlets. If you are interested in using Gradients or Patterns, take a look at the relevant sections of the WHATWG canvas specification.

2.8 Adding Interactivity via Slider

In this osculating circle demo, we use a jQuery UI slider to allow the user to choose the x value at which to draw the osculating circle. While we will walk through my jQuery implementation, the general principles of responding to user input are applicable to many other forms of user interactivity, including mouse clicks and text input. In course of developing the workbooks, however, we have found sliders being used more frequently than any other UI device.
The first step to implementing a slider is to get the relevant files from the jQuery website or link to the hosted versions at Google AJAX Libraries. While we host versions of jQuery, the code below uses Google's version; this means that if you want to implement a slider you can directly copy/paste the code below. Including the relevant jQuery files is as simple as putting the following lines in the head element of your HTML file:
2.<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
3.<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>
(Note: jQuery provides a very robust Theme Roller if you would like to make your own style to use with the slider or other widgets.)
Wherever you'd like to place the slider, you must include a div element styled with CSS for the desired width. For example:
1.<div id="xslide" style="width: 450px; margin: 10px"></div>
The element can also be styled with CSS in the head element or in an external file. If not styled at all, the slider will expand to fill the width of the container element, or the whole screen if there is none.
To generate the interactive slider, one selects the element via jQuery $("#xslide") and then calls slider on this element. If no parameters are specified to slider, it defaults to a minimum of 0 and a maximum of 100. As you can see below, the code to generate the osculating circle slider does specify a few parameters:
01.$("#xslide").slider({ // { } defines an object literal
02.min: XMIN, //minimum slider value
03.max: XMAX, //maximum value
04.value: (XMAX + XMIN) / 2, //default, initial value
05.step: X_STEP, //step size
06.animate: true, //jump to position when slider clicked
07.slide: function(event, ui) { //the function to perform when the slider is slid
08.cur_x = ui.value;
09.cur_curv = curvature(cur_x);
10.clear();
11.draw();
12.}
13.});
Most of the parameters are rather self-explanatory. The most important one, which powers the entire interactivity, is the slide parameter, into which we pass an anonymous function with two parameters, event and ui. Through these parameters one gains access to properties of the sliding event and the value of the widget itself. Thus this anonymous function, called everytime the value of the slider changes value, performs the following actions: sets current x, sets current curvature, clears the canvas, re-draws on the canvas using the updated values.
While this is a jQuery specific example, it represents a general paradigm for responding to events to generate user interaction. In general, when interaction changes a relevant value, the proper way to handle the changes in canvas is:
  1. Set/reset all relevant parameters, specifically the ones updated by the user. The variables that the user will update should be accessible from all relevant functions, including where you want to reset it.
  2. Clear the canvas, using clearRect as mentioned above.
  3. Redraw, with updates. To make this easier, I wrapped all of the individual drawing functions in a larger draw method.
1.function draw() {
2.$("#cur_curv").text(cur_curv); //display current curvature
3.drawAxes();
4.plotF();
5.drawPoint();
6.drawOsculating();
7.}
It is important to break up the drawing functionality like this because it abstracts the drawing away from the specific, potentially changeable parameters, which should be set elsewhere. Thus, for example, the X_STEPS, XSTEP, XMIN, XMAX values are set at the beginning of the program and then use those (as well as cur_x) values inside each drawing method. This sort of modularity also allows one to easily change the mathlet without having to manually change many numerical values.

3. Comparison of canvas to Alternative Options

Below is a table comparing various technologies for developing mathlets on a variety of criteria. While there are potentially more choices (i.e. Sage notebooks), these are generally the most common. Almost all mathlet development in the past has been in Java or Flash, but this paper, with principles essentially adaptable to the Scalable Vector Graphics (SVG) language manipulated by Javascript, shows that these technologies can be seen as viable alternatives. After reading the table, some of our implementation requirements will be listed and the choice to use canvas explained, after which we will make a couple of notes about browser support for canvas.
Figure 1: Comparison table of various potential platforms.

Java Flash/Flex canvas SVG
Open Source Sun has released Java as open source. Projects like OpenJDK also provide Free Software implementations of Java. The swf is an open specification and Flex is open source; tools to develop Flash mathlets are, generally, all closed source and pricey. canvas is a completely open specification as written by the WHATWG, with active response to community recommendations. SVG is a World Wide Web Consortium recommendation with a fully open specification.
Cross-Platform Either as applets or stand-alone programs, requires Java Virtual Machine and JRE, which Sun provides for virtually every OS and come pre-installed on most. Requires a browser-plugin, but 95% of computers have it installed. A web technology, canvas works in every browser that implements it (more on this later) regardless of platform. Same as canvas in terms of reliance on browser support. Internet Explorer does not natively implement canvas or SVG.
Math Resources Being one of the standard-bearers in mathlet development, there are many resources on Java development, including tutorials and pre-built tools like GeoGebra. Many mathlets have been developed in Flash. The website flashandmath.com, as well as Loci: Developers, offers a lot of instructional articles and ready-to-use classes. Very little work has been done on mathlets in canvas. Javascript has a very small library of mathematical functions, with some libraries existing for matrix and planar geometry. Little in the way of interactive mathlets, but SVG has been used for drawing many technical diagrams, so interactivity is the main feature to be handled by Javascript.
Rendering Speed Must wait for JRE / applet to load, which can be slow. Once loaded, performance is very snappy. Load time depends on complexity of mathlet. Once loaded, very responsive performance. All that must be loaded are relatively small HTML pages with some Javascript; negligible load time. With improvements in Javascript rendering engines like V8, SpiderMonkey, etc., canvas can also deliver very snappy demos. Load time similar to canvas, but slightly less responsive because re-drawing involves manipulating DOM elements.
Robustness Java is a powerful enough language to program anything imaginable, including complete office suites. While immensely robust, the power also manifests itself with higher overhead. While having a smaller API than Java, there are lots of interactive features already implemented. Because object-oriented, can be extended easily. Programmed in Javascript, which has the smallest API of the options, using canvas will require more work for some complex tasks. See canvas entry, uses Javascript as well.
Offline Use Needs an AppletViewer. Can hope that most users have it installed because it is too big to include with a zip bundle of workbooks. Flash Player works the same whether or not there is an internet connection. Adobe AIR also provides offline runtime environment. Only need connectivity if using a web service. Viewed natively in browser with client-side Javascript language. Only need connection if connecting to a web service, but not for actual mathlets. See canvas
Rendering Layers Applets create a "sandbox" where Java functionality runs completely isolated from the rest of page. Interactivity and text explanations on different layers. Roughly the same situation as Java, although the Apollo project aims to render HTML and Flash on the same level. canvas is an HTML element, so rendering takes place on exact same level as text. Javascript allows user interaction with page to directly influence the graphics. SVG is an XML language, rendered in the same DOM tree as an XHTML page. Both DOMs handled at same level by Javascript.

3.1 Implementation Requirements and the Choice to Use canvas

When faced with the problem of updating the Mathwright microworlds, we had a few implementation requirements that the new workbooks should meet:
  • Cross-platform and widely accessible.
  • Open standards. The workbooks should be built on entirely open (most often Free Software) technologies. Using open standards helps future-proof these workbooks and make sure that they are usable long into the future.
  • Client-side deployment. While making server-side tutorials would enable the use of advanced numerical computation and visualization tools, we wanted our tutorials to be run on the client's computer. This would allow them to be used in classrooms on laptop stations even when no reliable internet connection is available. Similarly, this requirement also means that the workbooks can be made into a zip file and distributed for easier deployment and use where internet connections are not reliable.
Because of these requirements, we decided to use a web-based, client-side deployment. Web-based because most computers have a modern enough web browser and client-side for the reasons detailed above. (X)HTML is the lingua franca of the web and is what we've used for most markup. When we need to display actual advanced mathematical equations, we use jsMath, an implementation of LaTeX in Javascript.
Which brings us to the crux of the implementation: Javascript. Thanks to efforts by companies like Google, Facebook, and Meebo, Javascript adoption and development of faster parsing engines has increased greatly in the last two years. Now, most web browsers parse Javascript fast enough to do any of the calculations necessary for fairly impressive mathematical demonstrations. This increased performance eliminates the need for a compiled solution such as Java applets or Flash demos to ensure snappy performance on interactive demonstrations. We originally began work, including the writing of a basic 3D rendering engine, using the W3C's SVG, an XML langauge for vector graphics. Because, however, dealing with new document elements is still fairly slow in Javascript and canvas provides an immediate-mode 2D rendering layer, we decided not to use SVG. Thus, we use Javascript both to do all of the numerical calculations and in fact to get user input and display the interactive graphics.

3.2 Browser Support for canvas

Nearly every modern browser supports the canvas element: Firefox 2.0+, Safari 3.1+, Chrome, Opera 9.0+, Konqueror 4, and any browser based on a new enough version of WebKit. For a more complete listing of compatibility, see this table, which can also be made to include more comparison tests than just canvas. There is one glaring omission from this list: Internet Explorer, which is far and away the most popular web browser in the world, if only because it is the default browser shipped with Microsoft Windows. This situation is not much worse than Java, Flash or SVG, which all require a plug-in for IE. In addition, there is a simple method for enabling most canvas functionality in IE.
Additionally, besides Firefox 3.5+, Safari 4.0+ and Chrome 2.0+, most browsers (read: Opera) do not support the text API in canvas. Note, however, that the canvas-text project implements the text API methods using pure Javascript by drawing fonts, represented by appropriately crafted JSON documents, as paths on the canvas. Additionally, many uses of canvas, such as the flot graphing library (see Resources section below) use an overlayed div element to render text on top of the canvas. Such methods are, however, on their way out as text support gets better.

3.2.1 canvas in Internet Explorer via excanvas

While IE does not natively support canvas (and there is no clear indication when it will), there are workarounds to recreate the same functionality and interface. Google's excanvas.js uses IE's Visual Markup Language to implement the canvas rendering interface for IE. Using excanvas is as simple as including the following line of code in the head element of your HTML element:
1.<!--[if IE]><script type="text/javascript" src="http://explorercanvas.googlecode.com/svn/trunk/excanvas.js"></script><![endif]-->
While excanvas works very well for almost all use cases, there are still some bugs and quirks that might prevent proper functionality of a desired mathlet. For example, there is a bug that prevents drawing of arcs on scaled canvases, which eliminates the critical feature of the osculating circle demo from being rendered. Running into such quirks is rare, but is a factor to consider when deciding to develop in canvas. The developers in charge of excanvas do actively maintain it, so such bugs often get fixed and there is also a push for IE to natively support canvas as other modern web browsers do.

4. What Else is Possible With canvas

This section will serve as an exhibition of more advanced demonstrations of the power of canvas element. First, we will show some more powerful mathematical demonstrations, including ones that take advantage of 3D rendering, and then show some general advanced uses of canvas. These links are selected to show the power of the canvas element both for mathematical demonstration and for advanced visualization in general. (Note: All links in this section are external links.)

4.1 More Advanced Mathematical Demos

All of the demos below that display 3D objects do so using Dean McNamee's Pre3d library for Javascript. I have made some custom code changes and contributions to make 3D surface rendering easier and more flexible and development of the library will continue to progress.
  • Local and Total curvature of a surface. Developed as part of the same workbook as the 2D osculating circle, these demos show 3D surface rendering with user interactivity.
  • Lorenz-84 3D Strange Attractor. Written by Dean McNamee, this demo shows a beautiful chaotic strange attractor in 3-dimensions that can be easily explored with the mouse.
  • Group actions on a 3-D vector being displaced inside a unit sphere.
  • Metric mapping: shows an initial velocity field (which is changeable) through \(GL_2\left(\mathbb{R}\right)\) and traces out an integral curve through the field. As this integral curve is being traced out, the relevant deformation is being applied to a circle.
  • Conformal maps: this demo allows the user to manipulate 6 paramaters that control a conformal map in the complex plane which then gets rendered in real time.

4.2 Other Examples

  • Evolving Cube: Watch as a cube evolves and gets extruded into a beautifully tentacled monster.
  • Bespin: collaborative text-editor for programmers implemented in canvas
  • Raytracer implemented completely in Javascript and rendering onto canvas.
  • Image evolution implements an annealing algorithm to approximate a polygonal drawing of the Mona Lisa.
  • Mozilla Open Web Tools directory: besides being a very useful directory of tools, the unique interface, including space theme, is implemented in canvas.

5. Resources About the canvas Element

6. Conclusions

We hope to have shown both the power of canvas-based mathlets and the ease of development involved. With anywhere from 100 to 1000 lines of Javascript and just a few lines of HTML markup, one can create interactive mathlets covering a broad range of topics. Using these technologies, we have provided an integrated interactive experience in which both the user interaction and graphics rendering both take place directly on a web page. Thus one can use entirely open standards (HTML, Javascript, canvas) to develop mathlets that perform as well as Flash and Java applets without having to deal with the overhead, compatibility issues, and sandboxed nature of those technologies. We hope that our work on the Metric Pattern Theory workbooks, along with the examples listed above, will encourage more math educators to turn to these open technologies to develop mathlets in the future.

7. Acknowledgements

Supported by the Technology Fellowship program from the Center for Educational Resources at The Johns Hopkins University (2008-2009), NSF (DMS-0456253), and NIH (R24-HL085343 and P41-RR015241).

No hay comentarios:

Publicar un comentario