http://www.maa.org/sites/default/files/images/upload_library/55/steinertthrelkeld/canvas/canvas.html
Open standards, web-based mathlets: making interactive tutorials using the html5 canvas element.
Shane Steinert-Threlkeld1 and J Tilak Ratnanather2,3
The Johns Hopkins University
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 HTML5canvas
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
Table of Contents
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 HTML5canvas
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
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 ascanvas
, 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
>
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 thecanvas
. It is convenient to use such a wrapping element because it makes positioning/styling thecanvas
much easier with Cascading Style Sheets (CSS) and also allows one to group other elements, such as the slider, with thecanvas
.<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 acanvas
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"
);
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);
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 callingtranslate
and then after also
calling scale
The
\[ \left[\begin{matrix} 1 & 0 & 250 \\ 0 & 1 & 250 \\ 0 & 0 & 1 \end{matrix}\right] \] The built-in transformations are
\[ \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:
\[ \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
The basic algorithm for plotting a function \(y = f(x)\) from
The
Aside from
Because functions are
Another byproduct of functions being
One last comment specifically on mathematical functions in Javascript. If you noticed earlier, the function
This was done because there are no built-in functions for
calculating derivatives. One can parametrize the coefficients of the
polynomial
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:
Wrapper methods such as
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.
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
Besides, however, colors and semi-transparent colors (in RGBA),
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
(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
The element can also be styled with CSS in the
To generate the interactive slider, one selects the element via jQuery
Most of the parameters are rather self-explanatory. The most important one, which powers the entire interactivity, is the
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
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
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 incanvas
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 fromx = xmin
tox = xmax
, incrementing bystep
. - At each iteration, call
ctx.lineTo(x, f(x))
. - Once done iterating, call
ctx.stroke()
(orfill
if desired) to actually draw the lines. - Call
ctx.closePath()
to end the path of the function.
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.
}
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 anObject
. 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.
//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
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.
};
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 thearc(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.
}
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.
}
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 tostrokeStyle
fillRect(x, y, w, h)
: generates a solid rectangle, according tofillStyle
clearRect(x, y, w, h)
: clears all the pixels on the canvas in the area inside the rectangle
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.
}
2.7 Colors and Styles
The rendering context has two important properties that determine how objects get drawn on thecanvas
: 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.
}
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:1.
<
link
rel
=
"stylesheet"
type
=
"text/css"
href
=
"http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/base/jquery-ui.css"
>
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
>
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
>
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.
});
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
, usingclearRect
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);
//display current curvature
3.
drawAxes();
4.
plotF();
5.
drawPoint();
6.
drawOsculating();
7.
}
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.
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]-->
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
- The WHATWG specification of
canvas
- Mozilla Developer Center
canvas
Tutorial - An Interactive
canvas
Tutorial - Ray Tracing Tutorial
- Flot: plotting and graphing library for jQuery
6. Conclusions
We hope to have shown both the power ofcanvas
-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.
No hay comentarios:
Publicar un comentario