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.
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.
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.
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 = :
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
.
var canvas = document.getElementById("osculating"); var ctx = canvas.getContext("2d"); var scaleFactor = 50; //move origin to center, make y ascend up //window: -5 to 5 ctx.translate(canvas.width / 2, canvas.height / 2); ctx.scale(scaleFactor, -scaleFactor); var X_STEPS = 75; var X_STEP = 10 / X_STEPS; var XMIN = -5, XMAX = 5; var XRANGE = XMAX - XMIN; //define functions and their derivatives //curve is 'parametrization' (x, f(x)) var f = function f(x) { return 1/20 * (Math.pow(x,4) + Math.pow(x,3) - 13*Math.pow(x,2) - x); }; var df = function df(x) { return 1/20 * (4*Math.pow(x,3) + 3*Math.pow(x,2) - 26*x - 1); }; var d2f = function d2f(x) { return 1/20 * (12*Math.pow(x,2) + 6*x -26); }; //calculate curvature at a point var curvature = function curvature(x) { return d2f(x) / Math.pow(1 + Math.pow(df(x),2), 1.5); }; //variables representing current values, non-static var cur_x = 0; var cur_curv = curvature(cur_x); function draw() { $("#cur_curv").text(cur_curv); //display current curvature drawAxes(); plotF(); drawPoint(); drawOsculating(); } function clear() { ctx.clearRect(XMIN, XMIN, XRANGE, XRANGE); } function drawAxes() { ctx.strokeStyle = "rgba(0,0,0,.5)"; ctx.lineWidth = 1 / scaleFactor; ctx.beginPath(); ctx.moveTo(XMIN,0); ctx.lineTo(XMAX,0); ctx.moveTo(0,XMIN); //y range is same as x range ctx.lineTo(0,XMAX); ctx.stroke(); ctx.closePath(); } function drawPoint() { var y = f(cur_x); ctx.fillStyle = "#FF0000"; ctx.beginPath(); ctx.arc(cur_x, y, .1, 0, 2*Math.PI, false); ctx.fill(); ctx.closePath(); } function plotF() { ctx.strokeStyle = "rgba(0,0,0,1)"; ctx.beginPath(); ctx.moveTo(XMIN, f(XMIN)); for(var x = XMIN; x <= XMAX; x += X_STEP) { ctx.lineTo(x, f(x)); } ctx.stroke(); ctx.closePath(); } //for explanation of functions for center and radius of circle, see //http://mathworld.wolfram.com/OsculatingCircle.html function drawOsculating() { var radius = 1 / Math.abs(cur_curv); //cx, cy use the fact that x(t) = t in this parameterization var cx = cur_x - (1 + Math.pow(df(cur_x),2))*df(cur_x)/d2f(cur_x); var cy = f(cur_x) + (1 + Math.pow(df(cur_x),2))/d2f(cur_x); ctx.strokeStyle = "#0000FF"; ctx.beginPath(); ctx.arc(cx, cy, radius, 0, 2*Math.PI, false); ctx.stroke(); ctx.closePath(); } draw(); $("#xslide").slider({ min: XMIN, max: XMAX, value: (XMAX + XMIN) / 2, step: X_STEP, animate: true, slide: function(event, ui) { cur_x = ui.value; cur_curv = curvature(cur_x); clear(); draw(); } });
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:
<html> <head> <title>Your Title Here</title> <link type="text/css" ref="stylesheet" href="/path/to/your/style.css" /> <script type="text/javascript" src="/path/to/your/osculating.js"></script> <!-- insert other stylesheets and javascripts (i.e. jQuery, which I use but is not necessary in Osculating demo) here --> </head> <body> <div id="canv">Current curvature:
</div> </body> </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.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:
var canvas = document.getElementById("osculating"); 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
.
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:
var scaleFactor = 50; //move origin to center, make y ascend up //window: -5 to 5 ctx.translate(canvas.width / 2, canvas.height / 2); //evaluates to ctx.translate(250, 250) 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.
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
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
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
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.
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
):
ctx.beginPath()
.ctx.moveTo(xmin, f(xmin))
step
(usually equal to (xmax - xmin) / numsteps
), loop from x = xmin
to x = xmax
, incrementing by step
.ctx.lineTo(x, f(x))
.ctx.stroke()
(or fill
if desired) to actually draw the lines.ctx.closePath()
to end the path of the function.This algorithm is carried out by plotF
in the osculating demo:
function plotF() { ctx.strokeStyle = "rgba(0,0,0,1)"; //will discuss this in later section ctx.beginPath(); ctx.moveTo(XMIN, f(XMIN)); for(var x = XMIN; x <= XMAX; x += X_STEP) { //X_STEPS and X_STEP defined earlier in program ctx.lineTo(x, f(x)); } ctx.stroke(); ctx.closePath(); }
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.
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:
function plotAll() { function plot(f) { //same code as before } function f(x) { //our "base" function return x; } var funcs = []; //empty array, will store functions in here for(var i = 0; i < 5; i++) funcs.push(function(x) { //add an anonymous function return f(x) + i; //translated up 1 }); for(var i = 0; i < funcs.length; i++) //plot all the generated functions plot(funcs[i]); } 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:
//define functions and their derivatives //curve is 'parametrization' (x, f(x)) var f = function f(x) { return 1/20 * (Math.pow(x,4) + Math.pow(x,3) - 13*Math.pow(x,2) - x); }; var df = function df(x) { return 1/20 * (4*Math.pow(x,3) + 3*Math.pow(x,2) - 26*x - 1); }; var d2f = function d2f(x) { return 1/20 * (12*Math.pow(x,2) + 6*x -26); };
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.
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
:
//for explanation of functions for center and radius of circle, see //http://mathworld.wolfram.com/OsculatingCircle.html function drawOsculating() { var radius = 1 / Math.abs(cur_curv); //cx, cy use the fact that x(t) = t in this parameterization var cx = cur_x - (1 + Math.pow(df(cur_x),2))*df(cur_x)/d2f(cur_x); var cy = f(cur_x) + (1 + Math.pow(df(cur_x),2))/d2f(cur_x); ctx.strokeStyle = "#0000FF"; ctx.beginPath(); ctx.arc(cx, cy, radius, 0, 2*Math.PI, false); ctx.stroke(); ctx.closePath(); }
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:
//ctx = 2D context to draw in //fill = boolean, to fill or not to fill function drawCircle(ctx, cx, cy, radius, fill) { ctx.beginPath(); ctx.arc(cx, cy, radius, 0, 2*Math.PI, false); if(fill) ctx.fill(); //whether or not to fill else ctx.stroke(); ctx.closePath(); }
Wrapper methods such as drawCircle
can help greatly reduce code redundancy when coding with the sometimes-verbose
canvas
element.
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 rectangleBecause 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:
function clear() { ctx.clearRect(XMIN, XMIN, XRANGE, XRANGE); //all defined earlier in program }
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.
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.
function drawAxes() { ctx.strokeStyle = "rgba(0,0,0,.5)"; ctx.lineWidth = 1 / scaleFactor; ctx.beginPath(); ctx.moveTo(XMIN,0); ctx.lineTo(XMAX,0); ctx.moveTo(0,XMIN); //y range is same as x range ctx.lineTo(0,XMAX); ctx.stroke(); ctx.closePath(); }
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.
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:
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:
$("#xslide").slider({ // { } defines an object literal min: XMIN, //minimum slider value max: XMAX, //maximum value value: (XMAX + XMIN) / 2, //default, initial value step: X_STEP, //step size animate: true, //jump to position when slider clicked slide: function(event, ui) { //the function to perform when the slider is slid cur_x = ui.value; cur_curv = curvature(cur_x); clear(); draw(); } });
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:
canvas
, using clearRect
as mentioned above.draw
method.function draw() { $("#cur_curv").text(cur_curv); //display current curvature drawAxes(); plotF(); drawPoint(); drawOsculating(); }
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.
canvas
to Alternative OptionsBelow 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
.
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. |
canvas
When faced with the problem of updating the Mathwright microworlds, we had a few implementation requirements that the new workbooks should meet:
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.
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.
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.
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.)
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.
canvas
canvas
.canvas
.canvas
Elementcanvas
canvas
Tutorialcanvas
Tutorial
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.
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).