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.
007.
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.
017.
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.
031.
var
curvature =
function
curvature(x) {
032.
return
d2f(x) / Math.pow(1 + Math.pow(df(x),2), 1.5);
033.
};
034.
035.
036.
var
cur_x = 0;
037.
var
cur_curv = curvature(cur_x);
038.
039.
function
draw() {
040.
$(
"#cur_curv"
).text(cur_curv);
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);
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.
084.
085.
function
drawOsculating() {
086.
var
radius = 1 / Math.abs(cur_curv);
087.
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.
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.
4.
5.
ctx.translate(canvas.width / 2, canvas.height / 2);
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
):
- Call
ctx.beginPath()
.
- Move to starting point:
ctx.moveTo(xmin, f(xmin))
- Having a variable called
step
(usually equal to (xmax - xmin) / numsteps
), loop from x = xmin
to x = xmax
, incrementing by step
.
- At each iteration, call
ctx.lineTo(x, f(x))
.
- Once done iterating, call
ctx.stroke()
(or fill
if desired) to actually draw the lines.
- 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)"
;
03.
ctx.beginPath();
04.
ctx.moveTo(XMIN, f(XMIN));
05.
for
(
var
x = XMIN; x <= XMAX; x += X_STEP) {
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
Object
s, 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
Object
s 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.
04.
}
05.
function
f(x) {
06.
return
x;
07.
}
08.
var
funcs = [];
09.
for
(
var
i = 0; i < 5; i++)
10.
funcs.push(
function
(x) {
11.
return
f(x) + i;
12.
});
13.
for
(
var
i = 0; i < funcs.length; i++)
14.
plot(funcs[i]);
15.
}
16.
plotAll();
One last comment specifically on mathematical functions in Javascript. If you noticed earlier, the function
f
and its derivatives
are defined explicitly:
01.
02.
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.
02.
03.
function
drawOsculating() {
04.
var
radius = 1 / Math.abs(cur_curv);
05.
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.
02.
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();
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);
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);
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:
(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({
02.
min: XMIN,
03.
max: XMAX,
04.
value: (XMAX + XMIN) / 2,
05.
step: X_STEP,
06.
animate:
true
,
07.
slide:
function
(event, ui) {
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:
- 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.
- Clear the
canvas
, using clearRect
as mentioned above.
- 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);
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
.
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:
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).