The Dendrogrammer Code Documentation

dendrogrammer  Dendrogrammer version 1.0.3

The Dendrogrammer > dendrogrammer > dendrogrammer.js (source view)
//JavaScript Document
//NB: to change the version number displayed in the About tab, edit line 4 of the PHP file
/**
 * The Dendrogrammer - production version 1.0.0
 * 
 * The Dendrogrammer creates an interactive dendrogram from clustering data.
 * These pages document the JavaScript part of the application (which is the 
 * major part).<br /><br />
 * The Dendrogrammer was created by David Allan Robb, March 2011 to August 2011
 * <br /><br />
 * 
 * 	NOTE: IMPORTANT: You need to know: There is a limit placed on the number of 
 * clusters that can be grown into the cluster tree. It is set in the ClusterNode 
 * constructor, this.growLimit=2000. It uses a global variable, growCounter. It 
 * is an arbitrary limit placed to prevent interminable loops with erroneous 
 * data. It could be increased.<br /><br />
 * 
 * 
 * There are some 'constants' declared at the start which could be tuned to affect 
 * the dendrogram appearance. These are: <br />
 * -leafSpace (the amount of vertical space occupied on the chart by one leaf),<br /> 
 * -margin (the amount of space around the chart outside the axes), and <br /> 
 * -leafLabelSpace (the amount of space for the labels down the vertical axis).
 * <br /><br /> 
 * 
 * There are also three element styling 'constants': <br />
 * standardStrokeWidth,<br />
 * standardLeafRadius,<br /> and nodeHighlightColour.<br /><br />
 * 
 * The following instances of class objects are declared at a global level. (However,
 * none of these objects are accessed via global scope. If one of the classes 
 * requires access to another it is passed a reference as a parameter.) Refer 
 * to class descriptions for details:<br />
 * -theClusterTable, the instance of ClusterTable <br />
 * -theStyles, the instance of Styles<br />
 * -theClusterTree, the instance of ClusterTree<br />
 * -theCanvas, the instance of Canvas<br />
 * -theAxes, the instance of GraphAxes<br />
 * -theDendrogram, the instance of Dendrogram 
 * 
 * @Module dendrogrammer
 * 
 */
