Data Structure Visualizations

Source Code

You can download the complete source for the HTML5/Javascript version of the visualizations here:

Source code for David Galles's original version is available from his website. The releases above are built on release 1.4 of David's code, but use separate version numbers.

A few notes / warnings:
  1. Do not try to look at the code to understand the algorithms. I have done one or two tricky things to get the visualizations to work property that rather obscure the algorithms themselves. Your favorte textbook, or even wikipedia, is a better bet for appropriate source code.
  2. Like all software projects, this one is a bit of a living thing -- it started life as a Java project, was rewritten in ActionScript3 (that is, flash), and then ported to Javascript. It was also my opportunity to learn flash and javascript, so by the time I figured out the best way to do things, half of the software was already written. I've done some going back to clean stuff up, but there is still a quite a lot of ugliness. Next time all my code will be pretty :).

Visualization Creation Tutorial

To creeate a new visualization, you need to create a javascript file and an HTML file. The HTML file should just be copied from a template, changing only one or two items (like the name of the javascript file). Examples of the HTML template and how to change it are at the end of this tutorial. In the javascript file, you will create a function (an object, really, but functions are objects in javascript) that:
  1. Creates any appropriate controls to control you visualization (inserting elements, deletig elements, etc)
  2. Creates callbacks for these controls that implement the visualizations. The visualizations are implemented by sending an array of strings to the animation manager -- the animation manager will then implement the animation, and handle all of the animation controls for you
  3. Listen for an undo event from the animation manager. When an undo event is detected, roll back the last operation

Using Algorithm function

