Source Code
You can download the complete source for the HTML5/Javascript version of the visualizations (both compressed and uncompressed) here:
A few notes / warnings:
A few notes / warnings:
-
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 w3-code jsHigh.
-
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:
- Creates any appropriate controls to control you visualization (inserting elements, deletig elements, etc)
-
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
- 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:
-
Call the superclass counstructor. Note the syntax, it's a little odd,
but we are forcing javascript into an tradtional object-oriented paradigm, so it will
complain a little at us.
- Add necessary controls
-
-
Initialize our "Memory Manager". For the most part, we will use a very
simple memory manager -- the old Pascal-style "Never free" memory manager. Start the
free list at 0, and then increment it every time you need a new piece of memory.
- Initialize any data structures we will be using
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:
-
This reset method is called, which resets the state of your object to exactly how it was
before any operations were performed
-
All of the operations up until the last one are replayed (though the animation information is
thrown away
- We end up in the same state that we were in right before the last operation was done
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 <;&rt;.
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. Creat
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.
- CreateCircle: objectID, label, [initial_x, initial_y]
-
objectID: Non-negative integer that represents the ID of this object. Must be different from any
ID currently active. Should be as small as posslbe for better performance.
-
label: the label that appears in the middle of the circle. It may contain end of line (\n) characters,
which allows you to place a multi-line label in the circle. Labels are centered in circles.
- initial_x: (optional, defaults to 0) the initial x position of the circle
- initial_y: (optional, defaults to 0) the initial u position of the circle
- CreateRectange: objectID, label, width, height, [initial_x, initial_y, xJustify, yJustufy, backgroundColor, foregroundColor]
-
objectID: Non-negative integer that represents the ID of this object. Must be different from any
ID currently active. Should be as small as posslbe for better performance.
-
label: the label that appears in the middle of the rectangle. It may contain end of line (\n) characters,
which allows you to place a multi-line label in the rectangle. Labels are centered in rectangles.
- width: The width of the rectangle, in pixels
- height: The height of the rectangle, in pixels
- initial_x: (optional, defaults to 0) the initial x position of the rectangle
- initial_y: (optional, defaults to 0) the initial u position of the rectangle
-
xJustify: (optional, defaults to "center"). One of "center", "left", "right" -- If the rectangle is at location (x, y),
does x stand for the left, center, or right of the rectangle
-
yJustify: (optional, defaults to "center"). One of "center", "top", "bottom" -- If the rectangle is at location (x,y0 does
y stand for the top, center, or bottom of the rectangle
-
backgroundColor: The initial color of the background of the rectangle, using HTML colors (#FF0000 for red, #00FF00 for green,
and so on)
-
backgroundColor: The initial color of the forground of the rectangle, using HTML colors (#FF0000 for red, #00FF00 for green,
and so on)
-
CreateHighlightCircle: objectID, color, [initial_x, initial_y, radius]
A highlight circle is much like a standard circle, except that it has no label, and no background color, so that
it does not obscure other objects like a circle does.
-
objectID: Non-negative integer that represents the ID of this object. Must be different from any
ID currently active. Should be as small as posslbe for better performance.
-
color: The initial color of the circle, using HTML colors (#FF0000 for red, #00FF00 for green,
and so on)
- initial_x: (optional, defaults to 0) the initial x position of the circle
- initial_y: (optional, defaults to 0) the initial u position of the circle
- radius: (optional, defaults to 20) The radius of the circle.
- CreateLabel: objectID, label, [initial_x, initial_x, centered]
-
objectID: Non-negative integer that represents the ID of this object. Must be different from any
ID currently active. Should be as small as posslbe for better performance.
-
label: the text of the label. It may contain end of line (\n) characters,
which allows you to place a multi-line labels.
- initial_x: (optional, defaults to 0) the initial x position of the label
- initial_y: (optional, defaults to 0) the initial y position of the label
- centered: (optional, defaults to true) true or 1 if the label should be centered, false or 0 if it should not.
-
CreateLinkedList: objectID, label, width, height, [initial_x, initial_y, linkPercent, verticalOrientation, linkPosEnd, numLabels]
-
objectID: Non-negative integer that represents the ID of this object. Must be different from any
ID currently active. Should be as small as posslbe for better performance.
- label: The label inside this linked list element (or the first label, if there are more than one)
- width: The width of the linked list element, in pixels
- height: The height of the linked list element, in pixels
- initial_x: (optional, defaults to 0) the initial x position of the linked list element
- initial_y: (optional, defaults to 0) the initial y position of the linked list element
- linkPercent: (optional, defaults to 0.25) The percentage of the linked list element that the outgoing pointer takes up.
- verticalOrientation: (optional, defaults to true). Should the linked list element be vertial (true) or horizontal (false)
-
linkPosEnd: (optional, defaults to false). Should the poiner appear at the bottom or left of the linked list element (true), or the
top or right of the linked list element (false)
-
numLabels: (optional, defaults to 1). The number of labels that the linked lists element can hold. See the adjacency
list implementat of a graph visualization for an example of a linked list element with more than 1 label.
- CreateBTreeNode: objectID, widthPerLabel, height, numLabels, inital_x, initial_y, backgroundColor, foregroundColor
Somewhat special-purpose animated object created for B-Trees. Single rectangle containing any number of labels, with no dividing
lines between the labels. Edges can be attached between each label, and to the left of the leftmost label, and to the right of the rightmost
label. See the BTree and B+ Tree visualizations for examples.
-
objectID: Non-negative integer that represents the ID of this object. Must be different from any
ID currently active. Should be as small as posslbe for better performance.
- widthPerLabel: The width of the B-Tree node is the number of labels * the width per label. Value is in pixels.
- height: Height of the B-Tree node in pixels
- numLabels: The number of labels in the BTree node.
- initial_x: The initial x position of the B-Tree node
- initial_y: The initial y position of the B-Tree node
-
backgroundColor: The initial color of the background of the rectangle, using HTML colors (#FF0000 for red, #00FF00 for green,
and so on)
-
backgroundColor: The initial color of the forground of the rectangle, using HTML colors (#FF0000 for red, #00FF00 for green,
and so on)
- Delete: objectID
-
objectID: The ID of the object to delete. All edges incident on this object will be removed. (If the delete is undone, then
all such edges will be restored). Once an Animated Element has been deleted, its ID is free to be used again.
Note that overly complicated ID management (which is really memory management, since IDs are just indicies into a
"memory array" of active animated objects) is not necessarily recommended, since it can lead to some subtle bugs.
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
- Move: objectID, toX, toY
Move the object smoothly over the next step from the current position to
the new position
-
objectID: The ID of the object to move. The object must exists, or an exception will be
thrown
- toX: The new X location to move to
- toY: The new Y location to move to
- SetPosition: objectID, toX, toY
Move the object immediately to the new position at the beginning of the next step
-
objectID: The ID of the object to move. The object must exists, or an exception will be
thrown
- toX: The new X location to move to
- toY: The new Y location to move to
- SetForegroundColor: objectID, color
Sets the foreground color (outline color and label color) of an object. Note that if
an object has several labels this will set the color of
all labels.
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- color: New foreground color
- SetBackgroundColor: objectID, color
Sets the background color of current object. Note that if
an object has several labels this will set the color of an object.
-
objectID: The ID of the object to modify. The object must exist, or an exception will be
thrown
- color: New background color
- SetHighlight: objectID, highlightVal
Mark an object as either highlighted or unhighlighted, based on the value of highlightVal.
objects that are highlighted will pulse red. Any object can be highlighted (thought labels
are slightly harder to read when highlighted) Note that if an object is left highlighted
after an animation is ended, it will not pulse until the animation starts again. Edges
can be highlighted using the highlight edge command.
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- highlightVal: 1 or true, turn on highlighting. 0 or false, turn off highlighting.
- SetText: objectID, newText, [textIndex]
Sets the value of the label associated with the object (the printing withing a circle, for instance).
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- newText: The new text of the label
-
textIndex: (optional, defaults to 0) Index of the text label to change. Only used in
objects that have more than one text label (B-Tree nodes, Linked List nodes). If the object does
not support multiple labels, this is a no-op.
- SetAlpha: objectID
Sets the alpha (transparency) of the object. 0 is completely transparent, 1 is completely opaque
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- SetHeight: objectID, newHeight
Sets the height (in pixels) of the object.
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- newHeight: The new height of the object.
- SetWidth: objectID, newWIdth
Sets the width (in pixels) of the object.
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- newWidth: The new width of the object.
- SetTextColor: objectID, newColor, [textIndex]
Sets the color of the label associated with the object
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- newColor: The new color to set. As with all colors, should be a html color string
-
textIndex: (optional, defaults to 0) If the object contain multiple labels (such as a linked-list node,
or a B-Tree node) determine which label to change the color of. If the object only has one label, this parameter
is ignored.
- SetNull: objectID, nullValue
Currently only used for linked-list elements. Should the area set aside for the pointer in the
linked list object be drawn as a null pointer (slash through the field)? This should probably
be automated (draw the slash if and only if the node is not connected to anything), but for now
this must be done manually.
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- nullValue: 0 or false for do not draw the slash, 1 or true for draw the slash.
- SetNumElements: objectID, numElements
Currently only used for B-Tree nodes. Changes the number of labels stored in this B-tree node.
Should probably be extended to at least Linked-list nodes.
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- numElements: integer, the number of elements this B-Tree node should have
- AlignRight: object1ID, object2ID
Align object1 so that it is immediately to the right of object2. Very handy for lining up labels
(where you don't necessarily know the width of the label), but can be used with any two objects.
-
object1ID: The ID of the object to move. The object must exists, or an exception will be
thrown
-
object2ID: The ID of the object used to align object1. The object must exists, or an exception will be
thrown
- AlignLeft: object1ID, object2ID
Align object1 so that it is immediately to the left of object2. Very handy for lining up labels
(where you don't necessarily know the width of the label), but can be used with any two objects.
-
object1ID: The ID of the object to move. The object must exists, or an exception will be
thrown
-
object2ID: The ID of the object used to align object1. The object must exists, or an exception will be
thrown
- AlignTop: object1ID, object2ID
Align object1 so that it is immediately on top of of object2. Very handy for lining up labels
(where you don't necessarily know the width of the label), but can be used with any two objects.
-
object1ID: The ID of the object to move. The object must exists, or an exception will be
thrown
-
object2ID: The ID of the object used to align object1. The object must exists, or an exception will be
thrown
- AlignBottom: object1ID, object2ID
Align object1 so that it is immediately below object2. Very handy for lining up labels
(where you don't necessarily know the width of the label), but can be used with any two objects.
-
object1ID: The ID of the object to move. The object must exists, or an exception will be
thrown
-
object2ID: The ID of the object used to align object1. The object must exists, or an exception will be
thrown
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.
- Connect: fromID, toID, [linkColor, curve, directed, label, anchorPosition]
,
- fromID: The ID of the object at the tail of the new edge
- toID: The ID of the object at the head of the new edge
- linkColor: (optional, defaults to "#000000") The color of the edge
- linkColor: (optional, defaults to false) true for a diected edge, false for an undirected edge
-
curve: (optional, defaults to 0.0) The "curviness" of the edge. 0.0 is perfectly straight, positive values
arc to the right, negative values arc to the left.
- directed (optional, defaults to true). True if the edge is directed, false if the edge is undirected
-
label (optional, defaults to ""). The label that appears along the edge (useful for things like edge
costs in graphs)
-
anchorPosition (optional, defaults to 0) If the edge could have more than one attachment
postion at the "from" node, (currently only used for B-Tree nodes, but could well be expanded to things like
doubly-linked lists) the index of the attachment position to use. Ignored for animated objects that
do not have multiple attachment positions
- Disconnect: fromID, toID
Removes an edge between two elements. If there is no edge, then this operation is a no-op.
- fromID: The ID of the "From" direction of the edge
- toID: The ID of the "To" direction of the edge
Note that even "undirected" edges have a "from" and a "to" -- determined by how the
edge was created using the Connect command.
- SetAlpha: objectID
Sets the alpha (transparency) of the object. 0 is completely transparent, 1 is completely opaque
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
- SetEdgeHighlight: fromID, toID, highlightVal
Mark an edge as either highlighted or unhighlighted, based on the value of highlightVal.
Edges that are highlighted will pulse red.
- fromID: The ID of the "From" direction of the edge
- toID: The ID of the "To" direction of the edge
- higlightVal: 0 or false, turn of higlighting. 1 or true, turn on highlighting.
Special Commands
- Step: <No parameters>
The step command allows you to keep everything from happening at once. The way that most
animations will work is that you will create a group of objects, then do a step, then do
some movements, then do a step, then do more movements, then do a step, and so on. All
commands that appear between adjacent steps will happen simultaneously. Each step represents where
the animation will pause when it is in single-step mode.
- SetLayer objectID, newLayer
Sets the layer of the object. All objects default to layer 0, and the "shown" layer always defaults
to 0. You can change the layers of different objects, and then change the list of which layers
are currently shown, to show or hide objects dynamically. (This is often useful for allowing the
user to show or hide information, or to alternate between different versions of a representation).
An object will only appear if its layer is one of the layers listed to be shown. An edge will
only appear of each of the objects that it connect are to be shown. While commands cannot be executed
while an animation is running, the global set of visible layers can be changed while an animation is running
-
objectID: The ID of the object to modify. The object must exists, or an exception will be
thrown
-
layer: The new layer for this object. Each object must live in one and only one layer
(though any combination of layers can be shown at any given time)
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 w3-code jsHigh, 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:
-
A bunch of <script> tags in the header to load oll of the necessary scripts. These files need to be
included in the correct order, based on dependancies. (It would be nice if
javascript had a standard #include-like mechanism to avoid manually inserting all of these files
in the HTM. While there are some ways around
it -- dynamically inserting the <script> tags using javascript calls --
not all browsers seem to work with these somewhat hacky methods. A brute-force inclusion
of all the files, in the proper order, works everywhere)
- An empty table with the id "algoControlSection" where the algorithm specific controls are to be placed
- A canvas element, where the animations will appear
- An empty table with the id "GeneralAnimationControls" where general animation controls are to be placed
- An onload = "init();" command in the body tag to kick everything off
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
<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 = "application/javascript" src = "AnimationLibrary/CustomEvents.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/UndoFunctions.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/AnimatedObject.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/AnimatedLabel.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/AnimatedCircle.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/AnimatedRectangle.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/AnimatedLinkedList.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/HighlightCircle.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/Line.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/ObjectManager.js"> </script>
<script type = "application/javascript" src = "AnimationLibrary/AnimationMain.js"> </script>
<script type = "application/javascript" src = "AlgorithmLibrary/Algorithm.js"> </script>
<script type = "application/javascript" src = "Place path to your javasript 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 w3-code jsHigh, 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:
-
All non-highlighted items are drawn before all highlighted items
(so highlighted items will appear on top of non-highlighted items)
-
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.