$(document).ready( function() {
	//##########################################################################
	//##########################################################################
	//PROCESSING STARTS HERE
	//##########################################################################
	
	//##########################################################################
	//Constants. Placed here to allow easy location for tuning

	//to help dimension the Raphael canvas	
	var leafSpace=8;  var margin=45; var leafLabelSpace=50;

	//standard styles for elements
	var standardStrokeWidth= 1.5;
	var standardLeafRadius= 2; 
	var nodeHighlightColour="red";

	//END OF Constants. 
	//###########################################################################

	//#######################################################################
	//The data structure
	//ClusterTable made of read in 
	// - clusterRows (node data), 
	// - datRows (filename data or manifest)
	// - and leafRows (leaf description data)
	//and 
	//ClusterTree made of ClusterNodes (made from the ClusterTable data).
	//######################################################################
	//set up the ClusterTable reading in the data
	var theClusterTable= new ClusterTable();
	
	//###########################################################################
	//instantiate the Gui class for user interaction
	//###########################################################################
	var theGui=new Gui(theClusterTable);

	//Global count of sprouted leaf nodes. Used when creating the tree
	//incremented by the global function, getNextLeafSproutNo()
	var leafSproutCounter=0;
	var growCounter=0;// used in limiting the growth (see top of code).

	//create the Styles object
	var theStyles= new Styles(standardStrokeWidth,standardLeafRadius,
			nodeHighlightColour);

	//set up the ClusterTree using data from the ClusterTable
	var theClusterTree= new ClusterTree(theClusterTable,theStyles);

	//set up the canvas
	var theCanvas = new Canvas(theClusterTree,theClusterTable,margin,
								leafSpace,leafLabelSpace, theGui);

	//draw the Axes using the GraphAxes class
	var theAxes= new GraphAxes(theCanvas,0,true);//x axis from 0 and have notches
	//###########################################################################
	//draw the dendrogram
	var theDendrogram = 
		new Dendrogram(theClusterTree,theCanvas,theStyles,theGui,theAxes);

	//###########################################################################
	//Bind function to buttons
	theGui.bindButtonFunctions(theDendrogram,theGui);

	//###########################################################################
	//output values to web page
	theGui.showDataDisplay(theCanvas.largestHt,theCanvas.noOfLeafs);
	
	//End OF output values to web page
	//############################################################################ 
	
	//############################################################################
	//PROCESSING ENDS HERE
	//############################################################################
	//############################################################################

	//############################################################################
	//Global Functions and objects
	//############################################################################
	/*
	 * A global method to access and increment the leafSproutCounter
	 * @method getNextLeafSproutNo
	 * increments the global leafSproutCounter
	 * @return the value of that counter
	 */
	function getNextLeafSproutNo(){    
		//increment count
		leafSproutCounter++;
		return leafSproutCounter;
	}
	/*
	 * A global object constructor to create point objects
	 * @constructor point
	 * Point object constructor
	 * @return an object with x and y properties as per parameters
	 */
	function point(x,y){
		this.x=x;
		this.y=y;	
	}
	/*
	 * For iPad detection.
	 * ref: Nicholas C. Zakas
	 * http://www.nczonline.net/blog/2010/04/06/ipad-web-development-tips/
	 * This is not made use of now. However I have left it in in case it may be 
	 * needed in future. Instead a more generic mobile OS detection is done 
	 * elsewhere
	 * @method isIPad
	 * @return boolean
	 */
	function isIPad(){
		return navigator.platform == "iPad";
	}
	//END OF Global Functions and objects
	//###########################################################################

	//###########################################################################
	//Class Definitions
	//###########################################################################
	//Below here are all the class definitions.

	//######################################################
	//GraphAxes class
	/**
	 * For defining, drawing, and redrawing the dendrogram axes<br /><br />
	 * 
	 * The constructor creates x and y axis graphic and axis label objects 
	 * on the given Raphael canvas. 
	 * 
	 * @class GraphAxes	 
	 */
	//Define functions for the methods
	/**
	 * Styles a given axis	
	 * @method style
	 * @param axis {graphic object} the axis object to be styled
	 * @param title {string} title text for the axis mouse-over
	 * @return {boolean} true. Return value not used.
	 */
	function GraphAxes_style(axis,title){
		//use the attr method to style the axis object
		axis.attr(
				{
					stroke: '#b2b2b2',
					'stroke-width': 2,
					title: title
				});
		return true;//not used
	}
	/**
	 * Draws an horizontal axis with notches every so often according to parameters
	 * @method drawXAxis
	 * @param paper {Raphael canvas}
	 * @param startPt {point object} canvas point to start drawing
	 * @param length {number} length to be drawn
	 * @param notchInterval {number} interval at which to place notches
	 * @return {graphic element} the drawn X axis
	 */
	function GraphAxes_drawXAxis(paper, startPt, length, notchInterval  ){    
		var notchSize=3;

		var myPathString="M "+startPt.x+" "+startPt.y+" ";
		currentLength=length;
		currentX=startPt.x;
		while ((currentLength-notchInterval)>=0){
			myPathString+="l 0 -"+notchSize+" l 0 "+notchSize;//add a notch
			//draw an interval
			myPathString+="l"+notchInterval+" 0";
			//chop interval off length
			currentLength=currentLength-notchInterval;
		}
		//one more notch
		myPathString+="l 0 -"+notchSize+" l 0 "+notchSize;//add a notch
		//complete the length
		myPathString+="l "+currentLength+" 0 z";
		return paper.path(myPathString);
	}
	/**
	 * Draws a smooth horizontal axis with no notches except at start and end	
	 * @method drawXAxisSmooth  
	 * 
	 * @param paper {Raphael canvas}
	 * @param startPt {Point}
	 * @param length {number}
	 * @return {graphic element} the drawn X axis
	 */
	function GraphAxes_drawXAxisSmooth(paper, startPt, length  ){    
		var notchSize=3;
		var myPathString="M "+startPt.x+" "+startPt.y+" ";
		myPathString+="l 0 -"+notchSize+" l 0 "+notchSize;//add a notch
		myPathString+="l "+length+" 0 ";
		myPathString+="l 0 -"+notchSize+" l 0 "+notchSize;//add a final notch
		myPathString+="z";//end it
		return paper.path(myPathString);
	}	
	/**
	 * Removes drawn elements of the axes.	
	 * @method remove  
	 */
	function GraphAxes_remove(){    
		this.xAxis.remove();
		this.yAxis.remove();
		this.xAxisLabel.remove();
		this.xAxisLabelB.remove();
		this.originLabel.remove();
	}
	/**
	 * Creates x and y axis graphic objects on the given Raphael canvas	
	 * @constructor GraphAxes  
	 * @param canvasIn - the Canvas ( a Canvas object)
	 * @param originHtIn - the Ht at the origin (a number)
	 * @param isNormalGraph - indidates if axes are for normal or summary.
	 * Affects labelling whether or not the X axis is to have notches (boolean)
	 * @return {GraphAxes}
	 */
	//Constructor function  
	function GraphAxes(canvasIn, originHtIn, isNormalGraph ){
		//initialise attributes
		//none read straight in

		//associate methods
		this.style = GraphAxes_style;
		this.drawXAxis= GraphAxes_drawXAxis;
		this.drawXAxisSmooth= GraphAxes_drawXAxisSmooth;
		this.remove = GraphAxes_remove;
		
		//derived attributes
		// set up origin.x and origin.y 
		this.origin=
			new point(canvasIn.margin+canvasIn.leafLabelSpace,canvasIn.margin);
		if (isNormalGraph){//draw an x-axis with notches
			this.xAxis=this.drawXAxis(canvasIn.paper, this.origin, 
					canvasIn.xWidth-(canvasIn.margin*2)-canvasIn.leafLabelSpace, 
					canvasIn.htScaleFactor/2);	
		}else{//draw a smooth x-axis
			this.xAxis=this.drawXAxisSmooth(canvasIn.paper, this.origin, 
					canvasIn.xWidth-(canvasIn.margin*2)-canvasIn.leafLabelSpace);
		}
		this.yAxis= canvasIn.paper.path("M "+this.origin.x+" "+this.origin.y+"l 0 "
				+(canvasIn.yHt-(canvasIn.margin*2)));
		//label the axes
		//create some drawn text to show the max value of ht. Call it xAxisLabel.
		this.xAxisLabel = canvasIn.paper.text(
				(canvasIn.largestHt*canvasIn.htScaleFactor+canvasIn.margin
						+canvasIn.leafLabelSpace), 
						canvasIn.margin-10, 
				'Max ht = '+canvasIn.largestHt);
		this.xAxisLabelB = canvasIn.paper.text(
				((canvasIn.largestHt*canvasIn.htScaleFactor/2)+canvasIn.margin
						+canvasIn.leafLabelSpace), 
						canvasIn.margin-20, 
				'Dissimilarity');
		//label the origin
		this.originLabel = canvasIn.paper.text((canvasIn.margin
						+canvasIn.leafLabelSpace), canvasIn.margin-10, originHtIn);
		//if this is a summary graph then label the leaf axis with "Summary"
		if(originHtIn>0){
			this.summaryLeafLabel= canvasIn.paper.text(canvasIn.margin, 
					Math.round(canvasIn.yHt/2), 
					'Summary.\nClick\nclusters for\nleaf lists.');
		}
		//actions to be done by the constructor
		this.style(this.xAxis,"X-axis");//style the xAxis
		this.style(this.yAxis,"Y-axis");//style the yAxis
	} 

	//End of GraphAxes class
	//######################################################

	//######################################################
	//ClusterTable class	
	/**
	 * Reads the data (held in the page as hidden elements of an HTML form) into 
	 * a table. The 'table' is actually three tables: an array of clusterRows, 
	 * an array of datRows, and an array of nodeRows.<br /><br />
	 * 
	 * The constructor calls the getData() method to fill the arrays. It also 
	 * calls assignIdNos() method.<br /><br />
	 * 
	 * ClusterTree is generated from the ClusterTable and once this has been done
	 * the job of the ClusterTable is complete and it is not referred to again.
	 * 
	 * @class ClusterTable
	 */
	//Define functions for the methods	
	/**
	 * 	Fills the table with data.<br />
	 *  Gathers data from the hidden form elements and insert in the table<br />
	 *  1) Creates an associative array of all the input elements in the page<br />
	 *  2) Dat file data: loop through the array picking out the fields for 
	 *  	each Dat row (manifest file)<br />
	 *  3) Node file data: as above but for the main cluster data <br />
	 *  4) Leaf file data: as above but for the leaf data <br /><br />
	 *  
	 *  Encompasses object constructor functions for clusterRow, datRow and leafRow
	 *  objects.
	 * 
	 * @method getData  
	 * 
	 * @return {string} the filename of the originating Dat file.
	 */
	function ClusterTable_getData(){//fills the table with data

		//object constructor
		function clusterRow(itemA, itemB, mergeHt){
			this.itemA=itemA;
			this.itemB=itemB;
			this.mergeHt=mergeHt;
			this.idNo=0;//set to zero for now
		}
		//object constructor
		function datRow(fileNameIn){
			this.fileName=fileNameIn;
		}		
		//object constructor
		function leafRow(shortIn, longIn){
			this.shortDescription=shortIn;
			this.longDescription=longIn;
		}

		//gather data from the hidden form elements and insert in the table

		//create an associative array of all the input elements in the page
		var $allInputElements=$(':input');//gather input elements in a jQuery object
		//make an array    	
		var inputValues = {};
		$allInputElements.each(function() {
			inputValues[this.name] = $(this).val();
		});
		//extract the file name
		var fileName=inputValues['fileName'];    
		//$( "#traceDump" ).append("fileName="+fileName+"</br>");

		//First the dat file data
		//loop through the array picking out the fields for each row
		var row=1;
		field = "dat"+row+"col0";
		while (inputValues[field]!=null){
			fileNameIn=inputValues[field];
			this.datArray[row-1]=new datRow(fileNameIn);
			row++;//next row
			field="dat"+row+"col0";
		}
		
		//Second the node file data
		//loop through the array picking out the fields for each row
		row=1;
		field = "node"+row+"col0";
		//$( "#traceDump" ).append("!value="+inputValues[field]+"!");
		while (inputValues[field]!=null){
			itemA=inputValues[field];
			field="node"+row+"col1";
			itemB=inputValues[field];
			field="node"+row+"col2";
			mergeHt=inputValues[field];
			this.cArray[row-1]=new clusterRow(itemA, itemB, mergeHt);
			row++;//next row
			field="node"+row+"col0";
		}

		//Third the leaf file data
		//loop through the array picking out the fields for each row
		var row=1;
		field = "leaf"+row+"col0";
		while (inputValues[field]!=null){
			shortDesc=inputValues[field];
			field="leaf"+row+"col1";
			longDesc=inputValues[field];
			this.leafArray[row-1]=new leafRow(shortDesc, longDesc);
			row++;//next row
			field="leaf"+row+"col0";
		}

		return fileName;// returns the name of the file originating the data     
	}
	/**
	 * Returns a row from the cArray by index	
	 * @method getRowByIndex  
	 * 
	 * @param indexIn {number}
	 * @return {clusterRow object}
	 */
	function ClusterTable_getRowByIndex(indexIn){
		return this.cArray[indexIn];  
	}
	/**
	 * Returns the row indexed by the current pointer value
	 * @method getCurrentRow  
	 * 
	 * @return {clusterRow object}
	 */
	function ClusterTable_getCurrentRow(){
		return this.cArray[this.pointer]; 
	}
	/**
	 * Returns the current pointer value
	 * @method getPtr  
	 * 
	 * @return {integer}
	 */
	function ClusterTable_getPtr(){
		return this.pointer;
	}
	/**
	 * Increments the pointer then returns the newly pointed to row.
	 * Should only be called after checking with .nextRowExists
	 * 
	 * @method getNextRow  
	 * 
	 * @return {clusterRow object}
	 */
	function ClusterTable_getNextRow(){
		this.pointer++;
		return this.cArray[this.pointer];
	}
	/**
	 * Returns the last row in the table
	 * 
	 * @method getLast  
	 * 
	 * @return {clusterRow object}
	 */
	function ClusterTable_getLast(){
		return this.cArray[this.cArray.length-1];
	}
	/**
	 * Detects whether or not there is another row beyond that pointed to 
	 * by the pointer
	 * 
	 * @method nextRowExists  
	 * 
	 * @return {boolean}
	 */
	function ClusterTable_nextRowExists(){
		if(this.pointer+1<this.cArray.length){
			return true;
		}
		return false;
	}
	/**
	 * Loops through the rows assigning an ID to the clusters
	 * the first is lastLeaf number +1 the next is lastLeaf+2 and so on
	 * 
	 * @method assignIdNos  
	 */
	function ClusterTable_assignIdNos(){
		nextClusterNo=this.lastDataRow+3;//e.g. 0 to 4 rows = 5 data rows = 6 leafs
		//= the first cluster is number 7
		for (i=0;i<this.cArray.length;i++){
			this.cArray[i].idNo=nextClusterNo;
			nextClusterNo++;
		}
	}
	/**
	 * Detects whether a cluster IdNo represents a leaf. 
	 * It will be a leaf if the idNo <= the number of leaves
	 * 
	 * @method isLeafByIdNo
	 * 
	 * @param idNoIn {integer}
	 * 
	 * @return {boolean}
	 */
	function ClusterTable_isLeafByIdNo(idNoIn){
		if(idNoIn<=this.getNoOfLeaves()&&idNoIn>0){
			return true;
		}
		return false;
	}
	/**
	 * Loops through the rows for the given IdNo and returns the array index 
	 * for that row
	 * 
	 * @method getRowIndexByIdNo
	 * 
	 * @param idNoIn {integer}
	 * 
	 * @return {integer}the array index for that row
	 */
	function ClusterTable_getRowIndexByIdNo(IdNoIn){
		for (i=0;i<=this.lastDataRow;i++){
			if(this.getRowByIndex(i).IdNo==IdNoIn){
				return i;
			}
		}
		return false;
	}
	/**
	 * Loops through the rows for the given IdNo and returns the itemA for that row
	 * 
	 * @method getItemAByIdNo
	 * 
	 * @param idNoIn {integer}
	 * 
	 * @return {number} the itemA for that row
	 */
	function ClusterTable_getItemAByIdNo(idNoIn){
		for (i=0;i<this.cArray.length;i++){
			if(this.getRowByIndex(i).idNo==idNoIn){
				return this.getRowByIndex(i).itemA;
			}
		}
		return false;
	}
	/**
	 * Loops through the rows for the given IdNo and returns the itemB for that row
	 * 
	 * @method getItemBByIdNo
	 * 
	 * @param idNoIn {integer}
	 * 
	 * @return {number} the itemB for that row
	 */
	function ClusterTable_getItemBByIdNo(idNoIn){
		for (i=0;i<=this.lastDataRow;i++){
			if(this.getRowByIndex(i).idNo==idNoIn){
				return this.getRowByIndex(i).itemB;
			}
		}
		return false;
	}
	/**
	 * Loops through the rows for the given IdNo and returns 
	 * the mergeHt for that row with that given IdNo
	 * 
	 * @method getHtByIdNo
	 * 
	 * @param idNoIn {integer}
	 * 
	 * @return {number} the itemB for that row
	 */
	function ClusterTable_getHtByIdNo(idNoIn){
		//returns the mergeHt for that row with that given IdNo
		//if it is a leaf id then return zero a leaf's ht is zero
		if(this.isLeafByIdNo(idNoIn)){
			return 0;
		}
		//loops through the rows for the given IdNo 
		//and returns the mergeHt for that row
		for (i=0;i<=this.lastDataRow;i++){
			if(this.getRowByIndex(i).idNo==idNoIn){
				return this.getRowByIndex(i).mergeHt;
			}
		}
		return false;
	}
	/**
	 * Returns the number of rows in the cArray table
	 * 
	 * @method getNoOfLeaves
	 * 
	 * @param idNoIn {integer}
	 * 
	 * @return {number} the itemB for that row
	 */
	function  ClusterTable_getNoOfLeaves(){
		return this.cArray.length+1;
	}
	/**
	 * Returns the search link text which is the 4th row in the dat table
	 * 
	 * @method getSearchLink
	 * 
	 * @return {string} the 4th item in the dat table
	 */
	function  ClusterTable_getSearchLink(){
		if(this.datArray.length>3){
			return this.datArray[3].fileName;
		}
		return null;
	}
	/**
	 * Returns the data description text which is the 3rd row in the dat table
	 * 
	 * @method getDescription
	 * 
	 * @return {string} the 3rd item in the dat table
	 */
	function  ClusterTable_getDescription(){
		if(this.datArray.length>2){
			return this.datArray[2].fileName;
		}
		return null;
	}

	//Constructor function
	/**
	 * Calls the getData() method to fill the tables. Calls assignIdNos() method.
	 * @constructor ClusterTable
	 */
	function ClusterTable(){

		//initialise attributes 
		this.cArray= new Array();//to hold the table rows
		this.datArray= new Array();//to hold the dat table rows
		this.leafArray= new Array();//to hold the leaf table rows

		//set the array pointer to -1 to indicate empty dendrogram
		this.pointer=-1;

		//associate methods
		this.getData = ClusterTable_getData;
		this.getRowByIndex = ClusterTable_getRowByIndex;
		this.getCurrentRow = ClusterTable_getCurrentRow;
		this.getPtr = ClusterTable_getPtr;
		this.getNextRow = ClusterTable_getNextRow;
		this.nextRowExists = ClusterTable_nextRowExists;
		this.assignIdNos = ClusterTable_assignIdNos;
		this.getLast = ClusterTable_getLast;
		this.getNoOfLeaves= ClusterTable_getNoOfLeaves;
		this.getRowIndexByIdNo= ClusterTable_getRowIndexByIdNo;
		this.getItemAByIdNo= ClusterTable_getItemAByIdNo;
		this.getItemBByIdNo= ClusterTable_getItemBByIdNo;
		this.isLeafByIdNo= ClusterTable_isLeafByIdNo;
		this.getHtByIdNo= ClusterTable_getHtByIdNo;
		this.getSearchLink= ClusterTable_getSearchLink;
		this.getDescription= ClusterTable_getDescription;

		//actions to be done by the Constructor

		this.getData();//fill the table
		this.lastDataRow=this.cArray.length-1;
		this.assignIdNos();
	} 
	//End of ClusterTable class
	//######################################################

	//######################################################
	//ClusterTree class
	/**
	 *   The ClusterTree class holds a tree of clusters. Actually it is one 
	 *   root node leading to all the other nodes in the tree.
	 *   The tree is a single root node which is the overall ancestor of all 
	 *   nodes in the tree. Each node is an instance of the ClusterNode class. 
	 *   Each node represents a cluster from the ClusterTable. 
	 *   The root node is the cluster that has the highest merge height.<br />
	 *   Each node has<br />
	 *   - nType: either leaf 2, cluster 1, or root 0<br />
	 *   - childA: one of the two child nodes (that are other node objects)<br />
	 *   - childB: see above<br />
	 *   - xHt: the height of the cluster OR zero for a leaf<br />
	 *   - parent: a parent node (another node object)<br />
	 *   - yPos: for a leaf this is the numbered leaf slot on the leaf 
	 *   axis (y axis). For a cluster or the root this is the point midway 
	 *   (in y) between the yPos of its two child nodes.<br />
	 *   For the purpose of the algorithm the leaf axis is the y axis because the
	 *   default orientation for the tree is on its side with root at the right
	 *   and leaves at the left arranged down the y axis.<br />
	 *   - element: a visible drawn graphical object representing the cluster 
	 *   	on the graph<br />
	 *   - bBox: a bounding box, a transparent graphical object representing 
	 *   	the live area on the graph associated with the cluster<br />
	 *   - rowIndex: the index of the row on the clusterTable from which it 
	 *   	was generated<br />
	 *   - idNo: the number given it by MATLAB in creating the clustering data<br />
	 *   e.g. in our example data the cluster on row 0 (Item A =1, ItemB=6 which are
	 *   two leaves are part of the first cluster. 5 rows (0 to 4) => 6 leafs => 
	 *   first cluster is no.7 so the idNo for that cluster will be 7.
	 *   <br /><br />
	 *  The ClsterTree constructor calls the new ClusterNode constructor method 
	 *  on the root node which in turn calls the ClusterNode grow() method which 
	 *  recursively calls itself to build the tree from the data in ClusterTable 
	 *  table. The constructor also sets the graph yPos for each node.
	 *   <br /><br />
	 *  Important ClusterTree attribute: <br />
	 *  this.root. However, this.root is accessed via the getRoot() method.
	 *   
	 *   @class ClusterTree
	 */
	//Define functions for the methods
	/**
	 * @method getRoot
	 * @return {node} the root node in the tree
	 */
	function  ClusterTree_getRoot(){
		return this.root;
	}	
	/**
	 * Recursively traverses the tree setting the yPos attribute. 
	 * Based on the yPos of the leaf nodes.
	 * The yPos for each leaf is already set (it is the sproutCount property)
	 * The yPos for a cluster is half way between the childA.yPos and childB.yPos
	 * but the yPos of the clusters start off null.
	 * 
	 * @method setClusterY
	 * @param node {ClusterNode object}
	 * @return {boolean} value not used.
	 */
	function  ClusterTree_setClusterY(node){
		//if the yPos if childA is null then set its yPos
		//if the yPos of childB is null then set its ypos
		if (node.childA.yPos==null){
			this.setClusterY(node.childA);
		}
		if(node.childB.yPos==null){
			this.setClusterY(node.childB);
		}
		//finally set the yPos based on yPoses of children
		if (node.childA.yPos<node.childB.yPos){
			//set ypos to that of child A + difference between A and B
			node.yPos=node.childA.yPos+(Math.abs(node.childA.yPos-node.childB.yPos)/2);
		}else{
			//set ypos to that of child B + difference between A and B
			node.yPos=node.childB.yPos+(Math.abs(node.childA.yPos-node.childB.yPos)/2);
		}  	
		return true;
	}
	/**
	 * Calls the new ClusterNode constructor method which in turn calls the CLusterNode
	 * grow() method which recursively calls itself to build the from the 
	 * data in ClusterTable table. It also sets the graph yPos for each cluster.
	 * 
	 * @constructor ClusterTree
	 * @param cTableIn {ClusterTable} the instance of ClusterTable containing 
	 * 		the input data.
	 * @param stylesIn {style object} carries the style info for local access
	 */
	function ClusterTree(cTableIn,stylesIn){

		//initialise attributes
		this.cTable=cTableIn;
		this.styles=stylesIn;
		//the root node is the node from the end of the clusterArray
		//i.e. that cluster with the highest cluster height
		var rootIdNo=this.cTable.getLast().idNo;

		//This line causes a recursive all on the CusterNode Constructor
		//in conjunction with the data in the cluster table which fill up
		//the tree
		this.root= new ClusterNode(this.cTable,rootIdNo,null,0,this.styles);

		//associate methods
		this.getRoot = ClusterTree_getRoot;
		this.setClusterY = ClusterTree_setClusterY;

		//processing to be done by the constructor
		//Set the yPos values for all the clusters
		this.setClusterY(this.root);
	} 

	//End of ClusterTree class
	//######################################################

	//######################################################
	//ClusterNode class
	/**
	 * The ClusterTree is made up of ClusterNodes. See description of 
	 * ClusterTree class for further details. A ClusterTree is a tree 
	 * structure of these ClusterNodes.
	 * <br /><br />
	 * 
	 * The constructor does the following:<br />
	 * If this node is a leaf <br />
	 * then it sets the child nodes to null and set default leaf descriptions
	 * 		and that is the end of that.<br />
	 * else it calls grow(), causing recursive calls on the new Cluster 
	 * Node constructor to spawn its childA and its childB child nodes.
	 * (and also sets default cluster descriptions)<br /><br />
	 * 
	 * Important ClusterNode attributes used by other classes:<br /><br />
	 * 
	 * parent - ancestor node or null for root. 
	 * 			Used in traversing the ClusterTree data structure as 
	 * 			are childA and B<br />
	 * childA - descendant node or null for leaf<br />
	 * childB - as childA<br />
	 * xHt - used when drawing elements on the graph (i.e. its xPos)<br />
	 * yPos - graph position on the vertical axis<br />
	 * element - the drawn object represented on the graph<br />
	 * label - graphical Raphael label drawn to left of the y axis<br />
	 * bBox - the bounding box for event sensing<br />
	 * longDescription - not used but set to mimic the short description<br />
	 * shortDescription - used to label the node. (Read from leaf data or 
	 * 		created from the cluster ID)
	 *
	 * 
	 * @class ClusterNode
	 */

	//Define functions for the methods
	/**
	 * @method isLeaf
	 * @return {boolean} true if the node is a leaf
	 */
	function ClusterNode_isLeaf(){
		if(this.nType==2){
			return true;
		}
		return false;      
	}
	/**
	 * @method isRoot
	 * @return {boolean} true if the node is the root
	 */
	function ClusterNode_isRoot(){
		if(this.nType==0){
			return true;
		}
		return false;      
	}
	/**
	 * Grows the tree by calling itself recursively. The recursion is via the 
	 * ClusterNode constructor which calls this grow() method.
	 * First grow down the childA branch. Then grow down the childB branch.
	 * 
	 * @method grow
	 * @param nodeIn {ClusterNode Object} the current node whose children are 
	 * 		to be grown.
	 * @return {boolean} value not used.
	 */
	function ClusterNode_grow(nodeIn){

		//retrieve the ID numbers of the node's ChildA and ChildB 
		//from the ClusterTable
		var childIdA=this.cTable.getItemAByIdNo(nodeIn.idNo);
		var childIdB=this.cTable.getItemBByIdNo(nodeIn.idNo);

		//First grow down the childA branch. Then grow down the childB branch.
		//Pass the each CLuster node the table, the id of the new node, the 
		//current node as the parent, and 4 as unknown type for the new node 
		//(i.e. don't know if cluster or leaf yet)
		this.childA= new ClusterNode(this.cTable,childIdA,nodeIn,4,nodeIn.styles);
		//Next grow down the childB branch
		this.childB= new ClusterNode(this.cTable,childIdB,nodeIn,4,nodeIn.styles);
		return true;//return value not used  
	}
	/**
	 * Turns on or off the highlighting of a tree branch<br /><br /> 
	 * The reason that "this" is not used in this block is due to it being 
	 * involved in event handling. When an event fires "this" becomes the object
	 * associated with the event rather than the current in scope class object.
	 * <br /><br />
	 * Uses a recursive call to highlight itself and all its children.
	 * 
	 * @method showBranch
	 * 
	 * @param nodeIn {a ClusterNode} the node at the base of the branch
	 * @param on {boolean} True indicates turn on highlight. False is off.
	 * @param isSummaryIn (boolean) True indicates this is a summary dendrogram.
	 * 
	 */
	function ClusterNode_showBranch(nodeIn,on,isSummaryIn){
		//check for nodeIn==null is so return false
		if(nodeIn==null){return false;};//attempt no processing on a null node
		//check for element null incase element is undrawn in a summary graph
		//attempt no styling if there is no element
		if(nodeIn.element==null){return true;};
		//if on = true then show highlighting else revert from highlighting
		if(on){
			//show highlighting
			nodeIn.element.attr(
					{
						'stroke':'red',
						'stroke-width':(nodeIn.styles.strokeWidth*1.5)
					});
			
			//if it is a leaf AND NOT s summary chart then highlight the label
			if((nodeIn.isLeaf())&&(!isSummaryIn)){
				nodeIn.label.attr(
						{
							'font-style':'italic',
							'font-weight':'bold'
						});
			}
		}
		else{
			//revert from highlighting
			nodeIn.element.attr(
					{
						'stroke':'black',
						'stroke-width':nodeIn.styles.strokeWidth
					});
			
			//if it is a leaf AND NOT a summary chart then turn of label highlight
			if((nodeIn.isLeaf())&&(!isSummaryIn)){
				nodeIn.label.attr(
						{
								'font-weight':'normal',
								'font-style':'normal'
						});
			}
		}
		//do same for children
		nodeIn.showBranch(nodeIn.childA,on,isSummaryIn);
		nodeIn.showBranch(nodeIn.childB,on,isSummaryIn);
		return true;//has no effect
	}
	/**
	 * Draws a transparent rectangular bounding box around the node element
	 * This will be used as a larger area to be sensitive for interacting with the 
	 * cluster.
	 * 
	 * @method drawBoundingBox
	 * 
	 * @param paperIn {Raphael canvas}
	 */
	function ClusterNode_drawBoundingBox(paperIn){

		//make the bounding box
		this.bBox=paperIn.rect(
				this.element.getBBox().x,
				this.element.getBBox().y,
				this.element.getBBox().width,
				this.element.getBBox().height);
		//style it transparent
		this.bBox.attr(
				{
					'fill':'blue',
					'stroke-opacity':0,
					'fill-opacity':0.1,
					'title':this.shortDescription
				});
		return true;//has no effect
	}
	/**
	 * Called by the ClusterNode_getDescendants method.
	 *  It does this by calling itself recursively on the node's two 
	 *  children until it comes to a leaf, then pushing the leaf onto the array.
	 * 
	 * @method appendDescendants
	 * 
	 * @param nodeIn {a ClusterNode} the node for which the list is sought.
	 * @param arrayIn {array} the array to which any leaves are to be appended 
	 * 		(an array of ClusterNodes all members of which will be type leaf)
	 * @return {array of leaf nodes} an array of leaf nodes descended from 
	 * 		the given node.
	 */
	function ClusterNode_appendDescendants(nodeIn,arrayIn){
		//check for nodeIn==null if so return arrayIn
		if(nodeIn==null){
			arrayIn.push("null node");
			return arrayIn;
		};//attempt no processing on a null node. Should not be needed.		
		//if the node is a leaf append to array and return
		if(nodeIn.nType==2){
			//now appending actual node itself
			arrayIn.push(nodeIn);
			return arrayIn;
		}
		//arrayIn.push("NodeID="+nodeIn.idNo);//just for tracing
		arrayIn=nodeIn.childA.appendDescendants(nodeIn.childA,arrayIn);
		arrayIn=nodeIn.childB.appendDescendants(nodeIn.childB,arrayIn);

		return arrayIn;
	}
	/**
	 * Given a cluster node this returns an array of the leaces descended from 
	 * that cluster node. Calls the appendDescendants method to fill the array.
	 * 
	 * @method getDescendants
	 * @param {node} the cluster node for which we want to list the descendant leaves
	 * @return {array} An array of descendant leaves OR if the node itself is a leaf
	 * then it will return the node itself.
	 */
	function ClusterNode_getDescendants(nodeIn){		
		var resultArray=new Array();
		//if this is a leaf then append push node and return 
		if (nodeIn.nType==2){
			resultArray.push(nodeIn);
			return resultArray;
		}
		resultArray=nodeIn.appendDescendants(nodeIn,resultArray);
		return resultArray;      
	}	
	/**
	 * Outputs a string listing the descendant leaf nodes of the given node
	 * Calls the node's getDescendants() method to make an array of 
	 * descendant leaves from which it compiles the string.
	 * 
	 * If the node is a leaf it outputs the leaf's short and long description
	 * 
	 * It also includes a search link in the dialog to send the descendant list to
	 * a search engine.
	 * 
	 * @method displayDescendants
	 * @param nodeIn {a ClusterNode}  the node for which the list is sought
	 * @param guiIn {a Gui} the gui 
	 * 
	 * @return boolean. Value not used.
	 */
	function ClusterNode_displayDescendants(nodeIn,guiIn){ 
		var resultsString="";
		var urlString="";
		//if the node is a leaf report its short and long description
		//else report its descendant leaves short descriptions
		if(nodeIn.isLeaf()){
			resultsString="Leaf: "+nodeIn.shortDescription;
			resultsString+=", "+nodeIn.longDescription;
			urlString=nodeIn.shortDescription;
		}else{
			var resultsArray=nodeIn.getDescendants(nodeIn);//get the descendants
			//loop through the array appending them to a string
			resultsString="Descendants of "+nodeIn.shortDescription+": ";
			for(var i=0;i<resultsArray.length-1;i++){
				resultsString+=resultsArray[i].shortDescription+", ";
				urlString+=resultsArray[i].shortDescription+"%20";
			}
			resultsString+=resultsArray[resultsArray.length-1].shortDescription;
			urlString+=resultsArray[resultsArray.length-1].shortDescription;
		}
		guiIn.showMessageWithLink(resultsString,'Node Descendants',urlString );
		return true;//has no effect
	}

	//Constructor function
	/**
	 * If this node is a leaf 
	 * then it sets the child nodes to null and set default leaf descriptions
	 * 		and then returns.
	 * else it calls grow(), causing recursive calls on the new Cluster 
	 * 		Node constructor to spawn its childA and its childB child nodes.
	 * 		(and also sets default cluster descriptions)
	 * 
	 * @constructor ClusterNode
	 * 
	 * @param cTableIn {ClusterTable}
	 * @param idNoIn {number}
	 * @param parentNodeIn {ClusterNode}
	 * @param nTypeIn {number}
	 * @param stylesIn {the Styles object}
	 */
	function ClusterNode(cTableIn,idNoIn,parentNodeIn,nTypeIn,stylesIn){

		//initialise attributes
		this.growLimit=2000;
		this.cTable=cTableIn;
		this.idNo=idNoIn;
		this.nType=nTypeIn;
		this.styles=stylesIn;
		this.parent=parentNodeIn;
		this.xHt=cTableIn.getHtByIdNo(idNoIn);
		this.rowIndex=cTableIn.getRowIndexByIdNo(idNoIn);
		this.leafSproutNo=null;//default for any node. Set later for a leaf
		this.currentGroup=1;//default starting group for any node.

		this.yPos=null;//graph position on the vertical axis
		this.element=null;//the drawn object represented on the graph
		this.label=null;//graphical Raphael label drawn to left of the y axis
		this.bBox=null;//the bounding box for event sensing yPos, element,
		//label, and  bBox will be added later after further processing

		//associate methods
		this.grow = ClusterNode_grow;
		this.isLeaf = ClusterNode_isLeaf;
		this.isRoot = ClusterNode_isRoot;
		this.showBranch=ClusterNode_showBranch;
		this.drawBoundingBox=ClusterNode_drawBoundingBox;
		this.getDescendants=ClusterNode_getDescendants;
		this.appendDescendants=ClusterNode_appendDescendants;
		this.displayDescendants=ClusterNode_displayDescendants;

		//if this a leaf then set the child nodes to null and set default 
		//		leaf descriptions
		//else grow(), causing a recursive call on the new Cluster Node constructor.
		//(also set default cluster descriptions)
		if(this.cTable.isLeafByIdNo(this.idNo)){
			this.nType=2;
			this.shortDescription=cTableIn.leafArray[this.idNo-1].shortDescription;
			this.longDescription=cTableIn.leafArray[this.idNo-1].longDescription;
			this.leafSproutNo=getNextLeafSproutNo();
			this.yPos=this.leafSproutNo;
			this.childA=null;
			this.childB=null;
		}else{
			//set default. Detail added later 
			this.shortDescription="Cluster ID"+this.idNo;
			this.longDescription=
				"No long description of cluster ID "+this.idNo+" has been set.";
			//check grow counter
			//if growCounter>some limit don't grow
			if (growCounter<this.growLimit){
				growCounter++;
				this.grow(this);//recursively calls the new ClusterNode
			}
		}
	} 

	//End of ClusterNode class
	//######################################################

	//######################################################
	/**
	 * The Canvas class defines the area on which the dendrogram is drawn.
	 * Holds attributes useful in positioning drawing elements on the dendrogram
	 * <br /><br />
	 * It holds some methods key to drawing the elements:<br />
	 * A drawn element effectively has two kinds of coordinates or position. 
	 * There is its graph position (the logical position on the graph in 
	 * relation to its mergeHt (on the X axis) and its yPos along the leaf 
	 * axis). Then there are its Raphael coordinates determining where 
	 * it is drawn on the canvas, taking into account margins and scaling factors. 
	 * A number of the Canvas methods deal with conversion between the graph 
	 * (logical) coordinates and the Raphael canvas coordinates. 
	 * (e.g. cvY() takes a logical yPos and returns the Raphael 
	 * canvas y coordinate.)
	 * <br /><br />
	 * The constructor sets attributes such as scale factor and margins used in 
	 * drawing dendrogram elements. It uses the window.width property to
	 * set the width. It creates the Raphael canvas object and 
	 * assigns it to the paper attribute.<br /><br />
	 * 
	 * Important Canvas attributes used often by other classes:	<br />
	 * margin <br />
	 * marginLeft<br />
	 * leafSpace<br />
	 * leafSpaceSummary<br />
	 * originHt<br />
	 * htScaleFactor<br />
	 * htScaleFactorSummary<br />
	 * leafLabelSpace<br />
	 * noOfLeafs<br />
	 * largestHt<br />
	 * paper (the Raphael canvas on which all elements are drawn).
	 * 
	 * @class Canvas  
	 */

	//Methods

	/**
	 * Takes a diagram Y and converts it to a canvas Y,
	 * taking into account margins and scale factors
	 * 
	 * @method cvY
	 * @param yIn {point object} a graph point y pos
	 * @return {number} the canvas Y 
	 */
	function Canvas_cvY(yIn){      	
		return this.margin+(yIn*this.leafSpace);
	}
	/**
	 * Does the same job as cvY but for a summary dendrogram
	 * 
	 * @method cvYSummary
	 *
	 * @param yIn {number} a graph point y pos
	 * @return {number} the canvas Y 
	 */
	function Canvas_cvYSummary(yIn){      	
		return this.margin+(yIn*this.leafSpaceSummary);
	}

	/**
	 * Takes a diagram X and converts it to a canvas X
	 * taking into account margins and scale factors
	 * 
	 * @method cvX
	 * @param xIn {number} a graph point x pos
	 * @return {number} the canvas X 
	 */
	function Canvas_cvX(xIn){      	
		return this.margin+this.leafLabelSpace+(xIn*this.htScaleFactor);
	}
	/**
	 * Takes a diagram X and converts it to a canvas X
	 * taking into account margins and scale factors
	 * 
	 * @method cvXSummary

	 * @param xIn {number} a graph point x pos 
	 * @return {number} the canvas X 
	 */
	function Canvas_cvXSummary(xIn){      	
		return this.margin+this.leafLabelSpace+(xIn*this.htScaleFactorSummary);
	}
	/**
	 * Takes a canvas X and converts it to a graph X
	 * taking into account margins and scale factors
	 * 
	 * @method graphXfromCvX
	 * 
	 * @param xIn {number} the canvas point x pos
	 * @return {number} the converted graph x pos 
	 */
	function Canvas_getGraphXfromCvX(xIn){      	
		return (xIn-this.margin-this.leafLabelSpace)/this.htScaleFactor;
	}
	/**
	 * Takes a diagram point and converts it to a canvas point
	 * taking into account margins and scale factors
	 * 
	 * @method cVPt
	 * 
	 * @param ptIn {point object} the diagram point
	 * @return {point object} the converted point 
	 */
	function Canvas_cVPt(ptIn){
		ptIn.x=this.cvX(ptIn.x);
		ptIn.y=this.cvY(ptIn.y);
		return ptIn;
	}
	/**
	 * Takes a Summary diagram point and converts it to a canvas point
	 * taking into account margins and scale factors
	 * 
	 * @method cVPtSummary
	 * 
	 * @param ptIn {point object} the diagram point
	 * @return {point object} the converted point 
	 */
	function Canvas_cVPtSummary(ptIn){
		ptIn.x=this.cvXSummary(ptIn.x);
		ptIn.y=this.cvYSummary(ptIn.y);
		return ptIn;
	}
	/**
	 * Returns the canvas coords (in the form of a point used for the 
	 * centre of the leaf) given a leaf slot number. e.g. if slot is 2 
	 * and leafspace is 15 then feed 30 to cvY and return that as y coord
	 * and feed 0 to cvX and return that as x coord
	 * 
	 * @method getLeafPt
	 * 
	 * @param slot {number} the leafe slot number 
	 * @return {point object} a canvas point 
	 */
	function Canvas_getLeafPt(slot){ 
		var y =this.cvY(slot);
		return new point(this.cvX(0),y);
	}	
	/**
	 * Returns the canvas coords (in the form of a point used for the centre 
	 * of the leaf) given a leaf slot number, in the summary graph.<br />
	 * e.g. if slot is 2 and leafspaceSummary is 4 then feed 8 to cvY and 
	 * return that as y coord and feed 0 to cvX and return that as x coord
	 * 
	 * @method getLeafPtSummary
	 * 
	 * @param slot {number} the leaf slot number 
	 * @return {point object} a canvas point 
	 */
	function Canvas_getLeafPtSummary(slot){ 
		var y =this.cvYSummary(slot);
		return new point(this.cvX(0),y);
	}
	/**
	 * Calculates what the scaleFactor will be for this diagram taking into account
	 * canvas size and largest merge Ht. This gets assigned to the 
	 * Canvas.htScalefactor attribute
	 * 
	 * @method calculateScaleFactor
	 * 
	 * @param largestHt {number} the max in the x axis
	 * @param widthForCanvas {number} available width
	 * @param margin {number} to leave round the sides outside the axes
	 * @param leafLabelSpace {number} space on LH side
	 * 
	 * @return {number} the scale factor
	 */
	function Canvas_calculateScaleFactor(largestHt,
						widthForCanvas,margin,leafLabelSpace){ 
		var result=widthForCanvas-margin-margin-leafLabelSpace;
		result=result/(largestHt*1.05);
		return result;
	}	
	/**
	 * Sets new values for xWidth and yHt. Creates a new Raphael canvas 
	 * in the container
	 * 
	 * @method createPaper
	 * 
	 * @param xWidthIn {number} the new width 
	 * @param yHtIn {number} the new ht 
	 */
	function Canvas_createPaper(xWidthIn,yHtIn){ 
		this.xWidth=xWidthIn; 
		this.yHt=yHtIn;
		this.paper = new Raphael(document.getElementById(this.gui.canvasContainer), 
				xWidthIn, yHtIn);
	}

	//Constructor function
	/**
	 * @constructor Canvas 
	 * 
	 * @param cTreeIn {the CLusterTree object} the cluster tree to be represented.
	 * @param cTableIn {the ClusterTable object} the cluster table. 
	 * @param htScaleFactorIn {number} the scale factor in the x direction.
	 * @param marginIn {number} the size of the margin around the chart 
	 * 				  within the canvas.
	 * @param leafSpaceIn {number} the amount of space allowed for each leaf on the 
	 * 					  leaf (Y) axis.
	 * @param leafLabelSpaceIn {number} the amount of extra space down the 
	 * 			vertical axis to leave for leaf labels.
	 */
	function Canvas(cTreeIn,cTableIn,marginIn,leafSpaceIn,leafLabelSpaceIn, guiIn){

		//initialise attributes
		//passed in
		this.cTree = cTreeIn;
		this.cTable = cTableIn;
		this.gui = guiIn;
		this.margin = marginIn;
		this.leafSpace=leafSpaceIn;
		this.leafSpaceSummary=leafSpaceIn/2;
		this.originHt=0;//set to higher during summary graph
		this.htScaleFactorSummary=null;//set later
		this.leafLabelSpace=leafLabelSpaceIn;
		this.marginLeft= this.margin+this.leafLabelSpace;

		//associate methods
		this.cvY=Canvas_cvY;
		this.cvYSummary=Canvas_cvYSummary;
		this.cvX=Canvas_cvX;
		this.cvXSummary=Canvas_cvXSummary;
		this.cVPt=Canvas_cVPt;
		this.cVPtSummary=Canvas_cVPtSummary;
		this.getLeafPt=Canvas_getLeafPt;
		this.getLeafPtSummary=Canvas_getLeafPtSummary;
		this.calculateScaleFactor=Canvas_calculateScaleFactor;
		this.getGraphXfromCvX=Canvas_getGraphXfromCvX;
		this.createPaper=Canvas_createPaper;
		
		//derived attributes
		this.screenWidth=$(window).width();
		$( "#traceDump" ).append("Window.width="+this.screenWidth+", ");

		this.widthForCanvas=this.screenWidth-50;// allowing for scroll bars etc
		this.noOfLeafs = this.cTable.getNoOfLeaves();
		this.largestHt = this.cTree.getRoot().xHt;
		this.htScaleFactor=this.calculateScaleFactor(
				this.largestHt,this.widthForCanvas,this.margin,this.leafLabelSpace);
		$( "#traceDump" ).append("isIPad()="+(navigator.platform == "iPad")+", ");
		
		//declare and draw the canvas
		this.createPaper(
			(this.largestHt*this.htScaleFactor)+(this.margin*2)+this.leafLabelSpace,
			((this.noOfLeafs+1)*this.leafSpace)+(this.margin*2)
			);
	} 	
	//End of Canvas class
	//######################################################

	//######################################################
	/**
	 * The Dendrogram class encapulates most of the methods for drawing, removing, 
	 * and redrawing the dendrogram and 
	 * summary dendrogram (Some drawing methods are within the ClusterNode class.)
	 * <br /><br />
	 * 
	 * The  constructor draws a dendrogram from the given ClusterTree.<br />
	 * It calls drawNode() to recursively draw the tree. <br />
	 * It calls setDragParams() to set up some functions as Raphael drag parameters
	 * for the threshold bar.<br />
	 * It calls drawThresholdBar() to draw the threshold bar onto the graph<br />
	 * It calls this.gui.showThreshold() to display the threshold value<br />
	 * It calls this.gui.showNoOfGroups(1) to display the no of Groups value 
	 * initially as 1
	 * <br /><br />
	 * Important Dendrogram attributes:<br />
	 * -thresholdBar - the dragable bar graphic object.<br />
	 * -groupList - the list of groups formed in response to a user command.<br />
	 * -isSummary - default false. Set to true if displaying summary dendrogram.
	 * <br /><br />
	 * 
	 * Some attributes are set in the constructor for tuning:<br />
	 * -leafLabelLength - the number of characters allowed for the leaf labels<br />
	 * -some thresholdBar dimensions<br />
	 * -some group band styling attributes<br />
	 * 
	 * @class Dendrogram class
	 * 
	 */

	//Methods

	/**
	 * Draws a given cluster node onto the dendrogram.
	 * 
	 * @method drawCluster
	 * 
	 * @param nodeIn {ClusterNode object} The cluster node to be drawn
	 * 
	 * @returns {boolean} but value not used
	 */
	function Dendrogram_drawCluster(nodeIn){

		var parentHt=null;// to hold ht of parent or notional parent ht for root

		//if nodeIn is the root set parentHt to nodeIn.xHt*1.05 i.e. 5% higher 
		//than max ht
		//this will give an area to interact with above the root node
		if (nodeIn.isRoot()){
			parentHt = nodeIn.xHt*1.05;
		}else{
			parentHt=nodeIn.parent.xHt;
		}

		//draw the cluster by starting at the cluster point node.xHt, node.yPos 
		//scribing over the crossbar section returning to the cluster 
		//point and finishing by scribing up the stem section creating 
		//one shape from a single path call

		//create a diagram point for the cluster
		var clusterPt=new point(nodeIn.xHt,nodeIn.yPos);

		//define the graph points for the cluster with no scaling factors
		var clusterPt=new point(nodeIn.xHt,nodeIn.yPos);
		var boundA=new point(nodeIn.xHt,nodeIn.childA.yPos);
		var boundB=new point(nodeIn.xHt,nodeIn.childB.yPos);
		var cEnd=new point(parentHt,nodeIn.yPos);

		//now convert them to canvas coordinates incorporating 
		//scale factors and margins
		clusterPtCv=this.canvas.cVPt(clusterPt);   	
		boundACv=this.canvas.cVPt(boundA);
		boundBCv=this.canvas.cVPt(boundB);
		cEndCv=this.canvas.cVPt(cEnd);

		//now create the required lengths for the path
		var cToBoundA= clusterPtCv.y-boundACv.y;
		var cToBoundB= clusterPtCv.y-boundBCv.y;
		var cToHt= cEnd.x-clusterPtCv.x;

		//draw the element as a path on the raphael canvas and assign it to 
		//the node element
		nodeIn.element=this.canvas.paper.path(
				"M "+clusterPtCv.x+" "+clusterPtCv.y+
				" l 0 "+cToBoundA+" l 0 "+(-1*cToBoundA)+
				" l 0 "+cToBoundB+" l 0 "+(-1*cToBoundB)+
				" l "+cToHt+" 0"
		);
		nodeIn.drawBoundingBox(this.canvas.paper);//draw its bounding box
		this.styleCluster(nodeIn);
		return true;//has no effect       
	}
	
	/**
	 * Draws a given cluster node onto the summary dendrogram.<br /><br />
	 * 
	 * if the nodeIn is not the root then check parent ht (i.e. ALWAYS 
	 * draw the root)<br />
	 * 		if the xHt of the parent < cutoffIn <br />
	 * 			then set element and bounding box to null and return<br />
	 * else<br />
	 * set element and bounding box to null<br />
	 * set the left hand end to be the cutoffIn Ht and draw the new cluster
	 * 
	 * @method drawClusterSummary
	 * 
	 * @param cutoffIn {number}  the Ht at which the graph is truncated
	 * @param nodeIn {ClusterNode object}  The cluster node to be drawn.
	 * 
	 * @returns {boolean}  but value not used
	 */
	function Dendrogram_drawClusterSummary(nodeIn,cutoffIn){
		
		if(!nodeIn.isRoot()){
			if(nodeIn.parent.xHt<cutoffIn){
				nodeIn.element=null;
				nodeIn.bBox=null;
				return true;
			}
		}
		var parentHt=null;// to hold ht of parent or notional parent ht for root

		//if nodeIn is the root set parentHt to nodeIn.xHt*1.05 
		//i.e. 5% higher than max ht
		//this will give an area to interact with above the root node
		if (nodeIn.isRoot()){
			parentHt = nodeIn.xHt*1.05;
		}else{
			parentHt=nodeIn.parent.xHt;
		}
		//draw the cluster by starting at the cluster point node.xHt, node.yPos 
		//scribing over the crossbar section returning to the cluster point 
		//and finishing by scribing up the stem section creating one shape 
		//from a single path call adjust all Hts by subtracting the cutoffIn Ht

		//define the graph points for the cluster with no scaling factors
		//first check cluster Ht and adust to cutoff if below cutoff Ht
		if(nodeIn.xHt<cutoffIn){
			var clusterSummaryHt=cutoffIn;
		}else{
			clusterSummaryHt=nodeIn.xHt;
		}
		
		var clusterPt=new point(clusterSummaryHt-cutoffIn,nodeIn.yPos);
		var boundA=new point(clusterSummaryHt-cutoffIn,nodeIn.childA.yPos);
		var boundB=new point(clusterSummaryHt-cutoffIn,nodeIn.childB.yPos);
		var cEnd=new point(parentHt-cutoffIn,nodeIn.yPos);

		//now convert them to canvas coordinates incorporating 
		//scale factors and margins
		clusterPtCv=this.canvas.cVPtSummary(clusterPt);   	
		boundACv=this.canvas.cVPtSummary(boundA);
		boundBCv=this.canvas.cVPtSummary(boundB);
		cEndCv=this.canvas.cVPtSummary(cEnd);

		//now create the required lengths for the path
		var cToBoundA= clusterPtCv.y-boundACv.y;
		var cToBoundB= clusterPtCv.y-boundBCv.y;
		var cToHt= cEnd.x-clusterPtCv.x;
	
		//draw the element as a path on the raphael canvas and assign it 
		//to the node element
		nodeIn.element=this.canvas.paper.path(
				"M "+clusterPtCv.x+" "+clusterPtCv.y+
				" l 0 "+cToBoundA+" l 0 "+(-1*cToBoundA)+
				" l 0 "+cToBoundB+" l 0 "+(-1*cToBoundB)+
				" l "+cToHt+" 0"
		);

		nodeIn.drawBoundingBox(this.canvas.paper);//draw its bounding box
		this.styleCluster(nodeIn);
		return true;//has no effect  		     
	}
	/**
	 * Styles a given cluster
	 * 
	 * @method styleCluster
	 * 
	 * @param nodeIn {ClusterNode object}  The cluster node to style (a Node)
	 */
	function Dendrogram_styleCluster(nodeIn){
		//use the attr method to style the axis object
		nodeIn.element.attr(
				{
					'stroke-width':this.strokeWidth,
					'stroke-linecap':'round',
					title: nodeIn.shortDescription
				});
		this.addEvents(nodeIn,this.gui,this);//add events to the new node
	}
	/**
	 * Styles a given leaf
	 * 
	 * @method styleLeaf
	 * 
	 * @param nodeIn {ClusterNode object} The leaf node to style 
	 */
	function Dendrogram_styleLeaf(nodeIn){
		//use the attr method to style the axis object
		nodeIn.element.attr(
				{
					'stroke-width':this.strokeWidth,
					'stroke-linecap':'round',
					//title: "Leaf "+nodeIn.idNo
					title: nodeIn.shortDescription
				});
		this.addEvents(nodeIn,this.gui,this);//add events to the new node
	}
	/**
	 * Draws a leaf graphic element
	 * @method drawLeaf
	 * 
	 * @param nodeIn {ClusterNode object} The node describing the leaf 
	 * 
	 * @return {boolean} but value not used
	 */
	function Dendrogram_drawLeaf(nodeIn){
		//generate a Raphael canvas pixel coordinate  	
		var leafPt=this.canvas.getLeafPt(nodeIn.yPos);

		//points and dimension for the stem part
		var parentHt=nodeIn.parent.xHt;
		var cEnd=new point(parentHt,nodeIn.yPos);
		cEndCv=this.canvas.cVPt(cEnd);
		var cToHt= cEnd.x-leafPt.x;
		//scribe out from the leaf pt to a box around the leaf pt. The box to 
		//enclose an imaginary circle radius of this.leafRadius. 
		//Then return to the point on the edge of the box in line with the stem. 
		//Them scribe the stem up to the parent height (or down to the parent 
		//height if the parent height is really low or zero.   	
		nodeIn.element=this.canvas.paper.path(
				"M "+leafPt.x+" "+leafPt.y+
				" l "+this.leafRadius+" 0 "+
				"l 0 "+(-1*this.leafRadius)+
				" l "+(-2*this.leafRadius)+" 0"+
				" l 0 "+(2*this.leafRadius)+
				" l "+(2*this.leafRadius)+" 0"+
				" l 0 "+(-1*this.leafRadius)+
				" l "+(cToHt-this.leafRadius)+" 0"
		);
		nodeIn.drawBoundingBox(this.canvas.paper);//draw its bounding box
		this.styleLeaf(nodeIn);//and add events
		return true;//has no effect
	}
	/**
	 * Draws a leaf graphic element for the Summary dendrogram. Summary leaves are 
	 * different to those on the normal chart. They have no base and vary 
	 * in length if they are to be drawn at all.<br /><br />
	 * 
	 * If the xHt of the parent < cutoffIn then set element to null and return<br />
	 * else<br />
	 * set the left hand end to be the cutoffIn Ht and draw the leaf<br />
	 * 
	 * @method drawLeafSummary
	 * 
	 * 
	 * @param nodeIn {ClusterNode object} The node describing the leaf (a Node)
	 * @param cutoffIn {number} the Ht at which the graph is truncated
	 * @returns {boolean} but value not used.
	 */
	function Dendrogram_drawLeafSummary(nodeIn,cutoffIn){
		
		if(nodeIn.parent.xHt<cutoffIn){
			nodeIn.element=null;
			nodeIn.bBox=null;
			return true;
		}
		//generate a Raphael canvas pixel coordinate at the truncated Ht
		var leafPt=this.canvas.getLeafPtSummary(nodeIn.yPos);

		//points and dimension leaf summary line
		var parentHt=nodeIn.parent.xHt;
		var cEnd=new point(parentHt-cutoffIn,nodeIn.yPos);
		var cEndCv=this.canvas.cVPtSummary(cEnd);
		var cToHt= cEndCv.x-leafPt.x;
		//represent the truncated leaf
		//Scribe a T shape as for a cluster but the head of the T is 
		//2/3rds the width of a leafSpaceSummary. The leg of the T runs 
		//from the parent ht down to the cutoff ht, i.e. the base line. 
		//The T head is needed to give the leaf some body for events
		//on its bounding box.
		nodeIn.element=this.canvas.paper.path(
				"M "+leafPt.x+" "+leafPt.y+
				" l 0 "+(-this.canvas.leafSpaceSummary*1/3)+
				" l 0 "+(this.canvas.leafSpaceSummary*2/3)+
				" l 0 "+(-this.canvas.leafSpaceSummary*1/3)+
				" l "+cToHt+" 0"
		);
				
		nodeIn.drawBoundingBox(this.canvas.paper);//draw its bounding box
		this.styleLeaf(nodeIn);
		return true;//has no effect
	}	
	/**
	 * Draws a text element on the graph next to the leaf.
	 * @method drawLeafLabel
	 * 
	 * @param nodeIn {ClusterNode object} The node describing the leaf 
	 * 
	 * @returns {Boolean} but value not used.
	 */
	function Dendrogram_drawLeafLabel(nodeIn){

		//generate a Raphael canvas pixel coordinate eqivalent to the leafPt
		var labelPt=this.canvas.getLeafPt(nodeIn.yPos); 	
		//set the x coord back from axis
		labelPt.x=labelPt.x-this.leafRadius-4;
		nodeIn.label=this.canvas.paper.text(labelPt.x, labelPt.y, 
											nodeIn.shortDescription);
		nodeIn.label.attr({'text-anchor': 'end'});
		
		return true;//has no effect
	}	
	/**
	 * Declares event handlers for the shape using the anonymous function.<br />
	 * Events for:<br />
	 * - mouseover, or tap in iPad<br />
	 * - mouseout, or tap something else in iPad<br />
	 * - mouse click, or tap in iPad<br /><br />
	 * 
	 * The parameters are passed here rather than referred to by "this" even
	 * when they are attributes local to the class because the "this" keyword
	 * has its scope hijacked by the event. The keyword, "this",
	 * becomes the event object during an event.
	 * 
	 * @method addEvents
	 * 
	 * @param nodeIn {ClusterNode object} The cluster tree node concerned.
	 * @param guiIn {Gui object} the gui.
	 * @param dendrogramIn (Dendrogram object) the dendrogram. Needed to 
	 * avoid "this" keyword for event handling
	 */   
	function Dendrogram_addEvents(nodeIn,guiIn,dendrogramIn){
		//show branch
		//aim is to produce a highlighting of the given cluster and all its childen
		nodeIn.bBox.node.onmouseover = 
			function(){
			nodeIn.showBranch(nodeIn, true, dendrogramIn.isSummary);
			this.style.cursor = 'pointer';   		
		};  		
		nodeIn.bBox.node.onmouseout = 
			function(){
			nodeIn.showBranch(nodeIn, false, dendrogramIn.isSummary);
			this.style.cursor = 'pointer';
		};
		//display descendants reports details of descendant nodes
		nodeIn.bBox.node.onclick = 
			function(){
			nodeIn.displayDescendants(nodeIn,guiIn);
		};		
	}	
	/**
	 * Declares event handlers for the shape using the anonymous function.<br />
	 * Events for:<br />
	 * - mouseover, or tap in iPad<br />
	 * - mouseout, or tap something else in iPad<br />
	 * - mouse click, or tap in iPad<br /><br />
	 * 
	 * The parameters are passed here rather than referred to by "this" even
	 * when they are attributes local to the class because the "this" keyword
	 * has its scope hijacked by the event. The keyword, "this",
	 * becomes the event object during an event.
	 * 
	 * @method addEventsToBand
	 * 
	 * @param groupIn {Group object} group which contains the band for the events
	 * @param guiIn {Gui object} the gui
	 * @param dendrogramIn (Dendrogram object) the dendrogram. Needed to 
	 * avoid "this" keyword for event handling
	 */   
	function Dendrogram_addEventsToBand(groupIn,guiIn,dendrogramIn){
		//show branch
		//aim is to produce a highlighting of the cluster represented
		//by the given group and all its childen.
		groupIn.band.node.onmouseover = 
			function(){
			groupIn.ancestorNode.showBranch(groupIn.ancestorNode, 
											true,dendrogramIn.isSummary);
			this.style.cursor = 'pointer';   		
		};  		
		groupIn.band.node.onmouseout = 
			function(){
			groupIn.ancestorNode.showBranch(groupIn.ancestorNode, 
											false, dendrogramIn.isSummary);
			this.style.cursor = 'pointer';
		};
		//display descendants reports details of descendant nodes
		groupIn.band.node.onclick = 
			function(){
			groupIn.ancestorNode.displayDescendants(groupIn.ancestorNode,guiIn);
		};		
	}	
	/**
	 * Appends cluster groups to a list<br /><br />
	 * if the nodeIn is above the threshold then call itself recursively on 
	 * childA and child B<br />
	 * else make a group from its descendants, append it to the list and return
	 * 
	 * @method appendGroups

	 * @param nodeIn {Group object} the node being examined.
	 * @param groupListIn {GroupList object} the groupList to hold the 
	 * 			resulting groups.
	 * @param thresholdIn {number} the threshold value used to define the groups.
	 */
	function Dendrogram_appendGroups(nodeIn,groupListIn,thresholdIn){
		//check for nodeIn==null is so return groupList unchanged
		if(nodeIn==null){
			return groupListIn;
		};//attempt no processing on a null node		
		//if the node is above the threshold then call itself on childA and child B
		if(nodeIn.xHt>thresholdIn){
			groupListIn=this.appendGroups(nodeIn.childA,groupListIn,thresholdIn);
			groupListIn=this.appendGroups(nodeIn.childB,groupListIn,thresholdIn);
			return groupListIn;
		}else{
			//make a group from this node's descendants and return
			//ref Group constructor Group(leafListIn, ancestorNodeIn)
			var foundGroup=new Group(nodeIn.getDescendants(nodeIn),nodeIn);
			//append to group list
			groupListIn.addToEnd(foundGroup);
			return groupListIn;
		}
	}
	/**
	 * Dendrogram_applyGroupingThreshold<br />
	 * 1) Defines groups of leaves based on a threshold mergeHt.<br />
	 * 2) Overlays rectangles on the dendrogram to highlight. Those clusters 
	 * 	  below the threshold mergeHt form groups differentiated by colour 
	 * 	  or shading of the overlayed rectangles.<br />
	 * 3) Display the details of the groups under the Groups tab.<br />
	 * 4) Feedback to the user via a dialoge that all this has occured.<br /><br />
	 * 
	 * Given a threshold number, do a depth first search to find the first 
	 * cluster below the threshold (with mergeHt < thresholdIn). The depth 
	 * first search is carried out by the appendGroups() method which calls 
	 * itself recursively. The search is started here by calling appendGroups 
	 * with the tree root node.
	 * 
	 * @method applyGroupingThreshold
	 * 
	 * @param thresholdIn {number}  the threshold to be applied.
	 */
	function Dendrogram_applyGroupingThreshold(thresholdIn){	
		//This is a depth first search to find the first cluster below the threshold
		//Then apply the current group number to all that cluster's decendants
		//then advance the group number, ascend to the parent and apply the next 
		//group number to any remaining group and so on.
		//The group number is a GroupList attribute.
		
		//validate the incoming threshold in case it was manually entered
		var validThreshold=thresholdIn-0;//Enforces a number
		//now check for in being NaN, if so then set it to zero
		if (isNaN(validThreshold)){
			validThreshold=0;
		}
		//only allow minimum of originHt
		if (validThreshold<this.canvas.originHt){
			validThreshold=this.canvas.originHt;
		}
		//only allow maximum of canvas.largestHt
		if (validThreshold>this.canvas.largestHt){
			validThreshold=this.canvas.largestHt;
		}
		
		var messageString="";//parameter for displayGroupList method
		this.groupList.reset();//empty any existing grouplist
		this.groupList.threshold=validThreshold;//store the threshold
		//first build the group list
		//search the tree adding groups to this.groupList
		this.groupList=this.appendGroups(this.cTree.getRoot(),
										this.groupList,validThreshold);	
		//output the groups list to the div 
		this.displayGroupList(this.groupList,messageString);
		this.drawGroupBands(this.groupList);//draw the bandsize());
		//threshold input may have been keyed in
		this.setThresholdBarToMatch(validThreshold);
		//in case an invalid threshold was entered
		this.gui.showThreshold(validThreshold);
		this.gui.showNoOfGroups(this.groupList.getSize());
	}
	/**
	 * Displays a single group in a div on the groups tab
	 * by appending it to the current contents of the groups tab
	 * 
	 * @method displayOneGroup
	 * 
	 * @param leafArrayIn {Array of ClusterNode objects} the leaf array which 
	 * represents the group
	 * @param groupIdIn {string} a string identifier or label for the group
	 */
	function Dendrogram_displayOneGroup(leafArrayIn,groupIdIn){ 
		//build the html to put in the group's display div
		var outputString=groupIdIn+": <br/>";
		//append all but the last item to the string with a comma separator
		for(var i=0;i<leafArrayIn.length-1;i++){
			outputString+=leafArrayIn[i].shortDescription+", ";
		}
		//append the last item, no comma
		outputString+=leafArrayIn[leafArrayIn.length-1].shortDescription;
		//create the group div
		var outputHtml='<div class="groupDiv" title="'+groupIdIn 
						+'" id="'+groupIdIn+'">';
		outputHtml+=outputString+'</div>';
		//append the group div to the current content of the groups tab
		var previousContent=$(this.gui.groupsTab).html();
		$(this.gui.groupsTab).html(previousContent+outputHtml);
	}    
	/** 
	 * Outputs details of the designated group list.
	 * Makes calls to this.displayOneGroup() and this.gui.notifyGroupFormation().
	 * 
	 * @method displayGroupList
	 * 
	 * @param groupListIn {GroupList object} an array of groups. 
	 * @param messageIn {string} String to preceed the display of the list
	 * Each group is an array of leaves.
	 */
	function Dendrogram_displayGroupList(groupListIn,messageIn){ 
		//loop through the list displaying in the div
		//var tempLeafArray=new Array();
		var resultsSummary=messageIn;
		resultsSummary+=groupListIn.getSize()+" groups were formed using";
		resultsSummary+=" the threshold set at "+groupListIn.threshold;	
		resultsSummary+=". See the 'Groups' tab for details.";	
		//var resultsString="Contents of Group List : ";
		var resultsString="";
		resultsString+="Threshold: "+groupListIn.threshold+"<br/>";
		if(groupListIn.isEmpty()){//if the list is empty say so
			resultsString+="The list is empty";
			return;
		}
		//add the list introduction into the output div clearing what was there
		var groupsIntroHtml='<div class="groupDiv" title="Intro" id="Intro">';
		groupsIntroHtml+=resultsString+'</div>';
		$(this.gui.groupsTab).html(groupsIntroHtml);
		//now trundle through the list displaying the groups
		this.displayOneGroup(groupListIn.getFirst().leafList,'Group-1');
		var limit=groupListIn.getSize();
		for (i=2;i<=limit;i++){
			this.displayOneGroup(groupListIn.getNext().leafList,'Group-'+i);
		}
		//inform user 
		this.gui.notifyGroupFormation(resultsSummary,groupListIn);
	}
	/**
	 * Takes a group and draws an overlay on the graph to delineate it.
	 * The band is a rectangle of length (the X dimension), thresholdIn. 
	 * Its top and bottom (the Y dimension) defined by groupIn.topBound and 
	 * groupIn.bottomBound<br /><br />
	 * 
	 * Makes a call on this.addEventsToBand().
	 * 
	 * @method drawOverlayBand
	 * @param groupListIn {Group object} an array of leaves. 
	 * @param {number} the threshold defining the groups
	 */
	function Dendrogram_drawOverlayBand(groupIn,thresholdIn){	
		//draw the band assigning it to the group's band attribute;
		//First gather the coordinates and dimensions
		// a raphael rectangle needs x,y,width,and ht.

		//Dimensions without scaling or margins:-
		//create an origin point for the band rectangle and the top left corner. 
		//(At the base of the dendrogram)
		var bandPt=new point(0,groupIn.topBound-0.5);
		// now the rentangle width
		
		//check for summary graph and make allowances
		if (this.canvas.originHt>0){
			//it is a summary graph
			var bandLength=thresholdIn-this.canvas.originHt; 
			var bandHt=(groupIn.bottomBound-groupIn.topBound)+1;
			//now convert them to canvas coordinates incorporating 
			//scale factors and margins
			bandPt=this.canvas.cVPtSummary(bandPt);
			bandLength=bandLength*this.canvas.htScaleFactorSummary;
			bandHt=bandHt*this.canvas.leafSpaceSummary; 
		}else{
			//it is a normal graph
			var bandLength=thresholdIn; 
			var bandHt=(groupIn.bottomBound-groupIn.topBound)+1;
			//now convert them to canvas coordinates incorporating 
			//scale factors and margins
			bandPt=this.canvas.cVPt(bandPt);
			bandLength=bandLength*this.canvas.htScaleFactor;
			bandHt=bandHt*this.canvas.leafSpace; 
		}
		//draw it 
		groupIn.band=this.canvas.paper.rect(
				bandPt.x,
				bandPt.y,
				bandLength,
				bandHt);
		//style it transparent
		groupIn.band.attr(
				{
					'fill':this.groupBandColour,
					'stroke-opacity':0,
					'fill-opacity':this.groupBandOpacity,
					'title':'Group from '+groupIn.ancestorNode.shortDescription
				});
		//add events to the band
		this.addEventsToBand(groupIn,this.gui,this);
		return true;//has no effect
	}
	/**
	 * Loops through the given group list drawing bands on the graph. It 
	 * alternates styling to create banding.<br /><br />
	 * 
	 * Makes calls on this.drawOverlayBand()
	 * 
	 * @method drawGroupBands
	 * @param groupListIn {GroupList object} an array of groups. 
	 */
	function Dendrogram_drawGroupBands(groupListIn){ 
		//loop through the list drawing bands on the graph
		if(groupListIn.isEmpty()){//if the list is empty do nothing
			return;
		}
		//now trundle through the list overlaying a band for each.

		//draw the overlay for the first band		
		this.drawOverlayBand(groupListIn.getFirst(),groupListIn.threshold);
		//now draw any remaining overlays
		var noOfGroups=groupListIn.getSize();
		for(var i=2;i<=noOfGroups;i++){
			//do the drawing 
			this.drawOverlayBand(groupListIn.getNext(),groupListIn.threshold);
			
			//and alternate styling
			if (i%2==0){
				groupListIn.getCurrent().band.attr(
						{
							'fill':this.groupBandAlternateColour,
							'stroke-opacity':0,
							'fill-opacity':this.groupBandOpacity
						});
			}
		}
		//record that the groups have been drawn
		groupListIn.drawn=true;
	}
	/**
	 * Draws the tree by starting with the root node and recursively 
	 * calling itself.<br /><br />
	 * 
	 * Makes calls on this.drawCluster(), this.drawLeaf() and this.drawLeafLabel().
	 * 
	 * @method drawNode
	 * 
	 * @param nodeIn {ClusterNode object} the node to be drawn
	 * @return {boolean} but value not used.
	 */
	function Dendrogram_drawNode(nodeIn){  	
		//draws the tree by starting with the root node 
		//and recursively calling itself
		var childA=nodeIn.childA;
		var childB=nodeIn.childB;

		//if the node is a leaf then draw the leaf and return
		//else draw the cluster
		if(nodeIn.isLeaf()){
			this.drawLeaf(nodeIn);
			this.drawLeafLabel(nodeIn);
			return true;
		}
		this.drawCluster(nodeIn);//draw the node  	

		//draw the child nodes
		this.drawNode(childA);
		this.drawNode(childB);

		return true;//has no effect  
	}	
	/**
	 * Draws the summary tree by starting with the root node and recursively 
	 * calling itself<br /><br />
	 * 
	 * Makes calls on this.drawClusterSummary() and this.drawLeafSummary().
	 * 
	 * @method drawNodeSummary
	 * 
	 * @param nodeIn {ClusterNode object} the node to be drawn
	 * @param cutoffIn {number} the mergeHt below which nothing is to be drawn
	 * @return {Boolean} but value not used.
	 */
	function Dendrogram_drawNodeSummary(nodeIn,cutoffIn){  	
		var childA=nodeIn.childA;
		var childB=nodeIn.childB;

		//if the node is a leaf then draw the leaf and return
		//else draw the cluster
		if(nodeIn.isLeaf()){
			this.drawLeafSummary(nodeIn,cutoffIn);
			return true;
		}
		this.drawClusterSummary(nodeIn,cutoffIn);//draw the node  	

		//draw the child nodes
		this.drawNodeSummary(childA,cutoffIn);
		this.drawNodeSummary(childB,cutoffIn);

		return true;//has no effect  
	}
	/**
	 * Draws the dragable threshold adjuster bar
	 * 
	 * @method  drawThresholdBar
	 * 
	 * @return {Boolean} but value not used.
	 */
	function Dendrogram_drawThresholdBar(){ 
		//set the graph point for the top left of the bar
		//It is set a number of leaf spaces above the top so it should protrude up
		var barPt=new point(this.canvas.largestHt,this.thresholdBarOverlapTop*-1);

		//generate a Raphael canvas pixel coordinate  	
		var barPtCv=this.canvas.cVPt(barPt);
		//set the bar length (Ht)
		var leafSpace=this.canvas.leafSpace;
		if (this.canvas.originHt>0){//it is a summary graph
			leafSpace=this.canvas.leafSpaceSummary;
		}
		var barLength=(this.canvas.noOfLeafs+this.thresholdBarLengthPlus)
						*leafSpace;
		//draw it 
		this.thresholdBar=this.canvas.paper.rect(
				barPtCv.x,
				barPtCv.y,
				this.thresholdBarWidth,
				barLength);
		//style it 
		this.thresholdBar.attr(
				{
					'stroke':'green',
					'fill':'green',
					'stroke-opacity':0.5,
					'fill-opacity':0.5,
					'title':'Threshold adjuster bar'
				});   	
		//make it dragable
		this.thresholdBar.drag(this.move,this.dragger,this.up);
		return true;//has no effect
	}
	/**
	 * Detects the threshold bar position and returns that in terms of the xaxis
	 * merge Ht.<br /><br />
	 * 
	 * Makes a call on this.canvas.getGraphXfromCvX().
	 * 
	 * @method getThresholdPos
	 * 
	 * @param barIn {graphic object} The threshold bar whose pos is to be detected
	 * 
	 * @return {number} the xPos of the bar
	 */
	function Dendrogram_getThresholdPos(barIn){    	
		//var barX=this.thresholdBar.x;//the shape x coord
		return this.canvas.getGraphXfromCvX(barIn.x);
	}
	/**
	 * Sets Dendrogram properties to serve as parameters for the 
	 * Raphael drag() method. 
	 * Based on Dmitry's Graffle example http://raphaeljs.com/graffle.html
	 * <br /><br />
	 * 
	 * Makes a call on guiIn.showThreshold(). 
	 * 
	 * @method setDragParams
	 * 
	 * @param canvasIn {Canvas object} The canvas
	 * @param guiIn {Gui object} The gui
	 */
	function Dendrogram_setDragParams(canvasIn, guiIn){ 
		
		var originHtNum=canvasIn.originHt-0;//the -0 forces a number type conversion
		//##############################################################
		// Set up drag method vars for use in drag() method
		// -dragger is the storage of the original start pos
		// -move is the acceptance of the new coord(s) and shape redraw
		// -up is the action to do on release
		//###############################################################
		this.dragger = function () {
			this.ox = this.attr("x");
			//this.oy = this.attr("y");//uncomment to unlock y movement
		};
		this.move = function (dx, dy) {
			var att = new Object();
			var limit=canvasIn.largestHt;
			//limit the movement to with the x axis bounds
			if (((this.ox + dx)>=canvasIn.marginLeft)&&
					((this.ox + dx)<=(canvasIn.cvX(limit)))){
				att.x=this.ox + dx;
			}else{//limit the move
				if((this.ox + dx)<canvasIn.marginLeft){//at low end
					att.x=canvasIn.marginLeft;
				}else if ((this.ox + dx)>(canvasIn.cvX(limit))){//at high end
					att.x=canvasIn.cvX(limit);
				}	
			}
			//att.y=this.oy + dy;//uncomment to unlock y movement
			this.attr(att);
			//thePaper.safari();//forces a redraw
			//calculate value to display depending on originHt (affected 
			//by summary graph)
			if (originHtNum>0){//it is a summary graph
			    var displayVal=
			    	(((att.x-canvasIn.marginLeft)/canvasIn.htScaleFactorSummary));
			}else{//just a normal graph
				var displayVal=((att.x-canvasIn.marginLeft)/canvasIn.htScaleFactor);
			}
			//in the case of summary chart this adds on the cutoff Ht
			displayVal+=originHtNum;
			//format the number
			if (displayVal.toPrecision){//if browser supports toPrecision() method 
				displayVal=displayVal.toPrecision(5);
			}  
			guiIn.showThreshold(displayVal);
		};
		this.up = function () {
			//do this code when the user lets go
			//but for now do nothing
		};
	}
	/**
	 * Sets the threshold bar position in response to some other control
	 * 
	 * @method setThresholdBarToMatch
	 * 
	 * @param thresholdIn {number} the threshold to set
	 * @return {number} the Merge Ht value of the new threshold position
	 */
	function Dendrogram_setThresholdBarToMatch(thresholdIn){  
		//Check for this being a summary graph and allow for altered threshold
		//position
		if (this.canvas.originHt>0){
			//it is a summary graph
			var endX=((thresholdIn-this.canvas.originHt)
					*this.canvas.htScaleFactorSummary)
					+this.canvas.marginLeft;
		}else{
			//just a normal graph
			var endX=(thresholdIn*this.canvas.htScaleFactor)+this.canvas.marginLeft;
		}		
		var startX=this.thresholdBar.attr('x');
		var dX=endX-startX;
		this.thresholdBar.translate(dX,0);//translate the bar by the x difference
		return thresholdIn;//the merge ht
	}	
	/**
	 * Sets the threshold bar to the maximum 
	 * merge Ht.
	 * 
	 * @method setThresholdBarToMax
	 * 
	 * @return {number} the Merge Ht value of the new threshold position
	 */
	function Dendrogram_setThresholdBarToMax(){ 
		var startX=this.thresholdBar.attr('x');
		//set the end X to be that of the max Ht
		var endX=(this.canvas.largestHt*this.canvas.htScaleFactor)
					+this.canvas.marginLeft;
		var dX=endX-startX;
		this.thresholdBar.translate(dX,0);
		return this.canvas.largestHt;//the merge ht
	}	
	/**
	 * Sets the threshold bar to zero (or the origin Ht in the case of Summary graph)
	 * 
	 * @method setThresholdBarToZero
	 * 
	 * 
	 * @return the Merge Ht value of the new threshold position
	 */
	function Dendrogram_setThresholdBarToZero(){
		
		var startX=this.thresholdBar.attr('x');
		var endX=this.canvas.marginLeft;
		var dX=endX-startX;
		this.thresholdBar.translate(dX,0);
		return this.canvas.originHt;//the merge ht at the orgin
	}	
	/**
	 * Sets the threshold bar to one step down the 
	 * merge Ht.
	 * 
	 * @method setThresholdBarToLess
	 * 
	 * @param fractionIn {number} the fraction of the full travel to make each step
	 * 			e.g. 20 means each step is 1/20th of the full travel
	 * 
	 * @return {number} the Merge Ht value of the new threshold position
	 */
	function Dendrogram_setThresholdBarToLess(fractionIn){

		var startX=this.thresholdBar.attr('x');
		
		//Check for this being a summary graph and allow for altered threshold
		//position
		if (this.canvas.originHt>0){
			//it is a summary graph
			var step=((this.canvas.largestHt-this.canvas.originHt)/fractionIn)
						*this.canvas.htScaleFactorSummary;
		}else{
			//just a normal graph
			var step=(this.canvas.largestHt/fractionIn)*this.canvas.htScaleFactor;
		}			
		//set the end X to be one step down
		var endX=startX-step;
		if(endX<this.canvas.marginLeft){//check not off end of chart
			endX=this.canvas.marginLeft;
		}
		var dX=endX-startX;
		this.thresholdBar.translate(dX,0);
		//Check for this being a summary graph and allow for altered return pos
		if (this.canvas.originHt>0){
			//it is a summary graph
			return ((endX-this.canvas.marginLeft)/this.canvas.htScaleFactorSummary)
					+this.canvas.originHt;//the merge ht
		}
		//just a normal graph
		return (endX-this.canvas.marginLeft)/this.canvas.htScaleFactor;//the merge ht
	}	
	/**
	 * Sets the threshold bar to one step up the 
	 * merge Ht.
	 * 
	 * @method setThresholdBarToMore
	 * 
	 * @param fractionIn {number} the fraction of the full travel to make each step
	 * 			e.g. 20 means each step is 1/20th of the full travel
	 * 
	 * @return {number} the Merge Ht value of the new threshold position
	 */
	function Dendrogram_setThresholdBarToMore(fractionIn){

		var startX=this.thresholdBar.attr('x');
		//Check for this being a summary graph and allow for altered threshold
		//position
		if (this.canvas.originHt>0){
			//it is a summary graph
			var step=((this.canvas.largestHt-this.canvas.originHt)/fractionIn)
						*this.canvas.htScaleFactorSummary;
		}else{
			//just a normal graph
			var step=(this.canvas.largestHt/fractionIn)*this.canvas.htScaleFactor;
		}
		
		//set the end X to be one step up
		var endX=startX+step;
		//this next line works for both normal and summary chart ok
		if(endX>((this.canvas.largestHt*this.canvas.htScaleFactor)+
					this.canvas.marginLeft)){//check not off end of chart
			endX=(this.canvas.largestHt*this.canvas.htScaleFactor)
					+this.canvas.marginLeft;
		}
		var dX=endX-startX;
		this.thresholdBar.translate(dX,0);
		//Check for this being a summary graph and allow for altered return pos
		if (this.canvas.originHt>0){
			//it is a summary graph
			return ((endX-this.canvas.marginLeft)/this.canvas.htScaleFactorSummary)
					+this.canvas.originHt;//the merge ht
		}
		//normal graph
		return (endX-this.canvas.marginLeft)/this.canvas.htScaleFactor;//the merge ht
	}	
	/**
	 * Sets the threshold to be one which will give the required no of groups.
	 * <br /><br />
	 * Takes the given no of groups and by a binary search sucessively applies a 
	 * new threshold until it finds one that fits the required no of groups and 
	 * leaves the threshold bar set there.<br /><br />
	 * 
	 * The binary search is limited to 75 iterations if it does not find the right
	 * spot by then it stops and returns the nearest no of groups it could get.
	 * <br /><br /> 
	 * 
	 * Group formation can fail to achieve the number of requested groups. There 
	 * are 4 ways this can occur<br />
	 * 
	 * 1) after 75 binary search iterations if some data set had many many 
	 * 	clusters at very similar hieghts, it might be that the requested 
	 * 	no. of groups was not reachable, due to running out of iterations. 
	 * 	I recon this is highly unlikely at 75 iterations.<br />
	 * 2) An impossible number of groups was requested (maybe a decimal, or 
	 * 	less than 1, or greater than the number of leaves<br />
	 * 3) if there are any leaves which merge at Ht zero then these cannot be 
	 * 	allocated to separate groups. They must share a group with their 
	 * 	zero Ht cluster mate. <br />
	 * 4) if creating groups on a summary graph then if the threshold would 
	 * 	need to be below the cutoff to create the requested number of groups 
	 * 	then it won't go below the cutoff Ht.<br /><br />
	 * 
	 * Makes a call on:
	 * 		this.displayGroupList()<br />
	 * this.drawGroupBands()<br />
	 * this.setThresholdBarToMatch()<br />
	 * this.gui.showThreshold()<br />
	 * and<br />
	 * this.gui.showNoOfGroups()<br /><br />
	 * Also makes calls on a number of GroupList methods
	 * 
	 * @method setThresholdForNoOfGroups
	 * 
	 * @param noOfGroupsIn {number} the required no of groups
	 * 
	 */
	function Dendrogram_setThresholdForNoOfGroups(noOfGroupsIn){
		//eliminate less than 1 and also iron out any erroneous data entered 
		//by defaulting to 0 if entry is dodgy
		var desiredGroups=noOfGroupsIn-0;//Enforces a number
		//now check for in being NaN, if so then set it to zero
		if (isNaN(desiredGroups)){
			desiredGroups=0;
		}
		//only allow minimum of 1
		if (desiredGroups<1){
			desiredGroups=1;
		}
		var currentHigh=this.canvas.largestHt;
		var currentLow=this.canvas.originHt;
		//var currentGroups=this.canvas.noOfLeafs;
		var currentGroups=-1;
		var loopCount=0;
		var trialThreshold=currentHigh;//set in loop
		var message="";//user feedback
		//loop while currentGroups<>noIfGroupsIn and loopCount<50
		//	increment loopCount
		//	set trialThreshold to hakf way between high and low
		//	apply trialthreshold.
		//  set currentThreshold to the generated no of groups
		//end loop
		//if currentGroups<>noOfGroupsIn then output alternative groups chose diaog
		//else indicate sucess
		//show the current threshold anyway.
		while ( (currentGroups!=desiredGroups)&&(loopCount<75) ){
			loopCount++;
			if (desiredGroups!=1){//don't try a new threshold of we want just 1 grp
				trialThreshold=currentLow+(currentHigh-currentLow)/2;
			}
			//if the grouplist has be drawn to canvas then undraw
			if(this.groupList.drawn){
				this.groupList.reset();
			}else{//reset with no undraw
				this.groupList.resetButLeaveCanvasAlone();
			}
			this.groupList.resetButLeaveCanvasAlone();
			this.groupList.threshold=trialThreshold;
			this.groupList=this.appendGroups(this.cTree.getRoot(),
										this.groupList,trialThreshold);
			currentGroups=this.groupList.getSize();
			if (currentGroups<desiredGroups){//threshold is too high
				currentHigh=trialThreshold;
			}else{
				currentLow=trialThreshold;
			}
		}
		if (currentGroups!=noOfGroupsIn){
			//output message about unable to match up. This message is taged on
			// the front of the normal dialog about formed groups.
			
			//check for summary chart and output a mesage as appropriate
			if(this.canvas.originHt>0){
				message= "The desired no. of groups, " + noOfGroupsIn +
				" , was not possible. Maybe you did not enter a number or " +
				"or you entered a decimal or your number" +
				" was outside the number of leaves. Or, as you are working on a " +
				"truncated summary " +
				"dendrogram at the moment, it is possible that the cutoff " +
				"is higher than the threshold would need to be to achieve " +
				"the required number of groups. In that case you will " +
				"need to restore the full dendrogram and try again. Instead, ";
			}else{//this is not a summary chart
				message= "The desired no. of groups, " + noOfGroupsIn +
					" , was not possible. (The minimum no. of groups that can " +
					"be formed is 1. There is a theoretical maximum of " +
					"the number of leaves but if there are any leaf " +
					"pairs that cluster at a dissimilarity ht. of zero" +
					" then these must share a group). Instead, ";
				}
		}else{
			//indicate total success ... by mentioning nothing extra
			message = "";
		}
		//output the groups list to the div 
		this.displayGroupList(this.groupList,message);
		this.drawGroupBands(this.groupList);//draw the bands
		//set the threshold bar to match
		this.setThresholdBarToMatch(trialThreshold);	
		this.gui.showThreshold(trialThreshold);//display the threshold value
		//display the final no of groups achieved
		this.gui.showNoOfGroups(currentGroups);

		return true; // no effect
	}	
	/**
	 * Removes the groups from the dendrogram <br />
	 * 1) reset the grouplist<br />
	 * 2) reset the threshold to max and its field<br />
	 * 3) reset group no field to 1<br />
	 * 4) undraw the bands<br /><br />
	 * 
	 * Makes calls on gui methods and the this.groupList.reset() method.
	 * 
	 * @method removeGroups
	 * 
	 */
	function Dendrogram_removeGroups(){	

		this.groupList.reset();//empty the group list
		this.gui.showThreshold(this.setThresholdBarToMax());//reset the threshold
		//clear groups tab
		$(this.gui.groupsTab).html("The groups have been removed.");
		this.gui.clearGroupNotifer();//clear the notifyer tab
		this.gui.showNoOfGroups(1);//reset the display in the no of Groups field
	}
	/**
	 * Removes all of the drawn elements including the axes
	 * 
	 * @method removeAll
	 * 
	 */
	function Dendrogram_removeAll(){	
		//remove any groups
		this.removeGroups();
		//remove the axes and labels
		this.axes.remove();
		//remove nodes
		//this.removeNode(this.cTree.getRoot());
		//remove the threshold bar
		this.thresholdBar.remove();
	}
	/**
	 * Draws a summary dendrogram truncated at the current threshold ht.<br />
	 * 1) record the cutoff Ht from the threshold pos<br />
	 * 2) clear the canvas by removing the paper object<br />
	 * 3) set a new xaxis scale factor<br />
	 * 4) create a new paper object of appropriate size<br />
	 * 5) draw new axes<br />
	 * 6) draw the summary dendrogram taking into account the cutoff<br />
	 * 
	 * @method drawSummary
	 */
	function Dendrogram_drawSummary(){
		this.isSummary=true;
		var cutoff=this.gui.getThresholdVal()-0;//forces a number		
		this.setThresholdBarToMatch(cutoff);//threshold input may have been keyed		
		this.canvas.paper.remove();//erase the paper and all the drawn elements
		//remove any groups
		this.removeGroups();		
		//set the summary scale factor in the canvas
		this.canvas.htScaleFactorSummary=this.canvas.calculateScaleFactor(
				this.canvas.largestHt-cutoff,
				this.canvas.widthForCanvas, this.canvas.margin,
				this.canvas.leafLabelSpace);
		//create a new paper for the new chart. 
		//This requires that the new dimensions be calculated. x (width) is 
		//to be the same as for original dendrogram
		this.canvas.createPaper(this.canvas.xWidth,
						((this.canvas.noOfLeafs+1)
						*this.canvas.leafSpaceSummary)
						+(this.canvas.margin*2)
				);
		// draw new axes
		this.axes=new GraphAxes(theCanvas,cutoff,false);
		//draw the tree in summary
		this.drawNodeSummary(this.cTree.getRoot(),cutoff);
		//set the canvas origin Ht to cutoff
		this.canvas.originHt=cutoff;
		//set up some functions as Raphael drag parameters
		this.setDragParams(this.canvas,this.gui);
		this.drawThresholdBar();//draw the threshold tool onto the graph
		this.gui.showThreshold(this.canvas.largestHt);//display the threshold value
		this.gui.showNoOfGroups(1);//display the no of Groups value
	}	
	/**
	 * Redraws the dendrogram after displaying truncated graph
	 * 1) clear the canvas by removing the paper object
	 * 2) resets the canvas originHt to 0
	 * 4) create a new paper object of appropriate size
	 * 5) draw new axes
	 * 6) draw the dendrogram
	 * 
	 * @method restore
	 */
	function Dendrogram_restore(){	
		
		this.isSummary=false;
		this.canvas.paper.remove();//erase the paper and all the drawn elements
		//remove any groups
		this.removeGroups();
		//set the canvas origin Ht to 0
		this.canvas.originHt=0;
		//create a new paper for the new chart. 
		//This requires that the new dimensions be calculated. x (width) is 
		//to be the same as for original dendrogram
		this.canvas.createPaper(this.canvas.xWidth,
			((this.canvas.noOfLeafs+1)*this.canvas.leafSpace)+(this.canvas.margin*2)
						);
		// draw new axes
		this.axes=new GraphAxes(theCanvas,0,true);
		this.drawNode(this.cTree.getRoot());//draw the tree);
		//set up some functions as Raphael drag parameters
		this.setDragParams(this.canvas,this.gui);
		this.drawThresholdBar();//draw the threshold tool onto the graph
		this.gui.showThreshold(this.canvas.largestHt);//display the threshold value
		this.gui.showNoOfGroups(1);//display the no of Groups value	
	}
	
	//Constructor function
	/**
	 * Some attributes are set here for tuning
	 * 
	 * @constructor Dendrogram 
	 * 
	 * @param cTreeIn {ClusterTree object } the filled cluster tree root node
	 * @param canvasIn {Raphael canvas object} the Raphael canvas on which to 
	 * 		  draw the chart
	 * @param stylesIn {Styles object } various styles used in the dendrogram 
	 * 		  such as line thickness.
	 * @param guiIn {Gui object }an object giving access to user interface methods
	 * @param axesIn {GraphAxes object} the axes for the graph
	 */
	function Dendrogram(cTreeIn, canvasIn, stylesIn, guiIn, axesIn){

		//initialise attributes
		//For Tuning
		this.leafLabelLength=12;//number of characters allowed for the leaf labels
		this.thresholdBarWidth=20; //number of units wide 
		//number of leafSpaces longer than the chart Ht.
		this.thresholdBarLengthPlus=9;
		this.thresholdBarOverlapTop=4;
		//number of leafSpaces it protrudes above the chart.
		this.groupBandColour='purple';
		this.groupBandAlternateColour='yellow';
		this.groupBandOpacity=0.4;
	
		//other attributes
		this.isSummary=false;//default. set true if displaying summary dendrogram
		this.styles=stylesIn;
		this.cTree = cTreeIn;
		this.canvas = canvasIn;
		this.leafRadius = stylesIn.leafRadius;
		this.strokeWidth = stylesIn.strokeWidth;
		this.gui=guiIn;
		this.axes=axesIn;
		this.thresholdBar=null;// will be the dragable bar but null for now
		this.dragger=null;//set later by setDragParams
		this.move=null;//set later by setDragParams
		this.up=null;//set later by setDragParams

		//initialise the Grouplist using the current value of the slider
		this.groupList=new GroupList(this.gui.getThresholdVal());

		//associate methods
		this.drawNode = Dendrogram_drawNode;
		this.drawNodeSummary = Dendrogram_drawNodeSummary;
		this.drawLeaf = Dendrogram_drawLeaf;
		this.drawLeafSummary = Dendrogram_drawLeafSummary;
		this.drawLeafLabel = Dendrogram_drawLeafLabel;
		this.drawCluster = Dendrogram_drawCluster;
		this.drawClusterSummary = Dendrogram_drawClusterSummary;
		this.applyGroupingThreshold=Dendrogram_applyGroupingThreshold;
		this.appendGroups=Dendrogram_appendGroups;
		this.displayGroupList=Dendrogram_displayGroupList;
		this.drawGroupBands=Dendrogram_drawGroupBands;
		this.drawOverlayBand=Dendrogram_drawOverlayBand;
		this.displayOneGroup=Dendrogram_displayOneGroup;
		this.drawThresholdBar=Dendrogram_drawThresholdBar;
		this.setDragParams=Dendrogram_setDragParams;
		this.getThresholdPos=Dendrogram_getThresholdPos;
		this.setThresholdBarToMatch=Dendrogram_setThresholdBarToMatch;
		this.setThresholdBarToMax=Dendrogram_setThresholdBarToMax;
		this.setThresholdBarToZero=Dendrogram_setThresholdBarToZero;
		this.setThresholdBarToLess=Dendrogram_setThresholdBarToLess;
		this.setThresholdBarToMore=Dendrogram_setThresholdBarToMore;		
		this.setThresholdForNoOfGroups=Dendrogram_setThresholdForNoOfGroups;
		this.removeGroups=Dendrogram_removeGroups;
		this.drawSummary=Dendrogram_drawSummary;
		this.removeAll=Dendrogram_removeAll;
		this.restore=Dendrogram_restore;

		this.styleCluster = Dendrogram_styleCluster;
		this.styleLeaf = Dendrogram_styleLeaf;
		this.addEvents = Dendrogram_addEvents;
		this.addEventsToBand = Dendrogram_addEventsToBand;

		//processing to be done by the Constructor
		this.drawNode(this.cTree.getRoot());//draw the tree
		//set up some functions as Raphael drag parameters
		this.setDragParams(this.canvas,this.gui);
		this.drawThresholdBar();//draw the threshold tool onto the graph
		this.gui.showThreshold(this.canvas.largestHt);//display the threshold value
		this.gui.showNoOfGroups(1);//display the no of Groups value
	} 

	//End of Dendrogram class
	//######################################################

	//######################################################
	//Styles class
	/**
	 * Allows a node object to have its own reference to the styles on hand.
	 * Makes them readily available when responding to events<br /><br />
	 * 
	 * This class has no methods
	 * 
	 * @class Styles
	 */

	//Define functions for the methods
	//none for now

	//Constructor function
	/**
	 * @constructor Styles 
	 * 
	 * @param swIn {number} strokeWidth
	 * @param lrIn {number} leafRadius
	 * @param cIn {number} nodeHighlightColour
	 */    
	function Styles(swIn,lrIn,cIn){
		//initialise attributes
		//e.g.
		this.strokeWidth=swIn;
		this.leafRadius=lrIn;
		this.nodeHighlightColour=cIn;
		this.animateTime=2000;//used for timing fades etc
		//associate methods
		//none for now
	} 

	//End of Styles class
	//######################################################

	//######################################################
	//GroupList class
	/**
	 * The group list contains a list of the groups formed from drawing a 
	 * similarity threshold on the dendrogram.
	 * The list is an array of Groups. The GroupList methods are for interrogating, 
	 * traversing, adding to and removing from the list.
	 * 
	 * @class GroupList
	 */

	//Define functions for the methods
	/**
	 * Returns the length of the list
	 * 
	 * @method getSize
	 * 
	 * @return {number}
	 */
	function GroupList_getSize(){
		return this.gArray.length;
	}	
	/**
	 * Returns the first group in the list<br /><br />
	 * If the list is not empty set pointer to first item and return the first item
	 * <br />
	 * else null
	 * 
	 * @method getFirst
	 * 
	 * @return {Group object}
	 * 
	 */
	function GroupList_getFirst(){
		if(!this.isEmpty()){
			this.pointer=0;
			return this.gArray[0];
		}
		return null;
	}
	/**
	 * Returns the last group in the list<br /><br />
	 * 
	 * if the list is not empty set pointer to last item and return the last item
	 * <br />
	 * else null
	 * 
	 * @method getLast
	 * 
	 * @return {Group object}
	 */
	function GroupList_getLast(){
		if(!this.isEmpty()){
			this.pointer=this.gArray.length-1;
			return this.gArray[this.gArray.length-1];
		}
		return null;
	}
	/**
	 * Returns the next group in the list<br /><br />
	 * 
	 * If pointer is less then the list length-1 
	 * then increments pointer and returns the item
	 * else returns null
	 * 
	 * @method getNext
	 * 
	 * @return {Group object}
	 */
	function GroupList_getNext(){
		if (this.pointer<this.gArray.length-1){
			this.pointer++;
			return this.gArray[this.pointer];
		}
		return null;
	}
	/**
	 * Returns the currently pointed to group in the list<br /><br />
	 * 
	 * @method getCurrent
	 * 
	 * @return {Group object}
	 */
	function GroupList_getCurrent(){ 
		return this.gArray[this.pointer];
	}	
	/**
	 * Removes a group from the list
	 * 
	 * @method removeFromEnd
	 * 
	 * @return {Group object} the popped group
	 */
	function GroupList_removeFromEnd(){ 
		return this.gArray.pop();
	}
	/**
	 * Adds a group to the list
	 * 
	 * @method addToEnd
	 * 
	 * @param groupIn {Group object}the group to be added
	 */
	function GroupList_addToEnd(groupIn){ 
		this.gArray.push(groupIn);
	}
	/**
	 * Returns true if ther are no items in the list
	 * 
	 * @method isEmpty
	 * 
	 * @return {boolean} 
	 */
	function GroupList_isEmpty(){ 
		if (this.gArray.length==0){
			return true;
		}
		return false;
	}
	/**
	 * Resets the group list to be empty and undraws the bands
	 * 
	 * @method reset
	 * 
	 * @return {boolean} but value not used.
	 */
	function GroupList_reset(){ 
		while (!this.isEmpty()){
			//empty the array and erase from canvas
			this.removeFromEnd().band.remove();
		}
		this.pointer=-1;
		this.drawn=false;
		return true;//has no effect
	}	
	/**
	 * Resets the group list to be empty but leaves the canvas alone.
	 * Used if no bands have been drawn
	 * 
	 * @method resetButLeaveCanvasAlone
	 * 
	 * @return {boolean} but value not used.
	 */
	function GroupList_resetButLeaveCanvasAlone(){ 
		while (!this.isEmpty()){
			//empty the array
			this.removeFromEnd();
		}
		this.pointer=-1;
		return true;//has no effect
	}	
	/**
	 * @constructor GroupList
	 * 
	 * @param thresholdIn {number} the mergeHt set to define the groups
	 */	    
	function GroupList(thresholdIn){
		//initialise attributes 
		this.gArray= new Array();//to hold the Groups
		this.pointer=-1;//index of current group
		this.threshold=thresholdIn;
		this.drawn=false;//records whether or not the group list has been drawn
						// onto the graph

		//associate methods with defined functions
		this.isEmpty=GroupList_isEmpty;
		this.addToEnd=GroupList_addToEnd;
		this.removeFromEnd=GroupList_removeFromEnd;
		this.getCurrent=GroupList_getCurrent;
		this.getNext=GroupList_getNext;
		this.getSize=GroupList_getSize;
		this.getFirst=GroupList_getFirst;
		this.getLast=GroupList_getLast;
		this.reset=GroupList_reset;
		this.resetButLeaveCanvasAlone=GroupList_resetButLeaveCanvasAlone;		
	} 

	//End of GroupList class
	//######################################################

	//######################################################
	//Group class
	/**
	 * A group contains a list of leaf nodes (such leaf nodes sharing a 
	 * common ancestor). Groups are collected in a GroupList. <br /><br />
	 * 
	 * Important attributes:<br />
	 * A group has attributes topBound and bottomBound which are the yPos of the
	 * topmost (nearest the origin) and bottommost (furthest from the origin) 
	 * leaves in the group. These are used in drawing the group bands.
	 * 
	 * @class Group
	 */

	//Define functions for the methods
	/**
	 * Returns the first item. (A group will never be empty)
	 * 
	 * @method getFirst
	 * 
	 * @return {ClusterNode object} a leaf node
	 */
	function Group_getFirst(){
		return this.leafList[0];
	}
	/**
	 * Return the last item. (A group will never be empty)
	 * 
	 * @method getLast
	 * 
	 * @return {ClusterNode object} a leaf node
	 */
	function Group_getLast(){
		return this.leafList[this.leafList.length-1];
	}

	//Constructor function
	/**
	 * @constructor Group
	 * 
	 * @param leafListIn {Array of ClusterNode objects} an array of leaf nodes
	 * @param ancestorNodeIn {ClusterNode} the node which is the ancestor of all 
	 * the other nodes in the list
	 */   
	function Group(leafListIn, ancestorNodeIn){
		//initialise attributes

		this.leafList=leafListIn;
		this.ancestorNode=ancestorNodeIn;
		//this.topBound=this.getFirst().yPos;
		this.topBound=leafListIn[0].yPos;
		//this.bottomBound=this.getLast().yPos;
		this.bottomBound=leafListIn[leafListIn.length-1].yPos;
		this.band=null; //a drawn object to be assigned later by the dendrogram

		//associate methods
		this.getFirst=Group_getFirst;
		this.getLast=Group_getLast;	
	} 

	//End of Group class
	//######################################################

	//######################################################
	//Gui class
	/**
	 * The Gui class.
	 * A reference to the instance of this class can be passed to any class 
	 * that needs to interact with the user or output onto the page.<br /><br />
	 * It has methods for displaying various dialogs and outputing messages. 
	 * Also I have tried to gather in here any references to the DOM 
	 * page elements such as specific divs or buttons. This is so as 
	 * too isolate the rest of the code from these references. Instead 
	 * the rest of the code accesses the buttons/divs via the 
	 * gui attribute to which they are assigned.<br /><br />
	 * 
	 * Important Gui attributes:<br />
	 * -leafSearchURL - the destination for the search link in the groups dialogs.<br />
	 * -dataDescription - the data description text read in from the .dat file row3
	 * Obtained from the clusterTable passed into the constructor<br />
	 * -mobile - true if a mobile OS is detected. This affects the display 
	 * behaviour of dialogs.
	 * <br /><br />
	 * 
	 * Some tunable attributes affecting dialog display behaviour:<br />
	 * -maxDialogHt<br />
	 * -maxMessageLengthToScroll - Beyond this and dialogs are ht limited 
	 * and scroll.<br /><br />
	 * Attributes referring to names of DOM objects which need accessed by 
	 * the application:
	 * <br />
	 * -groupsTab, thresholdField, groupNoField, canvasContainer, largestMergeHtDiv, 
	 * leafNodesDiv.<br /><br />
	 * 
	 * The constructor: <br />
	 * -Calls this.bindTabEvents() to set up the group tab event.<br />
	 * -Calls the jQuery tabs() method to invoke the tabs effect behaviour on 
	 * 	the page<br />
	 * -Calls the jQuery button() method to invoke the button styling and 
	 * 	behaviour on the page<br />
	 * 
	 * @class Gui 
	 */

	//Define functions for the methods
	/**
	 * Notifies the user the groups have been formed via a dialog
	 * Activates the pulsating Groups tab
	 * 
	 * @method notifyGroupFormation
	 * 
	 * @param message {string} the message to be shown in the dialog
	 * @param groupListIn {GroupList object} the group list to be output
	 */
	function Gui_notifyGroupFormation(message,groupListIn){	
		//pulsate effect for the groups tab
		$('#pulser').addClass('highlight');
		$('#pulser').text(groupListIn.getSize()+" Groups");
		//Make it pulse 30 times. Each cycle is 1 second
		//There is an event set to stop it on tab click
		$('#pulser').effect("pulsate", { times:30 }, 1000);

		//show the groups dialog
		this.showMessage(message,'Groups formed');
	}
	/**
	 * Clears groups from the tab notifyer
	 * 
	 * @method clearGroupNotifer
	 * 
	 */
	function Gui_clearGroupNotifer(){	
		//stop it pulsing
		$('#pulser').stop(true, true);
		//reset the pulser tab text
		$('#pulser').text("Groups");
		//reset the opacity
		 $('#pulser').css('opacity', 1);
	}
	/**
	 * Show the message dialog using the dialog groups div
	 * with an OK button
	 * 
	 * @method showMessage
	 * 
	 * @param message {string} the message to be shown in the dialog
	 * @param title {string} the title of the dialog
	 */
	function Gui_showMessage(message,title){
		var dialogWidth=300;//default width
		//show the message dialog using the dialog groups div
		$( "#dialog-groups" ).html(message);
		//check on length of message and adjust width it it is long
		if (message.length>this.maxMessageLengthToScroll/5){
			dialogWidth=this.maxMessageLengthToScroll/4;
		}
		//if the message is very long then limit the dialog ht so it scrolls
		//but dont do that if it is a mobile device
		if ((message.length/2>this.maxMessageLengthToScroll)&& (!this.mobile)){
			$( "#dialog-groups" ).dialog({
				height: this.maxDialogHt,
				width: dialogWidth,
				modal: true,
				buttons: {
					Ok: function() {
						$(this).dialog( "close" );
					}
				}
			});
		}else{
			$( "#dialog-groups" ).dialog({
				width: dialogWidth,
				modal: true,
				buttons: {
				Ok: function() {
					$(this).dialog( "close" );
				}
			}
			}); 
			}
		$("#dialog-groups").dialog('option', 'title', title);
	}
	/**
	 * Show the message dialog using the dialog groups div
	 * with an OK button
	 * include a hyperlink at the front of the message
	 * 
	 * @method showMessageWithLink 
	 * 
	 * @param message - the message to be shown in the dialog
	 * @param title {string} the title of the dialog
	 * @param urlString {string} the url for the link
	 */
	function Gui_showMessageWithLink(message,title,urlString){
		//assemble the hyperlink html, append the message to it
		//and call the show message method
		var hyperlinkString='<a href="'+this.leafSearchURL+urlString
							+'" target="_blank" '
							+'title="Search using '+ this.leafSearchURL+urlString
							+'" >Search Link</a>';
		this.showMessage(hyperlinkString+".</br> "+message,title);
	}
	/**
	 * Show the value of the threshold adjuster in the gui element for 
	 * displaying that.
	 * 
	 * @method showThreshold
	 * 
	 * @param valIn {number} the value to show
	 */
	function Gui_showThreshold(valIn){
		//format the number
		if (valIn.toPrecision){//if browser supports toPrecision() method 
			valIn=valIn.toPrecision(5);
		}  
		$( this.thresholdField ).val( valIn );
	}	
	/**
	 * Show the given value in the gui element for displaying no of groups.
	 * 
	 * @method showNoOfGroups
	 * 
	 * @param valIn {number} the value to show
	 */
	function Gui_showNoOfGroups(valIn){
		$( this.groupNoField).val( valIn );
	}	
	/**
	 * Show the given values in the gui element for displaying those.
	 * used for showning facts about the current loaded data set
	 * 
	 * @method showDataDisplay
	 * 
	 * @param h {number}  the value to show in largestMergeHtDiv
	 * @param n {number}  the value to show in leafNodesDiv
	 */
	function Gui_showDataDisplay(h,n){
		$( this.largestMergeHtDiv).text( h );
		$( this.leafNodesDiv).text( n );
		$( this.theDataDescriptionDiv).text( this.dataDescription );
	}	
	/**
	 * Get the current value of the threshold
	 * 
	 * @method getThresholdVal
	 * 
	 * @return {number} the value of the threshold
	 */
	function Gui_getThresholdVal(){
		return $( this.thresholdField).val();
	}	
	/**
	 * Attach functions to Gui buttons
	 * 
	 * @method bindButtonFunctions
	 * 
	 * @param dendrogramIn {Dendrogram object} the dendrogram object with 
	 * 		  all its functions
	 * @param guiIn {Gui object } the gui object passed is required in as 
	 * 		   the "this" keyword refernces the event rather diring a click.
	 */
	function Gui_bindButtonFunctions(dendrogramIn,guiIn){
		
		//bind the click event to the button with its handler
		$(function() {
			$('#groupingButton').bind('click', function() {
				dendrogramIn.applyGroupingThreshold($( "#amount" ).val());
			});
			$('#removeGroupsButton').bind('click', function() {
				dendrogramIn.removeGroups();
			});
			$('#truncateButton').bind('click', function() {
				dendrogramIn.drawSummary();
			});
			$('#restoreButton').bind('click', function() {
				dendrogramIn.restore();
			});
			$('#maxButton').bind('click', function() {
				guiIn.showThreshold(dendrogramIn.setThresholdBarToMax());
			});
			$('#zeroButton').bind('click', function() {
				guiIn.showThreshold(dendrogramIn.setThresholdBarToZero());
			});
			$('#lessButton').bind('click', function() {
				guiIn.showThreshold(dendrogramIn.setThresholdBarToLess(20));
			});
			$('#moreButton').bind('click', function() {
				guiIn.showThreshold(dendrogramIn.setThresholdBarToMore(20));
			});
			$('#minusButton').bind('click', function() {
				guiIn.showThreshold(dendrogramIn.setThresholdBarToLess(190));
			});
			$('#plusButton').bind('click', function() {
				guiIn.showThreshold(dendrogramIn.setThresholdBarToMore(200));
			});
			$('#useGroupNoButton').bind('click', function() {
				dendrogramIn.setThresholdForNoOfGroups($( "#groupNo" ).val());
			});
		});
	}
	/**
	 * Attach functions to Gui tab events
	 * 
	 * @method bindTabEvents
	 *
	 */
	function Gui_bindTabEvents(){		
		//bind the click event on groups tab to handling code
		$(function() {
			$('#pulser').bind('click', function() {
				//stop it pulsing
				$('#pulser').stop(true, true);
				//reset the opacity
				 $('#pulser').css('opacity', 1);
			});
		});
	}
	
	//Constructor function
	/**
	 * @constructor Gui 
	 * 
	 * @param clusterTableIn {ClusterTable object} the clusterTable
	 */  
	function Gui(clusterTableIn){

		//initialise attributes
		this.clusterTable=clusterTableIn;
		this.maxDialogHt=$(window).height()*0.75;//dialog is max ht 75% of window
		this.maxMessageLengthToScroll=2000;//beyond this and dialogs are ht limited
		//set default leafSearchURL
		this.leafSearchURL="http://www.google.co.uk/search?q=";
		//set leafSearchURL using input data
		if (this.clusterTable.getSearchLink()!=null){
			this.leafSearchURL=this.clusterTable.getSearchLink();
		}
		//set dataDescription using input data
		if (this.clusterTable.getDescription()!=null){
			this.dataDescription=this.clusterTable.getDescription();
		}
		//detect mobile device
		// Looks for the orientation property. 
		// In a mobile device such as ipad/android it is defined
		if (typeof orientation != 'undefined'){
			this.mobile = true;
		}else{
			this.mobile = false;
		}
		$( "#traceDump" ).append("gui.mobile="+this.mobile+"</br>");
		
		//names of DOM objects which need accessed by the application
		this.groupsTab='#groupsOutput';
		this.thresholdField='#amount';
		this.groupNoField='#groupNo';
		//used in a getElementById call, so no #
		this.canvasContainer='canvas_container';
		this.largestMergeHtDiv='#largestMergeHt';
		this.leafNodesDiv='#leafNodes';
		this.theDataDescriptionDiv='#theDataDescription';

		//associate methods
		this.notifyGroupFormation = Gui_notifyGroupFormation;
		this.showMessage = Gui_showMessage;
		this.showMessageWithLink = Gui_showMessageWithLink;
		this.showThreshold = Gui_showThreshold;
		this.bindButtonFunctions = Gui_bindButtonFunctions;
		this.bindTabEvents=Gui_bindTabEvents;
		this.showNoOfGroups = Gui_showNoOfGroups;
		this.clearGroupNotifer = Gui_clearGroupNotifer;
		this.getThresholdVal = Gui_getThresholdVal;
		this.showDataDisplay = Gui_showDataDisplay;			

		//processing to be done by Constructor
		
		this.bindTabEvents();//set up the group tab event

		//####################################################################
		//jQuery UI elements
		//####################################################################

		//####################################################################
		//jQuery tabs Widget			
		$(function() {
			$( "#tabs" ).tabs();
		});
		//END OF jQuery tabs Widget
		//####################################################################

		//####################################################################
		//jQuery buttons
		
		//apply button styling and interaction to the Toolbar buttons.
		//function is bound at global level after creation of the dendrogram
		
		$(function() {
			$( "#zeroButton" ).button({
				text: false,
				icons: {
					primary: "ui-icon-seek-first"
				}
			});
			$( "#lessButton" ).button({
				text: false,
				icons: {
					primary: "ui-icon-seek-prev"
				}
			});
			$( "#minusButton" ).button({
				text: false,
				icons: {
					primary: "ui-icon-minus"
				}
			});
			$( "#plusButton" ).button({
				text: false,
				icons: {
					primary: "ui-icon-plus"
				}			
			});
			$( "#moreButton" ).button({
				text: false,
				icons: {
					primary: "ui-icon-seek-next"
				}
			});
			$( "#maxButton" ).button({
				text: false,
				icons: {
					primary: "ui-icon-seek-end"
				}
			});

			$( "#groupingButton" ).button();
			$( "#removeGroupsButton" ).button();
			$( "#truncateButton" ).button();
			$( "#restoreButton" ).button();
			$( "#useGroupNoButton" ).button();
		});
	
		//END OF jQuery buttons
		//####################################################################
	} 

	//End of Gui class
	//######################################################

});// end of $(document).ready( function() {

The Dendrogrammer © 2011 D.Robb (See Readme for MIT licence). These pages were created with YUIDoc Copyright © 2011 Yahoo! Inc. All rights reserved.