Creating the javascript function is stil farily complicated, even when using the rest of the library. Many of the ugly details can be automated if your function "subclasses" the Algorithm function (located in AlgorithmLibrary/Algorithm.js (which is sort of a faux "class"). Consider the skeleton "MyAlgorithm" function included in the tarfile: Looking at various pieces of the file in turn:

Copyright: Code is released under in a FreeBSD license.
// Copyright 2011 David Galles, University of San Francisco. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY David Galles ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of the University of San Francisco
Next, the algorithm definition. We are doing a sort of "faked" inheritance within javascript. We define our function, set the prototype of our function to the prototype of our superclass, reset the constructor to be our own constructor, and then cache the superclass prototype, for simulating a java-style "super" call. Notice that to make inheritance work well on counstructors, we don't do anything in the main constructor function other than call an init function (that way we can have our init function call the init function of the superclass. Yes, this is a bit of a hack.)
function MyAlgorithm(am, w, h)
{
	this.init(am, w, h);
}

MyAlgorithm.prototype = new Algorithm();
MyAlgorithm.prototype.constructor = MyAlgorithm;
MyAlgorithm.superclass = Algorithm.prototype;
Next, we have our constructor. In general, we will need to do the following in our counstructor:
MyAlgorithm.prototype.init = function(am, w, h)
{
	// Call the unit function of our "superclass", which adds a couple of
	// listeners, and sets up the undo stack
	MyAlgorithm.superclass.init.call(this, am, w, h);

	this.addControls();

	// Useful for memory management
	this.nextIndex = 0;

	// TODO:  Add any code necessary to set up your own algorithm.  Initialize data
	// structures, etc.
}
Next we have the function to add controls. There are several helper functions to add controls. See the Algorithm.js file for more information on these helper functions.
MyAlgorithm.prototype.addControls =  function()
{
	this.controls = [];

    // Add any necessary controls for your algorithm.
    //   There are libraries that help with text entry, buttons, check boxes, radio groups
    //
    // To add a button myButton:
    //     this.mybytton = addControlToAlgorithmBar("Button", "MyButtonText");
    //     this.mybytton.onclick = this.myCallback.bind(this);
    //     this.controls.push(this.mybutton);
    //   where myCallback is a method on this function that implemnts the callback
    //
    // To add a text field myField:
    //    this.myField = addControlToAlgorithmBar("Text", "");
    //    this.myField.onkeydown = this.returnSubmit(this.myField,
    //                                               this.anotherCallback.bind(this), // callback to make when return is pressed
    //                                               maxFieldLen,                     // integer, max number of characters allowed in field
    //                                               intOnly);                        // boolean, true of only digits can be entered.
    //    this.controls.push(this.myField);
    //
    // To add a textbox:
    //   	this.myCheckbox = addCheckboxToAlgorithmBar("Checkbox Label");
    //      this.myCheckbox.onclick = this.checkboxCallback.bind(this);
    //      this.controls.push(myCheckbox);
    //
    // To add a radio button group:
    //	  this.radioButtonList = addRadioButtonGroupToAlgorithmBar(["radio button label 1",
    //                                                              "radio button label 2",
    //                                                              "radio button label 3"],
    //                                                             "MyButtonGroupName");
    //    this.radioButtonList[0].onclick = this.firstRadioButtonCallback.bind(this);
    //    this.controls.push(this.radioButtonList[0]);
    //    this.radioButtonList[1].onclick = this.secondRadioButtonCallback.bind(this);
    //    this.controls.push(this.radioButtonList[1]);
    //    this.radioButtonList[2].onclick = this.thirdRadioButtonCallback.bind(this);
    //    this.controls.push(this.radioButtonList[1]);
    //
    // Note that we are adding the controls to the controls array so that they can be enabled / disabled
    // by the animation manager (see enableUI / disableUI below)
}
We will need to "override" the reset method. Whenever the animation manager wants to undo an operation:
MyAlgorithm.prototype.reset = function()
{
	// Reset all of your data structures to *exactly* the state they have immediately after the init
	// function is called.  This method is called whenever an "undo" is performed.  Your data
	// structures are completely cleaned, and then all of the actions *up to but not including* the
	// last action are then redone.  If you implement all of your actions through the "implementAction"
	// method below, then all of this work is done for you in the Animation "superclass"

	// Reset the (very simple) memory manager
	this.nextIndex = 0;
}
Callbacks, doing actual work
//////////////////////////////////////////////
// Callbacks:
//////////////////////////////////////////////
//
//   All of your callbacks should *not* do any work directly, but instead should go through the
//   implement action command.  That way, undos are handled by ths system "behind the scenes"
//
//   A typical example:
//
//MyAlgorithm.prototype.insertCallback = function(event)
//{
//	// Get value to insert from textfield (created in addControls above)
//	var insertedValue = this.insertField.value;
//
//  // If you want numbers to all have leading zeroes, you can add them like this:
//	insertedValue = this.normalizeNumber(insertedValue, 4);
//
//  // Only do insertion if the text field is not empty ...
//	if (insertedValue != "")
//	{
//		// Clear text field after operation
//		this.insertField.value = "";
//      // Do the actual work.  The function implementAction is defined in the algorithm superclass
//		this.implementAction(this.insertElement.bind(this), insertedValue);
//	}
//}
//  Note that implementAction takes as parameters a function and an argument, and then calls that
//  function using that argument (while also storing the function/argument pair for future undos)

//////////////////////////////////////////////
// Doing actual work
//////////////////////////////////////////////
//   The functions that are called by implementAction (like insertElement in the comments above) need to:
//
//      1. Create an array of strings that represent commands to give to the animation manager
//      2. Return this array of commands
//
//    We strongly recommend that you use the this.cmd function, which is a handy utility function that
//    appends commands onto the instance variable this.commands
//
//    A simple example:
//
//MyAlgorithm.simpleAction(input)
//{
//	this.commands = [];  // Empty out our commands variable, so it isn't corrupted by previous actions
//
//	// Get a new memory ID for the circle that we are going to create
//	var circleID = nextIndex++;
//	var circleX = 50;
//	var circleY = 50;
//
//	// Create a circle
//	this.cmd("CreateCircle", circleID, "Label",  circleX, circleY);
//	circleX = 100;
//	// Move the circle
//	this.cmd("Move", circleID, circleX, circleY);
//	// First Animation step done
//	this.cmd("Step");
//	circleX = 50;
//	circleY = 100;
//	// Move the circle again
//	this.cmd("Move", circleID, circleX, circleY);
//	// Next Animation step done
//	this.cmd("Step");
//	// Return the commands that were generated by the "cmd" calls:
//	return this.commands;
//}

Enabling, disabling UI
// Called by our superclass when we get an animation started event -- need to wait for the
// event to finish before we start doing anything
MyAlgorithm.prototype.disableUI = function(event)
{
	for (var i = 0; i < this.controls.length; i++)
	{
		this.controls[i].disabled = true;
	}
}

// Called by our superclass when we get an animation completed event -- we can
/// now interact again.
MyAlgorithm.prototype.enableUI = function(event)
{
	for (var i = 0; i < this.controls.length; i++)
	{
		this.controls[i].disabled = true;
	}
}
Script to start everything
////////////////////////////////////////////////////////////
// Script to start up your function, called from the webapge:
////////////////////////////////////////////////////////////

var currentAlg;

function init()
{
	var animManag = initCanvas();
	currentAlg = new MyAlgorithm(animManag, canvas.width, canvas.height);

}

Animation Commands

The commands that we give to the animatino manager are a list (array) of strings. Each string starts with the name of the command (case-insensitive) followed by a list of arguments, separated by the token <;>. The first argument of (almost) every command is the ID of the object you want to create or access. So, the string to Move element 37 to position (100, 120) would be: Commands can be separated into two groups: Commands that create animated objects, and commands that manipulate previously created objects.

Object Creation and Deletion Commands

Object creation commands take as a first argument an integer representing the index of the object to create. This integer must not be the same as another object that is currently active in the system (though you can reuse numbers once the objects have been deleted). Like all commands, the creation commands have some required and some optional parameters.
Note that creation of an object using an objectID that already is in use will throw an exception. Deleting an ID that is not currently in use will also throw an exception.

Object Manipulation Commands

Edge Manipulation Commands

Edges are manipulated by giving the two objects associated with the edge. While edges can be graphically directed or undirected, all edges under the hood have a direction, which is the direction that they were given when the edge was created. There can only be one edge from any object to any other object (though there can be an edge from object1 to object2, and a different edge from object2 to object1.) Edges are always referred to by two IDs - the objectID of the "from" object, followed by the objectID of the "to" object. Any object can be connected to any other object.

Special Commands

Simple Stack Example

Everything make sense so far? Time for a simple, complete example. A simple stack visualization can be found at: Looking at the complete SimpleStack.js file in its entirety:

All code released under a FreeBSD license
// Copyright 2011 David Galles, University of San Francisco. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY David Galles ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL  OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of the University of San Francisco
Next up is the Initial setup. The first several lines of any visualization javascript will look like this, with SimpleStack replaced by whatever function you are writing
function SimpleStack(am, w, h)
{
	this.init(am, w, h);
}

SimpleStack.prototype = new Algorithm();
SimpleStack.prototype.constructor = SimpleStack;
SimpleStack.superclass = Algorithm.prototype;
The next items in the file are some constants. We placed them in the function's namespace to avoid symbol clashes:
SimpleStack.ELEMENT_WIDTH = 30;
SimpleStack.ELEMENT_HEIGHT = 30;
SimpleStack.INSERT_X = 30;
SimpleStack.INSERT_Y = 30;
SimpleStack.STARTING_X = 30;
SimpleStack.STARTING_Y = 100;
SimpleStack.FOREGROUND_COLOR = "#000055"
SimpleStack.BACKGROUND_COLOR = "#AAAAFF"
Next up is the constructor. Technically, the constructor was first:
function SimpleStack( ...
However, all of the work of the constructor is done in the init function -- that way constructors of subclass can effectively call the constructors of their superclasses. For the init function in this case, we need to do very little work. In this simple example we are not adding any elements to the canvas at load time. All we need to do is set up our own internal data structures. We are keeping track of two arrays -- an array that stores the actual stack (stackValues), and an array that stores the objectIDs of the elements of the stack (stackID)
SimpleStack.prototype.init = function(am, w, h)
{
	// Call the unit function of our "superclass", which adds a couple of
	// listeners, and sets up the undo stack
	SimpleStack.superclass.init.call(this, am, w, h);

	this.addControls();

	// Useful for memory management
	this.nextIndex = 0;

	this.stackID = [];
	this.stackValues = [];

	this.stackTop = 0;
}
Next up is the method to add algorithm controls and callbacks. The tricky bit here is that we can't do something like:
this.popButton.onclick = this.popCallback
to add our callbacks. Why not? This would pass in the proper function, but not the proper contex -- essentially, it would be passing a pointer to the code of the function, without saving the "this" pointer. So we need to bind the "this" pointer to our function before we store it.
SimpleStack.prototype.addControls =  function()
{
	this.controls = [];


    this.pushField = addControlToAlgorithmBar("Text", "");
    this.pushField.onkeydown = this.returnSubmit(this.pushField,
                                               this.pushCallback.bind(this), // callback to make when return is pressed
                                               4,                           // integer, max number of characters allowed in field
                                               false);                      // boolean, true of only digits can be entered.
	this.controls.push(this.pushField);

	this.pushButton = addControlToAlgorithmBar("Button", "Push");
	this.pushButton.onclick = this.pushCallback.bind(this);
	this.controls.push(this.pushButton);

	this.popButton = addControlToAlgorithmBar("Button", "Pop");
	this.popButton.onclick = this.popCallback.bind(this);
	this.controls.push(this.popButton);
}
As a side note, here's the code for the bind function, located in CustomEvents.js
Function.prototype.bind = function() {
	var _function = this;

	var args = Array.prototype.slice.call(arguments);
	var scope = args.shift()
	return function() {
		for (var i = 0; i < arguments.length; i++)
		{
			args.push(arguments[i]);
		}
		return _function.apply(scope, args);
	}
}
Moving on ... Next up is the reset function. All visualizations must implement the reset method. The reset method needs to restore all of our variables to the state that they were in right after the call to init. In our case, we only have 2 variables of importance. We could have recreated the arrays stackID and stackValues, but that is not necessary in this case, since we don't care about the current values in those arrays -- we will write over any value before we read it, once the stack top is 0.
SimpleStack.prototype.reset = function()
{
	// Reset the (very simple) memory manager.
	//  NOTE:  If we had added a number of objects to the scene *before* any user
	//         input, then we would want to set this to the appropriate value based
	//         on objects added to the scene before the first user input
	this.nextIndex = 0;

	// Reset our data structure.  (Simple in this case)
	this.stackTop = 0;
}
Next up, the callbacks. Note that we don't do any action directly on a callback -- instead, we use the implementAction method, which takes a bound function (using the bind method) and a parameter, and them calls that function using that parameter. implementAction also saves a list of all actions that have been performed, so that undo will work nicely.
SimpleStack.prototype.pushCallback = function()
{
	var pushedValue = this.pushField.value;

	if (pushedValue != "")
	{
		this.pushField.value = "";
		this.implementAction(this.push.bind(this), pushedValue);
	}

}

SimpleStack.prototype.popCallback = function()
{
	this.implementAction(this.pop.bind(this), "");
}
Finally, we get to the actual meat of our visualization -- the code that does the work. Note that we are mostly just implementing the action, using some calls to cmd to document what we are doing.
SimpleStack.prototype.push = function(pushedValue)
{
	this.commands = [];

	this.stackID[this.stackTop] = this.nextIndex++;

	this.cmd("CreateRectangle", this.stackID[this.stackTop],
			                    pushedValue,
								SimpleStack.ELEMENT_WIDTH,
								SimpleStack.ELEMENT_HEIGHT,
								SimpleStack.INSERT_X,
			                    SimpleStack.INSERT_Y);
	this.cmd("SetForegroundColor", this.stackID[this.stackTop], SimpleStack.FOREGROUND_COLOR);
	this.cmd("SetBackgroundColor", this.stackID[this.stackTop], SimpleStack.BACKGROUND_COLOR);
	this.cmd("Step");
	var nextXPos = SimpleStack.STARTING_X + this.stackTop * SimpleStack.ELEMENT_WIDTH;
	var nextYPos = SimpleStack.STARTING_Y;
	this.cmd("Move", this.stackID[this.stackTop], nextXPos, nextYPos);
	this.cmd("Step"); // Not necessary, but not harmful either
	this.stackTop++;
	return this.commands;
}

SimpleStack.prototype.pop = function(unused)
{
	this.commands = [];

	if (this.stackTop > 0)
	{
		this.stackTop--;

		this.cmd("Move", this.stackID[this.stackTop], SimpleStack.INSERT_X, SimpleStack.INSERT_Y);
		this.cmd("Step");
		this.cmd("Delete", this.stackID[this.stackTop]);
		this.cmd("Step");

		// OPTIONAL:  We can do a little better with memory leaks in our own memory manager by
		//            reclaiming this memory.  It is recommened that you *NOT* do this unless
		//            you really know what you are doing (memory management leads to tricky bugs!)
		//            *and* you really need to (very long runnning visualizaitons, not common)
		//            Because this is a stack, we can reclaim memory easily.  Most of the time, this
		//            is not the case, and can be dangerous.
		// nextIndex = this.stackID[this.stackTop];
	}
	return this.commands;
}
Almost done! Some code to enable / disable our algorithm controls while the animation is running...
// Called by our superclass when we get an animation started event -- need to wait for the
// event to finish before we start doing anything
SimpleStack.prototype.disableUI = function(event)
{
	for (var i = 0; i < this.controls.length; i++)
	{
		this.controls[i].disabled = true;
	}
}

// Called by our superclass when we get an animation completed event -- we can
/// now interact again.
SimpleStack.prototype.enableUI = function(event)
{
	for (var i = 0; i < this.controls.length; i++)
	{
		this.controls[i].disabled = false;
	}
}
Finally, some boilerplate to get everything started. You can pretty much cut and paste the following into your own code, replacing SimpleStack with your function:
////////////////////////////////////////////////////////////
// Script to start up your function, called from the webapge:
////////////////////////////////////////////////////////////

var currentAlg;

function init()
{
	var animManag = initCanvas();
	currentAlg = new SimpleStack(animManag, canvas.width, canvas.height);

}
And we're done. We just need to create the appriate HTML file to enbed this in, and we're good to go.

HTML Template

This visualization system is a combination of HTML and javascript -- you need a webpage to embed the javascript, and that webpage needs the following items: The eaiest solition is just to use the following template, changing the values that are specific to your application The template file is reproduced below, with the changes you need to make highlighted in red
<!DOCTYPE html>
<html>
    <head>

        <title>
           Place your title here
        </title>

        <!-- css sheet for how the page is laid out -->

        <link rel="stylesheet" href="visualizationPageStyle.css">


        <!-- jqueury stuff.  Only used for the animation speed slider. -->
        <link rel="stylesheet" href="ThirdParty/jquery-ui-1.8.11.custom.css">

        <script src="ThirdParty/jquery-1.5.2.min.js"></script>
        <script src="ThirdParty/jquery-ui-1.8.11.custom.min.js"></script>

        <!-- Javascript for the actual visualization code -->
        <script type = "text/javascript" src = "AnimationLibrary/CustomEvents.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/UndoFunctions.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/AnimatedObject.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/AnimatedLabel.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/AnimatedCircle.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/AnimatedRectangle.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/AnimatedLinkedList.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/HighlightCircle.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/Line.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/ObjectManager.js"> </script>
        <script type = "text/javascript" src = "AnimationLibrary/AnimationMain.js"> </script>
        <script type = "text/javascript" src = "AlgorithmLibrary/Algorithm.js"> </script>
        <script type = "text/javascript" src = "Place path to your javascript file here"> </script>


     </head>

    <body onload="init();" class="VisualizationMainPage">

        <div id = "container">

            <div id="header">
                <h1>Place your Header here </h1>
            </div>

            <div id = "mainContent">

                <div id = "algoControlSection">
                    <!-- Table for buttons to control specific animation (insert/find/etc) -->
                    <!-- (filled in by javascript code specific to the animtion) -->
                    <table id="AlgorithmSpecificControls"> </table>
                </div>

                    <!-- Drawing canvas where all animation is done.  Note:  can be resized in code -->

                <canvas id="canvas" width="1000" height="500"></canvas>

                <div id = "generalAnimationControlSection">
                    <!-- Table for buttons to control general animation (play/pause/undo/etc) ->
                    <!-- (filled in by javascript code, specifically AnimationMain.js)  -->

                    <table id="GeneralAnimationControls">  </table>
                </div>

            </div> <!-- mainContent -->

            <div id="footer">
                <p><a href="Algorithms.html">Algorithm Visualizations</a></p>
            </div>

        </div><!-- container -->
    </body>
</html>

Quirks and Advanced Techniques

Object Display Order

If two object overlap, which one is placed on top? The animation system uses the following rules to determine draw order:
  1. All non-highlighted items are drawn before all highlighted items (so highlighted items will appear on top of non-highlighted items)
  2. All items with the same highlight state are drawn in order of their identifiers (so objects with larger identifiers will be drawn in front of objects with small identifiers)
This system is not terribly sophisticated, but it works well enough. You just need to be sure that if you want object A to appear in front of object B, then object A has to have a higher object ID. If a more sophisticated system is required, this may be modified in a future release.

Debugging

Developing in javascript? Firebug is a very fine (and very free!) javascript debugger. However, there can be a problem with breakpoints. The animations in this system rely heavily on the javascript setTimeout command. If a timeout is set, and then firebug hits a breakpoint, the timeout will be lost. Thus, hitting a breakpoint in the wrong piece of code (pretty much anything in AnimationMain.js), will cause the animation to stop. I've managed the use of setTimeout calls so that hitting a breakpoint in code that uses the animation libraries (as opposed to code in the libraries themselves) should not cause this problem.