/**
* @class Visualiser implementation.
* @author Jakub Melezinek
*
* @constructor
* @param {jsgl.Panel} panel Panel for visualisation.
*/
btv.Visualiser = function(panel) {
/**
* @private
* @type {jsgl.Panel}
*/
this.panel = panel;
/**
* @private
* @type {btv.AnimatorsArrayList}
*/
this.animation = undefined;
/**
* @private
* @type {btv.BinaryTreeNode}
*/
this.selectedNode = null;
/**
* @private
* @type {btv.Visualiser.ArrayElement}
*/
this.arrayElem = null;
/**
* @TODO change if holder element size is changed
*
* @private
* @type {Number}
*/
this.width = panel.getHolderElement().clientWidth;
/**
* @TODO change if holder element size is changed
*
* @private
* @type {Number}
*/
this.height = panel.getHolderElement().clientHeight;
/**
* @private
* @type {Number}
*/
this.verticalSpacing = this.height/6;
/**
* Speed of moving of graphic nodes in px/ms.
*
* @private
* @type {Number}
*/
this.moveSpeed = 0.25;
/**
* Duration of the show in ms.
*
* @private
* @type {Number}
*/
this.stepDuration = 650;
/**
* @private
* @type {Number}
*/
this.animationFPS = 50;
/**
* Radius of circles in px.
*
* @private
* @type {Number}
*/
this.circleRadius = this.verticalSpacing/4;
/**
* Clonned stroke objects for nodes (circles).
*
* @private
* @type {jsgl.stroke.SolidStroke}
*/
this.circleStroke = new jsgl.stroke.SolidStroke();
this.circleStroke.setColor("#1d1b19");
this.circleStroke.setWeight(3);
/**
* Clonned stroke objects for selected node (circle).
*
* @private
* @type {jsgl.stroke.SolidStroke}
*/
this.selectedCircleStroke = new jsgl.stroke.SolidStroke();
this.selectedCircleStroke.setColor("#9b251b");
this.selectedCircleStroke.setWeight(4);
/**
* Clonned fill objects for nodes (circles).
*
* @private
* @type {jsgl.fill.SolidFill}
*/
this.circleFill = new jsgl.fill.SolidFill();
this.circleFill.setColor('#cdf');
/**
* Clonned stroke objects for lines.
*
* @private
* @type {jsgl.stroke.SolidStroke}
*/
this.lineStroke = new jsgl.stroke.SolidStroke();
this.lineStroke.setColor("#1d1b19");
this.lineStroke.setWeight(2);
}
/**
* Go to parent arrow - ⇑.
*
* @public
* @static
* @type {String}
*/
btv.Visualiser.upwardsDoubleArrow = String.fromCharCode(0x21d1);
/**
* Go to parent arrow - ⇑.
*
* @public
* @static
* @type {String}
*/
btv.Visualiser.downwardsDoubleArrow = String.fromCharCode(0x21d3);
/**
* Go to right child arrow - ⇘.
*
* @public
* @static
* @type {String}
*/
btv.Visualiser.southEastDoubleArrow = String.fromCharCode(0x21d8);
/**
* Go to left child arrow - ⇙.
*
* @public
* @static
* @type {String}
*/
btv.Visualiser.southWestDoubleArrow = String.fromCharCode(0x21d9);
/**
* Came from right child arrow - ↖.
*
* @public
* @static
* @type {String}
*/
btv.Visualiser.northWestArrow = String.fromCharCode(0x2196);
/**
* Came from left child arrow - ↗.
*
* @public
* @static
* @type {String}
*/
btv.Visualiser.northEastArrow = String.fromCharCode(0x2197);
/**
* @public
*/
btv.Visualiser.prototype.getMoveSpeed = function() {
return this.moveSpeed;
}
/**
* @public
* @param {Number} speed Speed of move of graphic elements in animations in px/ms.
*/
btv.Visualiser.prototype.setMoveSpeed = function(speed) {
this.moveSpeed = speed;
}
/**
* @public
*/
btv.Visualiser.prototype.getStepDuration = function() {
return this.stepDuration;
}
/**
* @public
* @param {Number} duration Duration of show animations in ms.
*/
btv.Visualiser.prototype.setStepDuration = function(duration) {
this.stepDuration = duration;
}
/**
* Play animation.
*
* @public
*/
btv.Visualiser.prototype.playAnimation = function() {
if(this.animation == null) {
return false;
}
this.animation.play();
return true;
}
/**
* Pause animation.
*
* @public
*/
btv.Visualiser.prototype.pauseAnimation = function() {
if(this.animation == null) {
return false;
}
this.animation.pause();
return true;
}
/**
* Skip whole animation.
*
* @public
*/
btv.Visualiser.prototype.skipAnimationForward = function() {
if(this.animation == null) {
return false;
}
var stopped = this.animation.stop(); // stop animator
this.animation.setCurrentAnimatorIndex(this.animation.getCount()); // after the last // stop animation
//this.redrawTree();
return stopped;
}
/**
* Factory method, that create btv.elements.NodeElement.
* This element represents a node as a group of a circle and a label.
* @see btv.elements.NodeElement
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {jsgl.Vector2D} location
* @returns {btv.elements.NodeElement} Extended jsgl.elements.GroupElement representing a node.
*/
btv.Visualiser.prototype.createNodeElement = function(node, location) {
// @author this part of the code is copy-pasted from JSGL library.
var domPresenter;
if(jsgl.util.BrowserInfo.supportsSvg) {
domPresenter = new jsgl.elements.SvgGroupDomPresenter(this.panel.ownerDocument);
}
else {
domPresenter = new jsgl.elements.NonSvgGroupDomPresenter(this.panel.ownerDocument);
}
var nodeElem = new btv.elements.NodeElement(domPresenter, this, node.getValue(), location);
if(node.selectable !== false) { // all selectable if not exactly false
// add click listener
nodeElem.addClickListener(jsgl.util.delegate(this, this.selectNodeListener));
nodeElem.setCursor(jsgl.Cursor.POINTER);
// all elements in group have to have reference to node because of selectNodeListener function
nodeElem.circle.node = node;
nodeElem.label.node = node;
}
return nodeElem;
}
/**
* Factory method, that create btv.elements.StepElement.
* This element represents an assistant node as a group of a circle and a label.
* @see btv.elements.StepElement
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {jsgl.Vector2D} location
* @returns {btv.elements.NodeElement} Extended jsgl.elements.GroupElement representing a node.
*/
btv.Visualiser.prototype.createStepElement = function(node, location) {
// @author this part of the code is copy-pasted from JSGL library.
var domPresenter;
if(jsgl.util.BrowserInfo.supportsSvg) {
domPresenter = new jsgl.elements.SvgGroupDomPresenter(this.panel.ownerDocument);
}
else {
domPresenter = new jsgl.elements.NonSvgGroupDomPresenter(this.panel.ownerDocument);
}
var stepElem = new btv.elements.StepElement(domPresenter, this, node.getValue(), location);
return stepElem;
}
/**
* Factory method, that create btv.elements.EdgeElement.
* This element represents an edge from a parent node to a child node as a group of lines.
* @see btv.elements.EdgeElement
*
* @public
* @param {btv.BinaryTreeNode} from
* @param {btv.BinaryTreeNode} to
* @returns {btv.elements.EdgeElement} Extended jsgl.elements.GroupElement representing an edge.
*/
btv.Visualiser.prototype.createEdgeElement = function(from, to) {
// @author this part of the code is copy-pasted from JSGL library.
var domPresenter;
if(jsgl.util.BrowserInfo.supportsSvg) {
domPresenter = new jsgl.elements.SvgGroupDomPresenter(this.panel.ownerDocument);
}
else {
domPresenter = new jsgl.elements.NonSvgGroupDomPresenter(this.panel.ownerDocument);
}
var edgeElem = new btv.elements.EdgeElement(domPresenter, this, from, to);
return edgeElem;
}
/**
* Factory method, that create btv.elements.ComparisonSignElement.
* This element represents a comparison sign as a group of a rectangle and a label.
* @see btv.elements.ComparisonSignElement
*
* @public
* @param {String} text
* @param {jsgl.Vector2D} location
* @returns {btv.elements.ComparisonSignElement} Extended jsgl.elements.GroupElement representing a comparison sign.
*/
btv.Visualiser.prototype.createComparisonSignElement = function(text, location) {
// @author this part of the code is copy-pasted from JSGL library.
var domPresenter;
if(jsgl.util.BrowserInfo.supportsSvg) {
domPresenter = new jsgl.elements.SvgGroupDomPresenter(this.panel.ownerDocument);
}
else {
domPresenter = new jsgl.elements.NonSvgGroupDomPresenter(this.panel.ownerDocument);
}
var ComparisonSignElem = new btv.elements.ComparisonSignElement(domPresenter, this, text, location);
return ComparisonSignElem;
}
/**
* Factory method, that create btv.elements.ArrayElement.
* This element represents an array as a group of rectangles and labels.
* @see btv.elements.ArrayElement
*
* @public
* @param {Number} length
* @returns {btv.elements.ArrayElement} Extended jsgl.elements.GroupElement representing an array.
*/
btv.Visualiser.prototype.createArrayElement = function(length) {
// @author JSGL
var domPresenter;
if(jsgl.util.BrowserInfo.supportsSvg) {
domPresenter = new jsgl.elements.SvgGroupDomPresenter(this.panel.ownerDocument);
}
else {
domPresenter = new jsgl.elements.NonSvgGroupDomPresenter(this.panel.ownerDocument);
}
var arrayElem = new btv.elements.ArrayElement(domPresenter, this, length);
return arrayElem;
}
/**
* Animate (re)creating and adding/showing node's nodeElement at given location.
*
* @private
* @param {btv.BinaryTreeNode} node Which node will be displayed.
* @param {jsgl.Vector2D} location Where the node will be displayed.
* @param {Boolean} show True if show - set animation duration, false if just recreate and readd
*/
btv.Visualiser.prototype.animateShowNodeElemAtLoc = function(node, location, show) {
var visualiser = this; // temporary visualiser reference for anonymous function
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
if(show == true) {
animator.setDuration(visualiser.stepDuration);
} else {
animator.setDuration(1);
}
// safely remove old nodeElement from panel if exists
visualiser.removeElement(node.nodeElement);
// create and add nodeElement
node.nodeElement = visualiser.createNodeElement(node, location);
visualiser.addElement(node.nodeElement);
});
}
/**
* Animate (re)creating and adding/showing stepElement at given location.
*
* @private
* @param {btv.BinaryTreeNode} node Which node will be displayed.
* @param {jsgl.Vector2D} location Where the node will be displayed.
* @param {Boolean} show True if show - set animation duration, false if just recreate and readd
*/
btv.Visualiser.prototype.animateShowStepElemAtLoc = function(node, location, show) {
var visualiser = this; // temporary visualiser reference for anonymous function
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
if(show == true) {
animator.setDuration(visualiser.stepDuration);
} else {
animator.setDuration(1);
}
// safely remove old nodeElement from panel if exists
visualiser.removeElement(node.nodeElement);
// create and add nodeElement
node.nodeElement = visualiser.createStepElement(node, location);
visualiser.addElement(node.nodeElement);
});
}
/**
* Animate (re)creating and adding node's nodeElement his index location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed on his normal location.
*/
btv.Visualiser.prototype.animateAddNodeElem = function(node) {
this.animateShowNodeElemAtLoc(node, this.getNodeIndexLocation(node.getIndex()), false);
}
/**
* Animate (re)creating and showing node's nodeElement at his index location location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed on his normal location.
*/
btv.Visualiser.prototype.animateShowNodeElem = function(node) {
this.animateShowNodeElemAtLoc(node, this.getNodeIndexLocation(node.getIndex()), true);
}
/**
* Animate (re)creating and adding node's nodeElement at given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed on his normal location.
* @param {btv.BinaryTreeNode|Number} nodeAt
*/
btv.Visualiser.prototype.animateAddNodeElemAt = function(node, nodeAt) {
this.animateShowNodeElemAtLoc(node, this.getNodeIndexLocation(nodeAt), false);
}
/**
* Animate (re)creating and showing node's nodeElement at given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed on his normal location.
* @param {btv.BinaryTreeNode|Number} nodeAt
*/
btv.Visualiser.prototype.animateShowNodeElemAt = function(node, nodeAt) {
this.animateShowNodeElemAtLoc(node, this.getNodeIndexLocation(nodeAt), true);
}
/**
* Animate (re)creating and adding node's nodeElement at given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed on given location.
* @param {btv.BinaryTreeNode|Number} nodeAt
*/
btv.Visualiser.prototype.animateAddAssistNodeElemAt = function(node, nodeAt) {
this.animateShowStepElemAtLoc(node, this.getNodeIndexLocation(nodeAt), false);
}
/**
* Animate (re)creating and showing node's nodeElement at given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed on given location.
* @param {btv.BinaryTreeNode|Number} nodeAt
*/
btv.Visualiser.prototype.animateShowAssistNodeElemAt = function(node, nodeAt) {
this.animateShowStepElemAtLoc(node, this.getNodeIndexLocation(nodeAt), true);
}
/**
* Animate (re)creating and adding node's nodeElement next to given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed.
* @param {btv.BinaryTreeNode|Number} nodeNextTo The node will be displayed next to this node.
*/
btv.Visualiser.prototype.animateAddNodeElemNextTo = function(node, nodeNextTo) {
this.animateShowNodeElemAtLoc(node, this.getNextToNodeLocation(nodeNextTo), false);
}
/**
* Animate (re)creating and showing node's nodeElement next to given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed.
* @param {btv.BinaryTreeNode|Number} nodeNextTo A node or an index of a node next to which the first node will be displayed.
*/
btv.Visualiser.prototype.animateShowNodeElemNextTo = function(node, nodeNextTo) {
this.animateShowNodeElemAtLoc(node, this.getNextToNodeLocation(nodeNextTo), true);
}
/**
* Animate (re)creating and adding node's nodeElement next to given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed.
* @param {btv.BinaryTreeNode|Number} nodeNextTo A node or an index of a node next to which the first node will be displayed.
*/
btv.Visualiser.prototype.animateAddAssistNodeElemNextTo = function(node, nodeNextTo) {
this.animateShowStepElemAtLoc(node, this.getNextToNodeLocation(nodeNextTo), false);
}
/**
* Animate (re)creating and showing node's nodeElement next to given node location.
*
* @public
* @param {btv.BinaryTreeNode} node Which node will be displayed.
* @param {btv.BinaryTreeNode|Number} nodeNextTo A node or an index of a node next to which the first node will be displayed.
*/
btv.Visualiser.prototype.animateShowAssistNodeElemNextTo = function(node, nodeNextTo) {
this.animateShowStepElemAtLoc(node, this.getNextToNodeLocation(nodeNextTo), true);
}
/**
* Animate swapping given nodes.
*
* @public
* @param {btv.BinaryTreeNode} node1
* @param {btv.BinaryTreeNode} node2
*/
btv.Visualiser.prototype.animateSwapNodeElems = function(node1, node2) {
var visualiser = this; // temporary reference becouse of anonymous functions
var tmp = {}; // temporary object to hold variables
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
node1.nodeElement.setZIndex(btv.elements.maxZIndex);
node2.nodeElement.setZIndex(btv.elements.maxZIndex);
tmp.point1 = node1.nodeElement.getLocation();
tmp.point2 = node2.nodeElement.getLocation();
tmp.direction = tmp.point2.subtract(tmp.point1);
var time = Math.sqrt(tmp.direction.getX()*tmp.direction.getX() + tmp.direction.getY()*tmp.direction.getY())/visualiser.moveSpeed;
animator.setFps(visualiser.animationFPS);
animator.setDuration(time);
});
animator.addStepListener(function(t) {
tmp.newLocation1 = new jsgl.Vector2D(tmp.point1.getX() + t*tmp.direction.getX(), tmp.point1.getY() + t*tmp.direction.getY());
tmp.newLocation2 = new jsgl.Vector2D(tmp.point2.getX() - t*tmp.direction.getX(), tmp.point2.getY() - t*tmp.direction.getY());
node1.nodeElement.setLocation(tmp.newLocation1);
node2.nodeElement.setLocation(tmp.newLocation2);
});
animator.addEndListener(function() { // swap edges
var tmp;
tmp = node1.edgeElement;
node1.edgeElement = node2.edgeElement;
node2.edgeElement = tmp;
node1.nodeElement.setZIndex(btv.elements.NodeElement.zIndex);
node2.nodeElement.setZIndex(btv.elements.NodeElement.zIndex);
});
}
/**
* Animate moving given nodes' nodeElements and edgeElement to their index locations.
*
* @public
* @param {btv.BinaryTreeNode[]} nodes
*/
btv.Visualiser.prototype.animateMoveToIndexLoc = function(nodes) {
var visualiser = this; // temporary reference becouse of anonymous functions
var tmp = {}; // temporary object to hold variables
tmp.startPoint = new Array();
tmp.direction = new Array();
tmp.time = 0;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.setFps(visualiser.animationFPS);
animator.addStartListener(function() {
var time;
for(var i = 0; i < nodes.length; i++) {
// maximalize z-index
nodes[i].nodeElement.setZIndex(btv.elements.maxZIndex);
if(visualiser.panel.containsElement(nodes[i].edgeElement)) {
nodes[i].edgeElement.setZIndex(btv.elements.maxZIndex);
}
tmp.startPoint[i] = nodes[i].nodeElement.getLocation();
tmp.endPoint = visualiser.getNodeIndexLocation(nodes[i].getIndex());
tmp.direction[i] = tmp.endPoint.subtract(tmp.startPoint[i]);
// same speed -> time of animation depend on distance, t = s/v
time = Math.sqrt(tmp.direction[i].getX()*tmp.direction[i].getX() + tmp.direction[i].getY()*tmp.direction[i].getY())/visualiser.moveSpeed;
tmp.time = Math.max(tmp.time, time);
}
animator.setDuration(tmp.time);
});
animator.addStepListener(function(t) {
for(var i = 0; i < nodes.length; i++) {
tmp.newLocation = new jsgl.Vector2D(tmp.startPoint[i].getX() + t*tmp.direction[i].getX(), tmp.startPoint[i].getY() + t*tmp.direction[i].getY());
nodes[i].nodeElement.setLocation(tmp.newLocation);
if(visualiser.panel.containsElement(nodes[i].edgeElement)) {
nodes[i].edgeElement.setStartEndNode(nodes[i].getParent(), nodes[i]);
}
}
});
animator.addEndListener(function() {
// set z-index to normal
for(var i = 0; i < nodes.length; i++) {
nodes[i].nodeElement.setZIndex(btv.elements.EdgeElement.zIndex);
if(visualiser.panel.containsElement(nodes[i].edgeElement)) {
nodes[i].edgeElement.setZIndex(btv.elements.EdgeElement.zIndex);
}
}
});
}
/**
* Animate moving given node's nodeElement to given location.
*
* @private
* @param {btv.BinaryTreeNode} node
* @param {jsgl.Vector2D} location
*/
btv.Visualiser.prototype.animateMoveAtLoc = function(node, location) {
var visualiser = this; // temporary reference becouse of anonymous functions
var tmp = {}; // temporary object to hold variables
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.setFps(visualiser.animationFPS);
animator.addStartListener(function() {
node.nodeElement.setZIndex(btv.elements.maxZIndex);
tmp.startPoint = node.nodeElement.getLocation();
tmp.endPoint = location;
tmp.direction = tmp.endPoint.subtract(tmp.startPoint);
// same speed -> time of animation depend on distance, t = s/v
var time = Math.sqrt(tmp.direction.getX()*tmp.direction.getX() + tmp.direction.getY()*tmp.direction.getY())/visualiser.moveSpeed;
animator.setDuration(time);
});
animator.addStepListener(function(t) {
tmp.newLocation = new jsgl.Vector2D(tmp.startPoint.getX() + t*tmp.direction.getX(), tmp.startPoint.getY() + t*tmp.direction.getY());
node.nodeElement.setLocation(tmp.newLocation);
});
animator.addEndListener(function() {
node.nodeElement.setZIndex(btv.elements.NodeElement.zIndex);
});
}
/**
* Animate moving given node's nodeElement to given location.
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {btv.BinaryTreeNode|Number} nodeNextTo A node or an index of a node
*/
btv.Visualiser.prototype.animateMoveNextTo = function(node, nodeNextTo) {
this.animateMoveAtLoc(node, this.getNextToNodeLocation(nodeNextTo));
}
/**
* Animate moving given node's nodeElement next to given node location.
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {btv.BinaryTreeNode|Number} nodeTo A node or an index of a node
*/
btv.Visualiser.prototype.animateMoveTo = function(node, nodeTo) {
this.animateMoveAtLoc(node, this.getNodeIndexLocation(nodeTo));
}
/**
* Animate moving given node's nodeElement next to array location.
*
* @public
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.animateMoveNextToArrayElem = function(node) {
var visualiser = this; // temporary reference becouse of anonymous functions
var tmp = {}; // temporary object to hold variables
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.setFps(visualiser.animationFPS);
animator.addStartListener(function() {
node.nodeElement.setZIndex(btv.elements.maxZIndex);
tmp.startPoint = node.nodeElement.getLocation();
tmp.endPoint = visualiser.arrayElem.getLocation().subtract(new jsgl.Vector2D(0, 2*visualiser.circleRadius));
tmp.direction = tmp.endPoint.subtract(tmp.startPoint);
// same speed -> time of animation depend on distance, t = s/v
var time = Math.sqrt(tmp.direction.getX()*tmp.direction.getX() + tmp.direction.getY()*tmp.direction.getY())/visualiser.moveSpeed;
animator.setDuration(time);
});
animator.addStepListener(function(t) {
tmp.newLocation = new jsgl.Vector2D(tmp.startPoint.getX() + t*tmp.direction.getX(), tmp.startPoint.getY() + t*tmp.direction.getY());
node.nodeElement.setLocation(tmp.newLocation);
});
animator.addEndListener(function() {
node.nodeElement.setZIndex(btv.elements.NodeElement.zIndex);
});
}
/**
* Animate (re)creating and adding node's edgeElement.
*
* @public
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.animateAddEdgeElem = function(node) {
var visualiser = this;
var tmp = {};
tmp.parent = node.getParent();
tmp.isRoot = node.isRoot();
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
// remove old edgeElement from panel if exists
visualiser.removeElement(node.edgeElement);
if(!tmp.isRoot) { // root has no parent so no edge
node.edgeElement = visualiser.createEdgeElement(tmp.parent, node);
visualiser.addElement(node.edgeElement);
}
});
}
/**
* Animate (re)creating and adding an arrayElement.
*
* @public
* @param {Number} length
*/
btv.Visualiser.prototype.animateAddArrayElem = function(length) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
// remove old edgeElement from panel if exists
visualiser.removeElement(visualiser.arrayElem);
visualiser.arrayElem = visualiser.createArrayElement(length);
visualiser.addElement(visualiser.arrayElem);
});
}
/**
* Animate removing an arrayElement.
*
* @public
*/
btv.Visualiser.prototype.animateRemoveArrayElem = function() {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
visualiser.removeElement(visualiser.arrayElem);
visualiser.arrayElem = null;
});
}
/**
* Animate (re)creating and showing a comparisonSignElement.
*
* @public
* @param {btv.BinaryTreeNode} node1 Node on the left/top - the moving one.
* @param {Boolean|Number} isGreater True if node1 is greater than or equal to node2, false if is smaller. 1 if is greater, 0 if is equal, -1 if is smaller.
* @param {btv.BinaryTreeNode} node2 Node on the right/botton - the static one.
*/
btv.Visualiser.prototype.animateShowComparSign = function(node1, isGreater, node2) {
var visualiser = this;
var tmp = {};
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(visualiser.stepDuration);
var labelText;
if(typeof(isGreater) == "boolean") {
labelText = isGreater ? ">=" : "<";
}
else {
if(isGreater > 0) {
labelText = ">";
} else if(isGreater < 0) {
labelText = "<";
} else {
labelText = "=";
}
}
var location = new jsgl.Vector2D(
(node1.nodeElement.getX() + node2.nodeElement.getX())/2,
(node1.nodeElement.getY() + node2.nodeElement.getY())/2);
tmp.comparisonSignElem = visualiser.createComparisonSignElement(labelText, location);
visualiser.addElement(tmp.comparisonSignElem);
});
animator.addEndListener(function() {
visualiser.removeElement(tmp.comparisonSignElem);
});
}
/**
* Animate showing of removing a nodeElement and an edgeElement.
*
* @public
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.animateShowRemoveNode = function(node) {
var visualiser = this;
var tmp = {};
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(visualiser.stepDuration);
if(node.edgeElement != null) {
tmp.startlineOpacity = node.edgeElement.getElementAt(0).getStroke().getOpacity();
}
tmp.startStrokeOpacity = node.nodeElement.getElementAt(0).getStroke().getOpacity();
tmp.startFillOpacity = node.nodeElement.getElementAt(0).getFill().getOpacity();
tmp.startLabelOpacity = node.nodeElement.label.getOpacity();
});
animator.addStepListener(function(t) {
if(node.edgeElement != null) {
node.edgeElement.getElementAt(0).getStroke().setOpacity((1-t) * tmp.startlineOpacity);
node.edgeElement.getElementAt(1).getStroke().setOpacity((1-t) * tmp.startlineOpacity);
node.edgeElement.getElementAt(2).getStroke().setOpacity((1-t) * tmp.startlineOpacity);
}
node.nodeElement.getElementAt(0).getStroke().setOpacity((1-t) * tmp.startStrokeOpacity);
node.nodeElement.getElementAt(0).getFill().setOpacity((1-t) * tmp.startFillOpacity);
node.nodeElement.label.setOpacity((1-t) * tmp.startLabelOpacity);
});
animator.addEndListener(function() {
visualiser.removeNodeElements(node); // remove nodeElement, edgeElement
});
}
/**
* Animate removing a nodeElement and an edgeElement.
*
* @public
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.animateRemoveNode = function(node) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
// remove elements from panel
visualiser.removeNodeElements(node);
});
}
/**
* Animate removing given elements.
*
* @public
* @param {Array<jsgl.elements.AbstractElement>}
*/
btv.Visualiser.prototype.animateRemoveElements = function(elements) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
// remove elements from panel
visualiser.removeElements(elements);
});
}
/**
* Animate removing given element.
*
* @public
* @param {jsgl.elements.AbstractElement}
*/
btv.Visualiser.prototype.animateRemoveElement = function(element) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
// remove elements from panel
visualiser.removeElement(element);
});
}
/**
* Animate changing (or showing of changing) node's nodeElement.
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {String} text
* @param {Boolean} struckThrough
* @param {Boolean} show
*/
btv.Visualiser.prototype.animateChangeAssistNodeShowParam = function(node, text, struckThrough, show) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
if(show === false) {
animator.setDuration(1);
} else {
animator.setDuration(visualiser.stepDuration);
}
node.nodeElement.label.setText(text);
node.nodeElement.label.setStruckThrough(struckThrough);
});
}
/**
* Animate showing of changing node's nodeElement.
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {String} text
* @param {Boolean} struckThrough
*/
btv.Visualiser.prototype.animateShowChangeAssistNode = function(node, text, struckThrough) {
this.animateChangeAssistNodeShowParam(node, text, struckThrough, true);
}
/**
* Animate changing node's nodeElement.
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {String} text
* @param {Boolean} struckThrough
*/
btv.Visualiser.prototype.animateChangeAssistNode = function(node, text, struckThrough) {
this.animateChangeAssistNodeShowParam(node, text, struckThrough, false);
}
/**
* Animate showing of inserting to arrayElement.
*
* @public
* @param {btv.BinaryTreeNode} node
* @param {Number} index
*/
btv.Visualiser.prototype.animateShowInsertToArrayElem = function(node, index) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(visualiser.stepDuration);
if(node == null) {
visualiser.arrayElem.setTextAt(index, "n");
} else {
visualiser.arrayElem.setTextAt(index, node.getValue());
}
});
}
/**
* Animate
*
* @private
* @param {btv.BinaryTreeNode} node
* @param {Boolean} show
*/
btv.Visualiser.prototype.animateSelectNodeShowParam = function(node, show) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
if(show) {
animator.setDuration(visualiser.stepDuration);
} else {
animator.setDuration(1);
}
visualiser.setSelectedNode(node);
});
}
/**
* Animate showing of selecting of a given node.
*
* @public
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.animateShowSelectNode = function(node) {
this.animateSelectNodeShowParam(node, true);
}
/**
* Animate selecting of a given node.
*
* @public
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.animateSelectNode = function(node) {
this.animateSelectNodeShowParam(node, false);
}
/**
* Animate showing the tree.
*
* @public
* @param {Number}
*/
btv.Visualiser.prototype.animateShowTree = function(ratio) {
var visualiser = this;
if(ratio == null) {
ratio = 1;
}
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(ratio * visualiser.stepDuration);
});
}
/**
* Animate redrawing given tree.
*
* @public
* @param {btv.BinaryTree} tree
*/
btv.Visualiser.prototype.animateRedrawTree = function(tree) {
var visualiser = this;
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
visualiser.redrawTree(tree);
});
}
/**
* Start animation, that create new btv.AnimatorsArrayList object and fire startAnimationListeners of given algorithm.
*
* @public
* @param {btv.AbstractAlgorithm} algorithm This algorithm's start animation chain listeners will be invoked
*/
btv.Visualiser.prototype.animateStart = function(algorithm) {
// it is just subalgorithm not a start of new algorithm
if(algorithm.isSubalgorithm) {
return;
}
this.animation = new btv.AnimatorsArrayList();
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
algorithm.fireStartAnimationListeners();
});
}
/**
* End animation, that fire endAnimationListeners of given algorithm.
*
* @public
* @param {btv.AbstractAlgorithm} algorithm This algorithm's end animation chain listeners will be invoked
*/
btv.Visualiser.prototype.animateEnd = function(algorithm) {
// it is subalgorithm not a start of new algorithm
if(algorithm.isSubalgorithm) {
return;
}
var animator = new jsgl.util.Animator();
this.animation.add(animator);
animator.addStartListener(function() {
animator.setDuration(1);
});
animator.addEndListener(function() {
algorithm.fireEndAnimationListeners();
});
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* NodeElement click listener - event handler.
*
* @private
* @param {Object} eventArgs
*/
btv.Visualiser.prototype.selectNodeListener = function(eventArgs) {
var node = eventArgs.getSourceElement().node;
if(node === this.getSelectedNode()) { // already selected => deselect
this.setSelectedNode(null);
} else {
this.setSelectedNode(node);
}
}
/**
* @public
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.setSelectedNode = function(node) {
if(this.selectedNode !== null && this.selectedNode === node) { // already selected
return;
}
if(this.selectedNode !== null) { // deselect previous
this.selectedNode.nodeElement.circle.setStroke(jsgl.util.clone(this.circleStroke));
}
if(node !== null) { // select given
node.nodeElement.circle.setStroke(jsgl.util.clone(this.selectedCircleStroke));
}
this.selectedNode = node;
}
/**
* @public
* @returns {btv.BinaryTreeNode} selected node or null
*/
btv.Visualiser.prototype.getSelectedNode = function() {
return this.selectedNode;
}
/**
* Safely add given jsglElements to jsglPanel.
*
* @public
* @param {jsgl.elements.AbstractElement[]} elements Array of jsglElements to add to jsglPanel.
* @return {Number} Return number of actually added elements.
*/
btv.Visualiser.prototype.addElements = function(elements) {
if(elements == null) { // null or undefined
return 0;
}
var count = 0;
for(var i = 0; i < elements.length; i++) {
if(elements[i] == null) {
continue; // skip null or undefined element
}
if(!this.panel.containsElement(elements[i])) {
this.panel.addElement(elements[i]); // add element if is not contained in panel
count++;
}
}
return count;
}
/**
* Safely add given jsglElement to jsglPanel.
*
* @public
* @param {jsgl.elements.AbstractElement} element A jsglElements to add.
* @return {Number} Return number of actually added elements.
*/
btv.Visualiser.prototype.addElement = function(element) {
return this.addElements([element]);
}
/**
* Securely add all jsglElements of given node to jsglPanel.
*
* @public
* @param {btv.BinaryTree} node
* @return {Number} Return number of actually added elements.
*/
btv.Visualiser.prototype.addNodeElements = function(node) {
if(node == null) {
return 0; // do nothing for null or undefined node
}
// @warning add elements here if node is composed of more than that elements
return this.addElements([node.nodeElement, node.edgeElement]);
}
/**
* Safely remove given jsglElements added to jsglPanel.
*
* @public
* @param {jsgl.elements.AbstractElement[]} elements Array of jsglElements to remove.
* @return {Number} Return number of actually removed elements.
*/
btv.Visualiser.prototype.removeElements = function(elements) {
if(elements == null) { // null or undefined
return 0;
}
var count = 0;
for(var i = 0; i < elements.length; i++) {
if(elements[i] == null) {
continue; // skip null or undefined element
}
if(this.panel.containsElement(elements[i])) {
this.panel.removeElement(elements[i]); // remove element if is contained in panel
count++;
}
}
return count;
}
/**
* Safely remove given jsglElement from jsglPanel.
*
* @public
* @param {jsgl.elements.AbstractElement} element A jsglElements to remove.
* @return {Number} Return number of actually removed elements.
*/
btv.Visualiser.prototype.removeElement = function(element) {
return this.removeElements([element]);
}
/**
* Securely remove all jsglElements added to jsglPanel referenced from the node.
*
* @public
* @param {btv.BinaryTree} node
* @return {Number} Return number of actually removed elements.
*/
btv.Visualiser.prototype.removeNodeElements = function(node) {
if(node == null) {
return 0; // do nothing for null or undefined node
}
// @warning add elements here if node is composed of more than that elements
return this.removeElements([node.nodeElement, node.edgeElement]);
}
/**
* @private
* @param {btv.BinaryTreeNode|Number} node A node or index of a node.
* @returns {jsgl.Vectro2D}
*/
btv.Visualiser.prototype.getNodeIndexLocation = function(node) {
if(node instanceof btv.BinaryTreeNode) {
node = node.getIndex();
}
var n = 0;
// number of row
for(var i = 0; node >= n; i++) {
n += Math.pow(2,i);
}
n -= Math.pow(2, i-1); // correction
// number of column
var j = node - n + 1;
j = 1 + (j-1)*2; // recalculated
return new jsgl.Vector2D(j/Math.pow(2, i) * this.width, i * this.verticalSpacing);
}
/**
* @private
* @param {btv.BinaryTreeNode|Number} node A node or an index of a node.
* @returns {jsgl.Vector2D}
*/
btv.Visualiser.prototype.getNextToNodeLocation = function(node) {
return this.getNodeIndexLocation(node).subtract(new jsgl.Vector2D(0, this.circleRadius + this.verticalSpacing/2));
}
/**
* @public
* @param {btv.BinaryTree} tree
*/
btv.Visualiser.prototype.redrawTree = function(tree) {
this.panel.clear();
this.setSelectedNode(null);
var node = tree.getRoot();
if(node === null) {
return;
}
node.nodeElement = this.createNodeElement(node, this.getNodeIndexLocation(node));
this.addElement(node.nodeElement);
this.redrawNodeRec(node.getLeft());
this.redrawNodeRec(node.getRight());
if(btv.controller.returnedValue != undefined) {
if(btv.controller.returnedValue instanceof btv.BinaryTreeNode) {
this.setSelectedNode(btv.controller.returnedValue);
}
// else if(btv.controller.returnedValue instanceof Array) {
else if(this.arrayElem != null) {
this.addElement(this.arrayElem);
var array = new Array();
for(var i = 0; i < btv.controller.returnedValue.length; i++) {
if(btv.controller.returnedValue[i] == null) {
array.push("n");
} else {
array.push(btv.controller.returnedValue[i].getValue());
}
}
this.arrayElem.setTextAll(array);
}
}
}
/**
* @private
* @param {btv.BinaryTreeNode} node
*/
btv.Visualiser.prototype.redrawNodeRec = function(node) {
if(node == null) {
return;
}
node.nodeElement = this.createNodeElement(node, this.getNodeIndexLocation(node));
node.edgeElement = this.createEdgeElement(node.getParent(), node); // has to have parent, root is recreated and readed in redrawTree
this.addNodeElements(node);
this.redrawNodeRec(node.getLeft());
this.redrawNodeRec(node.getRight());
}