//
////////  Mini-wheel functions	

//
///// wheel functions
//
	function initWheel(containerID) {
	  var pageType = gSelType;
	  
		// traverse the hierachies and determine rough spacing for the rim... 
		// how much space does diseases get? pathways? etc
		var totalSize = 0;
		var startAngle = 105;  // degrees from the x axis to start layout
		var nextAngle;
		for (var i=0; i< hierarchies.length; i++) {
			totalSize += hierarchies[i].size;	
		}
		var htmp = [];
		var minFrac = 20/360;  // at least 10 degrees of arc
		var sum = 0;
		for (i=0; i<hierarchies.length; i++) {
			htmp[i] = Math.max(minFrac,hierarchies[i].size/totalSize);
			sum+= htmp[i];
		}
		for (i=0; i<hierarchies.length; i++) {
			hierarchies[i].size = htmp[i]*100/sum;
		}
		
		// calculate finalSize ... // added after removing the miniwheel. if the miniWheel is added back, then this might break it.
		var finalSize = 0;
		for (var i=0; i< hierarchies.length; i++) {
			finalSize += hierarchies[i].size;	
		}
		
		// need to allocate space on the rim based on the dependancy tree of each topic
		// however need to at least allocate 10 degrees for the interactions hierarchy
		var bgOpt = (pageType == "context") ? "circle" : "shadow";

		wheel = new Wheel(containerID, bgOpt);
    wheel.setSizeConstants(finalSize, hierarchies.length);
		
		if(pageType == "context") {	
			for (var j=0; j<hierarchies.length; j++) {
					nextAngle = wheel.rim.layoutTopic(startAngle, j, hierarchies[j]);
					startAngle = nextAngle;
			}
		}
	}
	
	// 
	///////   Wheel constructor
	//
	
	function Wheel(wheelParent, bgOpt) {
	  var pageType = gSelType;
	  	  
		this.jContainer = x$("#" + wheelParent);
    this.wide =  500; //this.jContainer.width();
    this.hi   = 500; // this.jContainer.height();
		if(pageType == "context") {
      // this.wide = Math.max(this.wide, 435);  // was 710
      // this.hi = this.wide;
      this.jContainer.css({width: this.wide + "px"});
      this.jContainer.css({height: this.hi + "px"});
			this.centerRad = Math.min(this.hi, this.wide)/2-60;    // was 157.5
			this.rimThickness = 2.5;  // was 62.5
		} else {
			this.centerRad = 50;
			this.rimThickness = 1;
		} 		
		
		this.canvas = Raphael(document.getElementById(wheelParent),500, 500);
		
    this.left = 100; //this.jContainer.offset().left;
    this.top = 100; //this.jContainer.offset().top;
    this.centerPoint = {x:this.left + this.wide/2, y:this.top + this.hi/2};  // in screen coordinates - for tooltips and rim overlay		
		
		this.translation = {x:this.wide/2, y:this.hi/2};  // the center point of the wheel
		this.label = {};
		this.tip = {};
		this.totalCount = 0;
		this.bgOpt = bgOpt;
		
		this.center = new WheelCenter(this, this.canvas, this.centerRad, this.translation, this.bgOpt);		
		this.rim =    new Rim(this, this.canvas, this.centerRad, this.translation, this.rimThickness);
		this.rim.setTitle();

		if(pageType == "context") {
			this.overlay = new Overlay();
			this.overlay.resize();
			this.overlay.xOff = this.centerPoint.x + 5;  // these aren't set in overlay.resize() since wheel isn't fully defined yet
			this.overlay.yOff = this.centerPoint.y + 5;  // set them here
		}else {
			this.buttonSet = this.canvas.set();
			var butBG = this.canvas.rect(2,2,36,18,8).attr("fill", "#000000").attr("opacity", 0.4)
			this.revertBG = this.canvas.path("M8.5,16.5H18v-16H8.5c-4.418,0-8,3.582-8,8 S4.082,16.5,8.5,16.5z")
			this.revertBG.attr("fill", "#DDE3DD").attr("stroke", "#FFFFFF");
			this.prevBG = this.canvas.path("M28.5,0.5H18v16h10.5c4.418,0,8-3.582,8-8 S32.918,0.5,28.5,0.5z")
			this.prevBG.attr("fill", "#DDE3DD").attr("stroke", "#FFFFFF");
			this.revertFG = this.canvas.path("M5,3.5v9h2v-3.5L14,12.5v-9L7,6.5v-3.5z")
			this.revertFG.attr("fill", "#333333").attr("stroke", "none");
			var btnClr = (contextMgr.history.length >1)? "#3333333": "#C0C0C0";
			this.revertFG.attr("fill",btnClr).attr("stroke", "none");
			this.prevFG = this.canvas.path("M20.5,7.9L29, 2.9L29,12.9z")
			this.prevFG.attr("fill", btnClr).attr("stroke", "none");
			
			this.revertBG.attr("title", "Click to restore context to full, original set");
			this.revertFG.attr("title", "Click to restore context to full, original set");
			this.prevBG.attr("title", "Click to undo last context change");
			this.prevBG.attr("title", "Click to undo last context change");
			this.prevFG.click(function() {contextMgr.previous()});	
			this.prevBG.click(function() {contextMgr.previous()});
			this.revertFG.click(function() {contextMgr.restore(0)});
			this.revertBG.click(function() {contextMgr.restore(0)});

			this.prevFG.hover(function() {

				// over
				if(contextMgr.history.length >1) {
					wheel.prevBG.attr("fill", hiliteColor);
				} else {
					wheel.prevFG.attr("fill","#C0C0C0"); 
				}
			}, function() {  // out
				if(contextMgr.history.length >1) {
					wheel.prevBG.attr("fill", "#DDE3DD");
					wheel.prevFG.attr("fill","#333333");
				} else {
					wheel.prevBG.attr("fill", "#DDE3DD");
					wheel.prevFG.attr("fill","#C0C0C0");
				}				
			});
			
			this.revertFG.hover(function() {
				if(contextMgr.history.length >1) {
					wheel.revertBG.attr("fill", hiliteColor);
				} else {
					wheel.revertFG.attr("fill","#C0C0C0"); 
				}
			}, function() {
				if(contextMgr.history.length >1) {
					wheel.revertBG.attr("fill", "#DDE3DD");
					wheel.revertFG.attr("fill","#333333");
				} else {
					wheel.revertBG.attr("fill", "#DDE3DD");
					wheel.revertFG.attr("fill","#C0C0C0");
				}
			});

			this.prevBG.hover(function() {
				if(contextMgr.history.length >1) {
					wheel.prevBG.attr("fill", hiliteColor);
				} else {
					wheel.prevFG.attr("fill","#C0C0C0"); 
				}
			}, function() {
				if(contextMgr.history.length >1) {
					wheel.prevBG.attr("fill", "#DDE3DD");
					wheel.prevFG.attr("fill","#333333");
				} else {
					wheel.prevBG.attr("fill", "#DDE3DD");
					wheel.prevFG.attr("fill","#C0C0C0");
				}
			});
			
			this.revertBG.hover(function() {
				if(contextMgr.history.length >1) {
					wheel.revertBG.attr("fill", hiliteColor);
				} else {
					wheel.revertFG.attr("fill","#C0C0C0"); 
				}
			}, function() {
				if(contextMgr.history.length >1) {
					wheel.revertBG.attr("fill", "#DDE3DD");
					wheel.revertFG.attr("fill","#333333");
				} else {
					wheel.revertBG.attr("fill", "#DDE3DD");
					wheel.revertFG.attr("fill","#C0C0C0");
				}
			});

			this.buttonSet.push(butBG, this.revertBG, this.prevBG, this.revertFG, this.prevFG);
			this.buttonSet.translate(this.wide-42, this.hi-21);
		}

		this.setContextBtns = function() {  // call when context changes
			if(pageType == "context") return;
			if(contextMgr.history.length >1) {
				this.revertFG.attr("fill", "#333333");
				this.prevFG.attr("fill", "#333333");
			} else {
				this.revertFG.attr("fill", "#C0C0C0");
				this.prevFG.attr("fill", "#C0C0C0");
			}
		}
		
		this.resize = function(w, h) {
			this.wide = w;
			this.hi   = h;
			
			// shouldn't have to recompute the rim angles nor the gene center layout (without scaling)...
			// need to optimize this.....
			if(pageType == "context") {
				this.wide = this.wide
				this.hi = this.hi;

				this.centerRad = Math.min(this.hi, this.wide)/2-60;
				this.rimThickness = 2.5;  // was 62.5
				this.clear();
				this.canvas.setSize(20,20);
				this.jContainer.width(this.wide);
				this.jContainer.height(this.hi);
				this.canvas.setSize(this.wide, this.hi);
				this.left = this.jContainer.offset().left;
				this.top = this.jContainer.offset().top;
				this.centerPoint = {x:this.left + this.wide/2, y:this.top + this.hi/2};  // in screen coordinates - for tooltips
				// now redo it
				this.translation = {x:this.wide/2, y:this.hi/2};  // the center point of the wheel
				
				// first the center
				this.center.radius = this.centerRad;
				this.center.trans = this.translation;
				this.center.init();	
				this.center.layout();  // shouldn't have to layout just for a resize...
				// now the rim
				this.rim.innerRadius = this.centerRad;
				this.rim.trans = this.translation;
				this.rim.rimThickness = this.rimThickness;
				this.rim.init();
				var startAngle = 105;
				var nextAngle;				
				for (var j=0; j<hierarchies.length; j++) {
						nextAngle = this.rim.layoutTopic(startAngle, j, hierarchies[j]);  // shouldn't have to layout just for a resize
						startAngle = nextAngle;
				}
				this.rim.setTitle();
				
				if(selectionMgr.o) {
					var cursel = selectionMgr.o;
					var otopic= cursel.getTopic(); 
					if(otopic != "nondatasetconcept" && otopic.indexOf("gene")<0)  {
						// not a out-of-dataset concept and not a gene and not an adhoc list of genes
						var hnodes = [];
						if(cursel.type == "category" || cursel.topic == "interaction") {
							hnodes.push(cursel.getRimObject());   // theres only one
						} else {   // its a topic
							for (var j=0; j<cursel.hierarchyNodes.length; j++) {
								hnodes.push(cursel.hierarchyNodes[j].getRimObject());
							}
						}
						this.rim.showSelectionWedges(hnodes);
					}
				}
				if(this.overlay) this.overlay.resize();
				
			} else {
				this.centerRad = 50;
				this.rimThickness = 1;
			}
		};
		
		this.getGeneScreenLoc = function(index) {
			var obj = genes.list[index].graphics.wheel;  // the raphael object
			var box = obj.obj.getBBox();
			// make a copy to avoid IE errors
			var newBox = {};
			var b;
			for (b in box) {
				newBox[b] = box[b];
			}
			var l = this.jContainer.offset().left + 5;  
			var t= this.jContainer.offset().top + 5;
			newBox.x += l;
			newBox.y += t;
			return newBox;
		};
		
		this.showChildLabels = function(o) {
			this.overlay.showChildLabels(o);
		};
		//  ............. start on the circle packing .............
	
		this.setSizeConstants = function(total, numTopics) {
			this.totalCount = total;
			// 30 is the opening at the top left for the text
			this.rim.degPerUnit = (360-30-this.rim.spacing[0]*numTopics)/total;
		};
		
		this.clear = function() {
			this.center.cleanUp();
			this.rim.cleanUp();
			this.canvas.clear();
		};
	}
	
	function WheelCenter(wheel, c, centerRad, trans, bgOpt) {

		this.bgSet = c.set();
		this.circleSet = c.set();
		this.containerSet = c.set();  
		this.wheel = wheel;
		this.radius = centerRad;
		this.trans = trans;
		this.padding = 1;  // 1 pixel between circles
		this.orgByTopic = "";
		this.c = c;
		this.bgOpt = bgOpt;
		this.curHiliteList = [];
		
		this.cleanUp = function() {
			this.containerSet.remove();
			this.containerSet = this.wheel.canvas.set();
			this.circleSet.remove();
			this.circleSet = this.wheel.canvas.set();
			for (var i=0; i<genes.list.length; i++) {
				genes.list[i].graphics.wheel = null;
			}
		}
		
		this.init = function() {
			if(this.bgOpt == "shadow") {
				var shadow = c.circle(0,0, this.radius);
				shadow.attr({stroke: "none", fill: "#000", translation: "3,3", opacity: "0.3"});
				shadow.blur(2);
				this.bgSet.push(shadow);
			}
			if(this.bgOpt == "circle") {
				var bgRad = Math.min(this.wheel.wide/2, this.wheel.hi/2)
				shadow = c.circle(0,0, bgRad);
				var bgGrad = "r(0.5,0.5)#FFFFFF-#FFFFFF:" + Math.floor(bgRad+this.wheel.rimThickness);
				bgGrad+= "-#000000:" + bgRad;
				shadow.attr("fill", "#FFFFFF").attr("stroke", "#DDDDDD");
				this.bgSet.push(shadow);
			}
			if(this.bgOpt == "oval") {
				shadow = c.ellipse(0,0, this.wheel.wide/2, this.wheel.hi/2);
				shadow.attr("fill", "#FFFFFF").attr("stroke", "#DDDDDD");
				this.bgSet.push(shadow);
			}
			shadow.click(function(e) {
				// hide the tip if any and delselect all genes
        // if(bioTip.isPostState) {
        //  bioTip.forceHide();
        // } else {
         selectionMgr.set(null, []);
        //  bioTip.forceHide(); 
        // }
			});
			var cGrad = "r(0.5,0.5)#FFFFFF-#FDFDFD:" + Math.floor(this.radius*0.44);
			cGrad+= "-#DADADA:" + Math.floor(this.radius*0.81); 
			cGrad+= "-#C4C4C4:" + Math.floor(this.radius*0.88);
			cGrad+= "-#AAAAAA:" + Math.floor(this.radius*0.92); 
			cGrad+= "-#DADADA:" + Math.floor(this.radius*0.95) + "-#FFFFFF:" + this.radius;

			if(gSelType != "context") cGrad = "#DDE3DD";
			var o = c.circle(0,0, this.radius).attr("stroke-width","0").attr("fill",cGrad);
			o.click(function(e) {
				// hide the tip if any and delselect all genes
				if(bioTip.isPostState) {
					bioTip.forceHide();
				} else {
					selectionMgr.set(null, []);
					bioTip.forceHide();	
				}
			});
			this.bgSet.push(o);
			this.bgSet.translate(this.trans.x, this.trans.y);
		};
		
		this.init();
			
		this.layout = function() {
				// loop through all the orgBy categories and process them one by one ..
				// not yet implemented here... for now process all genes
				var sTime = new Date();
				var groups = [];
				var ind;
				//$(window).css("cursor", "wait");   // show wait cursor during layout ... could take a while
				this.cleanUp();
				
				if(pageHeaders["context"].viewState.orgBy == "gene proFamily") {
					for (var i = 0; i< functionGroups.list.length; i++) {
						var tg = functionGroups.list[i].getGenesInContext();
						if(tg.length > 0) {
							groups.push({o:functionGroups.list[i], list:tg, 
							name:functionGroups.list[i].getName(),
							type:functionGroups.list[i].getType(), bbox:null, circles:[]})
						}
					}
					this.orgByTopic = "by Molecular Function"
				}
				if(pageHeaders["context"].viewState.orgBy == "gene location") {
					for (i = 0; i< locationGroups.list.length; i++) {
						tg = locationGroups.list[i].getGenesInContext();
						if(tg.length > 0) {
							groups.push({o:locationGroups.list[i], list:tg, 
							name:locationGroups.list[i].getName(),
							type:locationGroups.list[i].getType(), bbox:null, circles:[]});
						}
					}
					this.orgByTopic = "by Cellular Location"
				}
				if(pageHeaders["context"].viewState.orgBy == "none none") {
					tg = genes.listInContext();
					if(tg.length > 0) groups[0] = {list:genes.listInContext(), name:"", type:"none", bbox:null, circles:[]};
					this.orgByTopic = "";
				}
				var containers = [];
				for (var m=0; m<groups.length; m++) {				
					var glist = organizeCircles(groups[m].list, pageHeaders["context"].viewState.sizeBy, pageHeaders["context"].viewState.colorBy, true);
					groups[m].circles = glist;  // this has the newly computed locations for all genes in the group
					var grpRad = glist.getRadius();
					if (glist.children.length < 2) grpRad=grpRad*1.5;
					containers.push({radius:grpRad, children:glist, name:groups[m].name, groupIndex:m, o:groups[m].o});
				}
				// now create a group of the parents (i.e. groups[]) and lay them out
				// the "false" in the organizeCircles call tells it not to map color etc ... only sort and layout
				var parList = organizeCircles(containers, pageHeaders["context"].viewState.sizeBy, pageHeaders["context"].viewState.colorBy, false);
				// parList has the locations of the circles... need to scale and position this
				// then position and scale all children of the containers
				
				var scaleFactor = this.radius/parList.getRadius();  // this is used for everything
				this.parList = parList;
				this.scaleFactor = scaleFactor;
				parList.scale(scaleFactor);							// now the containers are properly scaled
				var parCenter = parList.getCenter();
				
				//parList.setCenter(this.radius, this.radius)
				for (i=0; i<parList.children.length;i++) {
					var tmp = parList.children[i];
					tmp.cx = this.trans.x - parCenter.cx + tmp.cx;
				    tmp.cy = this.trans.y - parCenter.cy + tmp.cy ;
					tmp.color = "#EAEAEA"
					containers[tmp.geneIndex].children.center = {cx:tmp.cx, cy:tmp.cy};
					var gcircle = new GeneCircle(tmp, containers[tmp.geneIndex].o,  "wheelGroup" )
					
					this.containerSet.push(gcircle.obj)
				}
				
				for (i=0; i<containers.length; i++) {
					var grp = containers[i].children;
					grp.scale(scaleFactor);
					var cCenter = grp.getCenter();
					for (j=0; j<grp.children.length; j++) {
						var gx = grp.children[j].cx;  // the location computed from the individual group layout
						var gy = grp.children[j].cy;
						var gRad = grp.children[j].radius;			
						var gCol = grp.children[j].color;
						grp.children[j].cx +=  grp.center.cx;
						grp.children[j].cy +=  grp.center.cy;
						var geneIndex = grp.children[j].geneIndex;
						var gcircle = new GeneCircle(grp.children[j], genes.list[geneIndex],  "wheelGene" );
						genes.list[geneIndex].graphics.wheel = gcircle;
						this.circleSet.push(gcircle.obj);
						
					}
				}
				// 
				// now set the selection
				//
				
				var selList = selectionMgr.get().geneList;
				for (i=0; i<selList.length; i++) {
					if(genes.list[selList[i]].graphics.wheel) genes.list[selList[i]].graphics.wheel.select();
				}
				
				// labels for the groups
				//
				if(this.calloutSet) {
					this.calloutSet.stop();
					this.calloutSet.remove();
				}
				if(gSelType == "context") {  //only on context page
					var calloutTextAttrs = {"font-size":"10pt", "fill":"#FFFFFF", "text-anchor":"middle"};
					var calloutBgAttrs   = {"fill":"#333333", "stroke":"none", "opacity":"0.9"};
					this.calloutSet = this.c.set();
					for (i=0; i<containers.length; i++) {
						var gName = containers[i].name.replace(/\s/g, "\n");
						var gLoc = containers[i].children.center;  // cx, cy
						var gbox = containers[i].children.center;  // cx, cy
						var gRad = containers[i].radius;  // is this right??  
						//gbox.cx+= this.trans.x;
						//gbox.cy+= this.trans.y;
						var gCalloutText = this.c.text(gbox.cx, gbox.cy, gName).attr(calloutTextAttrs);
						var tbox = gCalloutText.getBBox();
						// if tbox is much bigger than gbox .... then don't show it???  ... dinky groups
						var gCalloutBg = this.c.rect(tbox.x-6, tbox.y, tbox.width+12, tbox.height, 6).attr(calloutBgAttrs);
						this.calloutSet.push(gCalloutBg);
						this.calloutSet.push(gCalloutText);
						gCalloutText.toFront();
					}
					// now wait for 5 seconds then fade them off in 2 seconds.
					// could be tricky... if the wheel is relayedout before the animation is called
					// then the object is already gone.
					
					var animAttr = [];
					animAttr["0%"] = {opacity:1};
					animAttr["70%"] = {opacity:1};
					animAttr["100%"] = {opacity:0, callback:function() 
							{if(wheel.center.calloutSet) {
								wheel.center.calloutSet.hide();
								wheel.center.calloutSet=null;
							}}};
					//this.calloutSet.animate(animAttr, 3000);
					this.calloutSet.animate({opacity:0}, 7000, "<",
							function() 
							{if(wheel.center.calloutSet) {
								wheel.center.calloutSet.remove();
								wheel.center.calloutSet=null;
							}});
				}
				wheel.rim.setTitle();
			};
			
			this.hiliteList = function(indices) {  
				// hilite all the genes in the list - l=list of indices
				if(this.curHiliteList.length !=0) this.dehiliteList();
				for (var i=0;i<indices.length;i++) {
					var gc = genes.list[indices[i]].graphics.wheel;
					if(gc) {
						gc.hilite();
						this.curHiliteList.push(gc);
					}
				}
			};
			
			this.dehiliteList = function() {
				for (var i=0; i< this.curHiliteList.length; i++) {
					var g = this.curHiliteList[i];  // geneCircle objects
					g.dehilite();
				};
				this.curHiliteList = [];
			};
			
			
	}
	
	function GeneCircle(gircle, o,  type) {  // geneIndex <0 for containers
		var rad = Math.max(1, gircle.radius-wheel.center.padding);
		this.obj = wheel.canvas.circle(gircle.cx, gircle.cy, rad);
		this.obj.attr("fill", gircle.color);
		if(type == "container") {
			this.obj.attr("stroke", "#666666").attr("stoke-width", 1);
		} else {
			this.obj.attr("stroke", "none");
		}
		if(o != null) {  // == null is a group containing all genes... doesn't have events
			this.obj.name = o.getName();
			this.obj.type = type;
			this.obj.o = o;
			if(gSelType == "context") {
				this.obj.hover(function(e) {
          // if(!bioTip.isMenuPosted) {  // do nothing if bioTip menu options are glued to screen... modal
           if(this.o.graphics) {
             this.o.graphics.wheel.hilite();   
           } else {
             this.attr("stroke", "#FF0000").attr("stroke-width", 2);
           }
          //  bioTip.start(e, this.node, this.o, this.type);
          // }
				}, function(e) {
					if(this.o && this.o.graphics) {  // its a gene circle not a group circle (which don't hilite)
							this.o.graphics.wheel.dehilite();
					} else {
						this.attr("stroke", "none");
					}
          // bioTip.hideTip(e);
				});
			}
		}
		this.obj.click(function(e) {   // allow clicking on non-context pages 
			var logTopic = 'none';
			var logName = 'none';
			if (this.o) {
				logTopic = this.o.getTopic();
				
				if (logTopic != 'genes') {
					logName = this.o.name;
				} else {
					logName = 'gene';
				}
			}

			if(gSelType == "context") selectionMgr.set(this.o);
		});
		this.isSelected = false;
		this.isHilited = false;
		
		this.select = function() {  // 'this' is the circleGroup object, not raphael object
			this.isSelected = true;
			if(gSelType == "context") {
				this.obj.attr({"stroke-width": 1, "stroke": "#000000"});
			} else {
				this.obj.attr("fill", "#000000").attr("stroke", "none");  // black node, no stroke on non-context pages
			}
		};
		this.deselect = function() {
			this.isSelected = false;
			if(this.obj.o) {
				if(this.obj.o.graphics)  {
					if(this.isHilited && gSelType == "context") {
						this.obj.attr("stroke-width", 3);
						this.obj.attr("stroke", "#F00");
					} else {
						this.obj.attr("fill", gircle.color).attr("stroke", "none");
					}
				}
			}
		};
		
		this.hilite = function() {
			if(this.obj.o.graphics) {
				this.obj.attr("stroke-width", 3);
				this.obj.attr("stroke", "#F00");
				this.isHilited = true;
			}
		};
		this.dehilite = function() {
			if(this.obj.o && this.obj.o.graphics) {
				if(this.isSelected) {
					this.obj.attr("stroke-width", 1);
					this.obj.attr("stroke", "#000000")
				} else {
					if(this.obj.type == "container") {
						//this.obj.attr("stroke-width", 1);
					} else {
						//this.obj.attr("stroke-width", 0);
						this.obj.attr("stroke", "none");
					}
				}
				//if (! Modernizr.svg) wheel.center.layout();
				this.isHilited = false;
			}
		}
	}
	
	function Rim(wheel, c, centerRad, trans, rimThickness) {
	//	this.topicColor = ["#00BDAF", "#FC6AAB", "#43A6E6", "#91CF25", "#E36FDF", "#FBB03B"];
		this.wheel = wheel;
		this.innerRadius = centerRad;
		this.trans = trans;
		this.rimThickness  = rimThickness;
		this.spacing = [2,1,0.5,0.5,0.5,2];  // the spacing between topics = [0], between categories [1] -to- [5]
		this.lineWidth = [5,4,4,4,4,4];
		this.linecap = ["round", "butt", "butt","butt","butt","round"];
		this.minChipSize = 20;  // need at least 20 pixels to subdivide
		this.rSet = c.set();
		this.calloutBand = {bg:null, radius:centerRad+rimThickness, x:this.trans.x, y:this.trans.y};  
		this.levelBands = [];
		this.wedges = {hoverBased:[], selectionBased:[]};
		this.maxWedgeCount = 10;  // don't expect to see a topic in more than 10 locations in the hierarchy
		this.title = null;
		this.orgByTitle = null;
		this.c = c;  // the canvas object
		
		this.init = function() {
			var delRad = (gSelType == "context")? 6 : 0;
			var startRad = (gSelType == "context")? 22.5 : 0;
			var rad = this.innerRadius+startRad;
			for (var i=0; i<6;i++) {  // these are the concentric gray circles behind the rim objects
				if(i == 5) {
					rad = this.levelBands[4].radius + 2*delRad;   // centerRad + rimThickness;
					o = null;
				} else {
					if(gSelType == "context") {
						o = this.c.circle(0,0,rad);
						o.attr("stroke-width","1").attr("stroke","#CCCCCC");
						o.translate(this.trans.x, this.trans.y);
						this.rSet.push(o);
					} else {
						o = null;
					}
				}
				this.levelBands[i]= {bg:o, radius:rad};
				rad+= delRad;
			}
			this.calloutBand.radius = this.levelBands[5].radius;
			this.wedges.selectionBased = [];
			for (i=0;i<this.maxWedgeCount; i++) {
				var sObj = this.c.path().attr({"stroke":"none", "fill":"#666666"}).hide();
				var line = this.c.path().attr({"stroke":"#666666", "fill":"none"}).hide();				
				this.wedges.selectionBased.push({obj:sObj, line:line, topic:null});	
				this.rSet.push(line);
				this.rSet.push(sObj);
			}
			// selection are below hover
			this.wedges.hoverBased = [];
			for (i=0;i<this.maxWedgeCount; i++) {
				var hObj = this.c.path().attr({"stroke":"none", "fill":"#FF0000"}).hide();
				var line = this.c.path().attr({"stroke":"#FF0000", "fill":"none"}).hide();
				this.wedges.hoverBased.push({obj:hObj,line:line,topic:null});
				this.rSet.push(line);
				this.rSet.push(hObj);
			}
			// update the hierarchy nodes - we'll need this for the rim soon.
			for (i=0; i<hierarchies.length; i++) {
				hierarchies[i].setContext(); // when the rim renders it will have updated hierarchies by topic
			}
		};
		
		this.init();

		
		this.setTitle = function() {
			if(this.title != null) this.title.remove();
			if(this.orgByTitle != null) this.orgByTitle.remove();
			var fsize = (gSelType == "context")? 16: 11;
			var geneTxt = genes.numberInContext() + " Genes"
			this.title = c.text(0, -this.levelBands[3].radius, 
				       geneTxt).attr("font-size", fsize).attr("font-family:", "Futura");
			this.title.translate(this.trans.x, this.trans.y);
			this.rSet.push(this.title);
			var orgByTxt = "by Biomarker"; // this needs to be set by the legend ...
			this.orgByTitle = c.text(0, -this.levelBands[3].radius+fsize, 
				             wheel.center.orgByTopic).attr("font-size", (fsize-3)).attr("font-family",
				            "Futura").attr("font-style", "italic")
			this.orgByTitle.translate(this.trans.x, this.trans.y);
			this.rSet.push(this.orgByTitle);
		};
		
		this.showHoverWedges = function(hierNodes) {
			// place wedge(s) pointing to the appropriate topics in the rim.
			
			// there could be more than one... up to this.maxWedgeCount
			for (var i=0; i<Math.min(hierNodes.length, this.maxWedgeCount); i++) {
				var angle = 0.5*(hierNodes[i].angles.end+hierNodes[i].angles.start);					
				var pos = getXYonCircle(angle, this.innerRadius);
				var x = pos.x+this.trans.x;
				var y = pos.y+this.trans.y;
				//var d = "M" + (x-15) + ", " + (y-5) + " l15, -14 l 15, 14 h-5 v6 h-20 v-6z";  // straight arrow
				var d = "M" + (x+9.3) + ", " + (y+0.067) + "c0.775,0,1.714-0.628,1.714-1.403v-0.597c-8,0-10-15-10-15s-2,15-10,15v0.597c0,0.775,0.316,1.403,1.091,1.403";
				this.wedges.hoverBased[i].obj.attr("path", d).rotate(90-angle, x,y).attr("opacity",0).show();	
				// draw a line from the tip to the chip
				var pos2 = getXYonCircle(angle, this.levelBands[hierNodes[i].level].radius);
				var d2 = "M"+x + ","+y + "L"+ (pos2.x+this.trans.x) + ", " + (pos2.y+this.trans.y);
				this.wedges.hoverBased[i].line.attr("path", d2).show();		
				this.wedges.hoverBased[i].obj.animate({"opacity":0.8}, 400);  // hover wedges animate on - since there can be a lot of flashing				
				this.wedges.hoverBased[i].topic = hierNodes[i];	
			}
		}
		this.showSelectionWedges = function(hierNodes) {
			for (var i=0; i<Math.min(hierNodes.length, this.maxWedgeCount); i++) {
				var angle = 0.5*(hierNodes[i].angles.end+hierNodes[i].angles.start);		
				var pos = getXYonCircle(angle, this.innerRadius);
				var x = pos.x+this.trans.x;
				var y = pos.y+this.trans.y;
				//var d = "M" + (x-15) + ", " + (y-5) + " l15, -14 l 15, 14 h-5 v6 h-20 v-6z";
				var d = "M" + (x+9.3) + ", " + (y+0.067) + "c0.775,0,1.714-0.628,1.714-1.403v-0.597c-8,0-10-15-10-15s-2,15-10,15v0.597c0,0.775,0.316,1.403,1.091,1.403";
				
				var pos2 = getXYonCircle(angle, this.levelBands[hierNodes[i].level].radius);
				var d2 = "M" + x + "," + y + "L"+ (pos2.x+this.trans.x) + ", " + (pos2.y+this.trans.y);
				this.wedges.selectionBased[i].line.attr("path", d2).show();

				var txt = selectionMgr.o.name;
				this.wedges.selectionBased[i].obj.attr("path", d).rotate(90-angle, x,y).attr("title", txt).show(); // selection wedges don't animate on
				this.wedges.selectionBased[i].topic = hierNodes[i];
			}
		}
		
		this.hideHoverWedges = function() {
			// hide all
			for (var i=0; i<this.maxWedgeCount; i++) {
				this.wedges.hoverBased[i].obj.stop().hide();
				this.wedges.hoverBased[i].line.hide();
			}
		}
		this.hideSelectionWedges = function() {
			// hide all
			for (var i=0; i<this.maxWedgeCount; i++) {
				this.wedges.selectionBased[i].obj.hide();
				this.wedges.selectionBased[i].line.hide();
			}
		}
		
		this.colorByContext = function() {
			/*
			   set the rim chip color based on whether it is in the context or not  
			*/
			for (var k=0; k< hierarchies.length; k++) {
				for (var j=0; j< hierarchies[k].nodes.length; j++) {
					var n = hierarchies[k].nodes[j];
					var color;
					if(n.rimChip) {
						if(n.inContext) {
							color = n.hierarchyParentObj.getColorForLevel(n.level);
						} else {
							color = "#999999";
						}
						n.rimChip.attr("stroke",color);
					}
				}
			}
		}
		
		this.layoutTopic = function(startAngle, topicIndex, hier) {
			var angle = startAngle;
			var endAngle = angle + this.degPerUnit*hier.size;
			hier.root.angles = {start:angle, end:endAngle};
			// draw a gradated polygon from inner to out .. white on inner, color on outer
			// have to do this as a series of arcs since raphael cant use radial gradients on paths
			//var oset = drawGradatedArc(c,startAngle, endAngle, this.levelBands[5].radius, 0,0,0, hier.baseHSV)
			//this.rSet.push(oset);
			// draw the topic name on an arc. Raphael doesn't support this, so have to do it by hand
      
      //this.rSet.push(drawTextOnArc(c,hier.name, this.innerRadius+14, (endAngle+angle)/2));
			layoutRimChip(hier.root, startAngle, endAngle);
			return endAngle + this.spacing[0];		
		};
		
		this.cleanUp = function() {
			this.rSet.remove();
			// remove references from hierarchy nodes as well?
			for (var j=0; j<hierarchies.length; j++) {
				hierarchies[j].deleteRimReferences();
			}
			
		};
		
		this.hide = function() {
			this.rSet.hide();
		};
		
		this.show = function() {
			this.rSet.show();
		};

		this.hiliteChip = function() {
			var sel = selectionMgr.get();
			var chip;
			if (sel && sel.obj) chip = sel.obj.rimChip;

			if (chip) {
				chip.attr("stroke", "#000000");
				this.curChipHilite = chip;
			} else {
				//chip = sel.obj.getRimObject().rimChip;
			}
			//console.log(chip)
		};

		this.dehiliteChip = function() {
			var chip = this.curChipHilite;
			
			if(chip && !chip.removed) {
				if(chip.hierarchyNode.inContext) {
					chip.attr("stroke", chip.baseColor);
				} else {
					chip.attr("stroke", "#999999");
				}
			}
			//if (chip) chip.attr("stroke", chip.baseColor);
		};
	}
//
//////////////////// Drawing functions
//
	function layoutRimChip(hn,startAngle,endAngle) {  // recursively lay out a topic on the rim
		var minChipSize = 3;  // pixels
		if(hn.isInData == false) return;
		var useShowOnRim = true;
		if(!hn.showOnRim && useShowOnRim) return;
		var level = hn.level;
		var itm;
		var color = "#999999";
		if(hn.inContext) color = hn.hierarchyParentObj.getColorForLevel(level);
		
		/*
		 *  set rimchip color to gray if the dependants have no genes in the context
		 */
		
		var topic = hn.getTopic();
		/*
		if(topic.indexOf("rocess")>=0 || topic.indexOf("isease")>=0 || topic.indexOf("athway")>=0 || topic.indexOf("nteraction")>=0 ) {
			var dList = hn.getChildObjects();
			if(!$.isArray(dList)) dList = [dList];
			var hasGenesInContext = false;
			for (var m=0; m<dList.length; m++) {
				if(dList[m].getGenesInContext().length > 0) {
					hasGenesInContext = true;
					break;
				}
			}
			if(!hasGenesInContext) {
				color = "#999999";
			} 			
		}
		*/

		var smallAngle = 0;
		if(endAngle - startAngle >= 180) smallAngle = 1;
		var chip = drawArc(wheel.canvas,startAngle,endAngle,0,smallAngle,0,color,level);
		hn.angles={start:startAngle, end:endAngle};
		chip.hierarchyNode = hn;
		chip.baseColor = color;

    // var browserVersion = parseFloat(parent.comm.BrowserDetect.version);
    // if (browserType.toLowerCase() == "safari" && browserVersion >= 5.1) {
     // chip.hover(function(event) {hoverOnChip(this, event);});
    // } else {
     chip.hover(function(event) {hoverOnChip(this, event);}, function() {hoverOffChip(this);});
    // }

    chip.hover(function(event) {hoverOnChip(this, event);});		    
		chip.click(function() {clickOnChip(this);});
    // chip.dblclick(function() {dblClickOnChip(this);});
		chip.attr("cursor", "pointer");
		

		wheel.rim.rSet.push(chip);
		hn.rimChip = chip;

		/*  figure out if there's enough space to lay out the next level down... this is a bit 
		complicated by the fact that there might be enough space to layout all the children at the mimimal size
		but some of the children might be too piggish and suck up some space of their siblings...
		need to know this ahead of time....
		four  approaches - 
			1 - don't layout any unless theres enough space for every child (even if they all get the same space)
				2 - try to layout based on the number of dependants
					3 - if that fails then start with the smallest subtree and give it the mimimum space
					stealing some allocation from all the rest... and so forth up to the largest one
					 	4 - if that fails then give each child the same size
		*/ 
		// var children = hn.children;  // the original way...
		var children = [];
		var descendantCounts = [];
		
		for (var i=0; i<hn.children.length; i++) {
			var nc = hn.children[i];
			if(useShowOnRim) {
				if(hn.hierarchyParentObj.nodes[nc].showOnRim) {
					children.push({index:nc, descendantCount:hn.hierarchyParentObj.nodes[nc].getDescendantCount()});
				}
			} else {
				children.push({index:nc, descendantCount:hn.hierarchyParentObj.nodes[nc].getDescendantCount()});
			}	
		}
		var numDescendants = 0;
		for (i=0; i<children.length; i++) {
			numDescendants+= children[i].descendantCount;
		}
		
		var minChip = Math.max(0,children.length*minChipSize + 2*(children.length-1));  // average in pixels
		if(chip.getTotalLength() >= minChip && children.length > 0) {			
			var curRadius = wheel.rim.levelBands[level].radius;
			var minAngle = 180*minChipSize/(curRadius*Math.PI);
			var iFail = false;			
			var totalArc = hn.angles.end - hn.angles.start - (children.length-1)*wheel.rim.spacing[level+1];  // total arc length for this parent			
			var remainder = totalArc;
			var remainingDescendants = numDescendants;
			var unallocated = [];  
			var allocated = [];
			for (var i=0; i<children.length; i++) {
				unallocated[i] = {node:hn.hierarchyParentObj.nodes[children[i].index], descendantCount:children[i].descendantCount};  // the child hierarchy node
			}
			unallocated.sort(function(a,b) {
				return b.descendantCount - a.descendantCount;  // in descreasing order .. used for inverse for loop to come next
			});
			while(unallocated.length > 0 && !iFail) {
				var idx = unallocated.length-1;

				var delA = remainder*unallocated[idx].descendantCount/remainingDescendants; // this child's share in arc
				if(delA < minAngle) delA = minAngle;
				remainder = remainder - delA;
				remainingDescendants = remainingDescendants - unallocated[idx].descendantCount;
					// divide by zero issues?
				if(remainder < minAngle && idx > 0) {
						iFail = true;
				}
				allocated.push({node:unallocated[idx].node, delAngle:delA});
				unallocated.splice(idx,1);   // remove this one
			}
			if(iFail) {
				// allocate evenly ... proportional allocation failed...
				allocated = [];
				var ds = totalArc/children.length;
				for (i=0;i<children.length; i++) {
					allocated.push({node:hn.hierarchyParentObj.nodes[children[i].index], delAngle:ds})
				}
			}
			var sa = startAngle;
			var ea;
			for (i=0; i<children.length; i++) {
				var cnode = allocated[i].node;
				ea = sa + allocated[i].delAngle;
				layoutRimChip(cnode, sa,ea);
				sa = ea + wheel.rim.spacing[level+1];
			}
		}
	}
	
	// hover and click functions for rim chips ... also called by the grandTour
	function hoverOnChip(chip, event) {
    // if(!bioTip.isMenuPosted) {
			// compensate for Safari bug
      // var browserVersion = parseFloat(parent.comm.BrowserDetect.version);
      // if (browserType.toLowerCase() == "safari" && browserVersion >= 5.1 && ! tourOptions.isOnTour) {
				wheel.hoverX = event.pageX;
				wheel.hoverY = event.pageY;
				x$("#overlay").click(function(){clickOnChip(chip);});
				x$("#overlay").on("mousemove", function(e){
					if (Math.abs(e.pageX - wheel.hoverX) > 5 || Math.abs(e.pageY - wheel.hoverY) > 5) {
						wheel.overlay.hide();
						hoverOffChip(chip);
						$("#overlay").unbind();
					}
				});
				x$("#overlay").css({display:"block"})
      // }
			
			chip.attr({"stroke": "#ff0000", "stroke-width": 6, "stroke-linecap": "square"});
      // bioTip.start(null, chip, chip.hierarchyNode, chip.hierarchyNode.type);
			wheel.showChildLabels(chip.hierarchyNode);
			// hilite them
			var gl = chip.hierarchyNode.getGenesInContext();
			wheel.center.hiliteList(gl);
    // }
	};
	function hoverOffChip(chip) {
    // var browser = parent.comm.BrowserDetect.browser;
    // var browserVersion = parseFloat(parent.comm.BrowserDetect.version);
    // if (!(browserType.toLowerCase() == "safari" && browserVersion >= 5.1)) {
			wheel.overlay.hide();
    // }
    // bioTip.hideTip(null);

		// only unhighlight if we're not the current selection
		if (selectionMgr.get().obj !== chip.hierarchyNode) {
			if(chip.hierarchyNode.inContext) {
				chip.attr("stroke",chip.baseColor);
			} else {
				chip.attr("stroke","#999999");
			}
			
		} else {
			chip.attr("stroke","#000");
		}
		chip.attr({"stroke-width": wheel.rim.lineWidth[chip.hierarchyNode.level],
			"stroke-linecap": wheel.rim.linecap[chip.hierarchyNode.level]});
		wheel.center.dehiliteList();
	}
	function clickOnChip(chip) {	
		// un-highlight the last selection
		var sel = selectionMgr.get();
		if (sel && sel.obj) {
			var lastChip =sel.obj.rimChip;
			if (lastChip) lastChip.attr("stroke", lastChip.baseColor);
		}
		
		selectionMgr.set(chip.hierarchyNode);
	};

	function drawTextOnArc(c,txt, radius, angle) {
		// draw a text string on an arc.
		// have to cobble it together - raphael doesn't support text on a path
		// text is centered at the (radius, theta) location

		var ta = c.print(0,0, txt.toUpperCase(),c.getFont("DejaVu"), 11); // create paths of text objects
		var nh = Math.floor(txt.length/2); 
    var curMinTheta = angle;
    var curMaxTheta = angle;
		var bbox;
		var orient;
		var rotBias;
		var pobj;
		var o = [];
		var indx = 0;
		for (var i=0; i<txt.length; i++) {  // spaces aren't turned into paths... missing gaps
			o[i] = {text:txt[i].toUpperCase(), p:null};
			if(txt[i] != " " ) {
				o[i].p = ta[indx];
				indx++;
			}
		}
		
		if(angle < 180) {  // make sure text is legible and not upside down
			orient = 1;
			rotBias = 90;
		} else {
			orient = -1;
			rotBias = 270;
		}
		if(angle > 360) {
			orient=1;
			rotBias = -270;
		}
		for (var i=nh; i>=0; i--) {  // the first half
			//place this glyph
			if(o[i].text != " ") {
				pobj = o[i].p;
				bbox = pobj.getBBox();
				loc = getXYonCircle(curMinTheta, radius);
				var xc = bbox.x+ bbox.width/2; 
				var yc = bbox.y+ bbox.height/2; 
				pobj.rotate(rotBias-curMinTheta, xc, yc); // centered on the glyph
				pobj.translate(loc.x+ wheel.translation.x-xc, loc.y+wheel.translation.y -yc);
				curMinTheta = curMinTheta + orient*360*bbox.width*1.2/(2*radius*Math.PI);
				indx--;
			} else {
				curMinTheta = curMinTheta + orient // one degree for spaces
				// don't decrement the counter
			}
			
		}
		for (i=nh+1; i<txt.length; i++) {  // now the other half on the other side of the midpoint
			if(o[i].text != " ") {
				pobj = o[i].p;
				bbox = pobj.getBBox();
				xc = bbox.x+ bbox.width/2; 
				yc = bbox.y+ bbox.height/2;
				curMaxTheta = curMaxTheta - orient*360*bbox.width*1.2/(2*radius*Math.PI);
				loc = getXYonCircle(curMaxTheta, radius);
				pobj.rotate(rotBias-curMaxTheta, xc,yc);
				pobj.translate(loc.x+ wheel.translation.x-xc, loc.y+wheel.translation.y-yc);
				indx++;
			} else {
				curMaxTheta = curMaxTheta - orient;
			}
		}
		return ta;
	}
	
	
	function drawArc(c, startAngle, endAngle, xRot, arcFlag, sweepFlag, color, level) {
		var curRad = wheel.rim.levelBands[level].radius;  // the inner one
		var loc = getXYonCircle(startAngle, curRad);		
		var endpt = getXYonCircle(endAngle, curRad);

		var o=c.path(genPathArc(loc.x, loc.y, curRad, xRot, arcFlag, sweepFlag, endpt.x, endpt.y));
		var width = wheel.rim.lineWidth[level];
		var linecap = wheel.rim.linecap[level];
		o.attr("stroke", color);
		o.attr("stroke-width", width).attr("stroke-linecap", linecap);
		o.translate(wheel.translation.x, wheel.translation.y);
		return o;
	}
	
	function drawGradatedArc(c,startAngle, endAngle, outerRad, xRot, arcFlag, sweepFlag, startColor) {
		var curRad = outerRad;
		var curColor = startColor;  // hsv  color.h, etc
		var nsteps = 7;
		var delSat = curColor.s/nsteps;
		var delVal = (1-curColor.v)/nsteps;
		var delAlpha = 0.5/nsteps;
		var alpha = 0.5;
		
		var oset = c.set();
		for (var i=0; i<nsteps; i++) {  // draw as a series of linear arcs - varying alpha only
			var loc = getXYonCircle(startAngle, curRad);		
			var endpt = getXYonCircle(endAngle, curRad);
			var o=c.path(genPathArc(loc.x, loc.y, curRad, xRot, arcFlag, sweepFlag, endpt.x, endpt.y));
			alpha = alpha - delAlpha;
			var cc = "hsba(" + curColor.h + ", " + curColor.s + ", " + curColor.v + ", " + alpha + ")";
			o.attr("stroke-width", 2).attr("stroke", cc);
			
			o.translate(wheel.translation.x, wheel.translation.y);
			curRad = curRad -1;  // width is two, offset is 1... eliminates aliasing
			oset.push(o);
		}
		return oset;
	}
		
		function genPathArc(sx,sy,radius,xaxisrot, largeArcFlag, sweepFlag, ex,ey) {
			var t="M" + sx + " " + sy + "A" + radius + " " + radius + " " + xaxisrot ;
			t+= " " + largeArcFlag + " " + sweepFlag + " " + ex + " " + ey;
			return t;
		}
		
		var tipTimer = null;
		
		function Overlay() {
			this.containerDiv  = document.createElement("div");
			this.containerDiv.className = "overlay";
			
			this.containerDiv.id = "overlay";
			document.body.appendChild(this.containerDiv);
			this.jContainer = x$(document.getElementById("overlay"));
			//
			// a chrome bug w.r.t. pointer-events: none
			// also ie doesn't support it... need to grab the vml and set it specifically
			var browserVersion = 5.1;
      var browserType = "safari";
      
			if(browserType.toLowerCase() == "safari" && browserVersion >= 5.1) {
				x$("#overlay").css({cursor:"pointer"});
				this.jContainer.css({display: "none"});
			}
			// end bug hack....
			this.wide = 100;
			this.high = 100;
			this.canvas = Raphael(this.containerDiv, this.wide, this.high);
			this.maxNumLeafs = 10;  // display the top N children if we're at the periphery of the rim (noChildChips)
			this.xOff = 0;
			this.yOff = 0;  // set in showChildLabels each time.... bummer

			this.resize = function() {
			  /*
				// can't calculate document size since it's dependent on this.jContainer's current size
				this.canvas.clear();
				this.canvas.setSize(100,100);
				this.jContainer.css("width",100);
				this.jContainer.css("height", 100);
				// now the width and height can be accurate.
				var w = x$(document).width() -10;
				var h = x$(document).height() -10;
				this.jContainer.css("width", w);
				this.jContainer.css("height", h);
				this.wide = w;
				this.high = h;
				this.canvas.setSize(w,h);
				*/
			};

			this.showChildLabels = function(o) {
        if(o.type == "leaf") return;
        // TODO RE - Make sure these values are correct.
				var wl = 250;
				var wh = 250;
				        
        // var wl = wheel.jContainer.offset().left;  // shouldn't have to do this here...
        // var wh = wheel.jContainer.offset().top;  
				this.xOff = wl+wheel.wide/2;
				this.yOff = wh+wheel.hi/2;
				this.canvas.clear();				
				var labelObjs = [];
				var outerRad = wheel.rim.calloutBand.radius;				
				// the semi-opaque mask over everything except the wheel
				var mtxt = "";
				mtxt = "M0,0 L" + this.wide + ",0 L" + this.wide + "," + this.high;
				mtxt+= "L 0," + this.high + "L 0," + this.yOff + "L" + (this.xOff-outerRad) + ", " + this.yOff;
				mtxt+= genPathArc(this.xOff-outerRad, this.yOff, outerRad, 0,1,0,this.xOff-outerRad, this.yOff-1);
				mtxt+= "L" + (this.xOff-outerRad) + ", " + this.yOff + "L0, " + this.yOff + "L0,0";
				this.canvas.path(mtxt).attr("fill", "#CCCCCC").attr("fill-opacity", "0.3").attr("stroke", "none");
				//this.canvas.path(mtxt).attr("fill", "#ff66CC").attr("fill-opacity", "0.4").attr("stroke", "none");
				
				var parAngle = (o.angles.end + o.angles.start)*0.5;
				var isBottom = false;
				if(o.angles.end > 270 && o.angles.start < 270) isBottom = true;  // special case
				// need to start at the middle child and work your way
				// outwards... making sure that there's enough vertical space
				// between each label and its previous neighbor
				// some special case for the bottom of the wheel to handle left and right orientations
				// note that if there are children, but not rimChips for them - then all the labels
				// emmanate from the same location (fan out)
				var minDeltaY = 14;
				var vertMin = o.children.length*minDeltaY;  // need at least this much vertical space
													 // assuming its not split on the bottom of the wheel
															
				var midAngle = parAngle;  // the parent mid angle
				
				var c;
				// sort them by angle....
				var children = [];
				var parRad = wheel.rim.levelBands[o.level].radius;
				var parP0 = getXYonCircle(parAngle, parRad);
				var parP1 = getXYonCircle(parAngle, outerRad);
				var minY = -outerRad;
				var maxY = outerRad;
				var sRad;
				var xe;
				var hasRimChip = false;
				for (var i=0; i<o.children.length; i++) {
					var to = o.hierarchyParentObj.nodes[o.children[i]];
					if(to.rimChip) {
						hasRimChip = true;
						break;
					}
				}
				
				var addedChild = false;
				var childCount = -1;
				for (var i=0; i<o.children.length; i++) {
					addedChild = false;
					
					var to = o.hierarchyParentObj.nodes[o.children[i]];
					var ta = midAngle;
					sRad = parRad;
					if(to.rimChip) {
						ta = (to.angles.end + to.angles.start)*0.5;
						sRad = wheel.rim.levelBands[to.level].radius;						
					} 
					var p0 = getXYonCircle(ta,sRad);
					var p1 = getXYonCircle(ta, outerRad);
					if (hasRimChip) {
						if (to.rimChip) {
							childCount++;
							children[childCount] = {o:to, angle:ta, chipRadius:sRad, p0:p0, p1:p1};
							addedChild = true;
						}
					} else {
						childCount++;
						children[childCount] = {o:to, angle:ta, chipRadius:sRad, p0:p0, p1:p1};
						addedChild = true;
					}
					//children[i] = {o:to, angle:ta, chipRadius:sRad, p0:p0, p1:p1};
					
					if (addedChild) {
						if(ta >90 && ta <=270) {
							children[childCount].tAnchor = "end";
						} else {
							children[childCount].tAnchor = "start";
						}
					}
				}
				
				
				children.sort(function(a,b) {
					return a.angle - b.angle;  // in increasing angle
				});
				// if there are no child chips then pick the highest ranked items (if they are ranked) 
				// and show those along with a " and NN lower ranked processes ..."
				var co = [];
				if(!hasRimChip) {
					var topic = children[0].o.topicArray;
					if(topic == pathways || topic == diseases || topic == bioProcesses) {
						co = arrayIntersection(o.getChildObjects());  // all descendant leaf nodes
						children=[];
						for (i=0; i<co.length; i++) {
							children.push({o:co[i], angle:parAngle,chipRadius:parRad, p0:parP0, p1:parP1});
						}
						children.sort(function(a,b) {
							return a.o.getRank().rank - b.o.getRank().rank;
						});	
					}
					// take the most significant ones only up to maxNumLeafs
					if(children.length > this.maxNumLeafs) {
						children =  children.slice(children.length-this.maxNumLeafs, children.length);
					}	
				}
				// something special for the nasty case at the bottom of the wheel... (doesn't seem to happen at top)
				if(isBottom && !hasRimChip) {
					var mid = Math.floor(children.length/2);
					children[mid].p1 = getXYonCircle(270, outerRad);
					// now layout in decreasing angle
					var curY = children[mid].p1.y;
					for (var i=mid-1;i>=0; i--) {
						curY = curY-minDeltaY;
						var xe = getXonCircle(235,outerRad, curY);
						children[i].p1 = {x:xe, y:curY};
					}
					// now in increasing angle
					var curY = children[mid].p1.y;
					for (i=mid+1; i<children.length; i++) {
						curY = curY-minDeltaY;
						var xe = getXonCircle(290,outerRad, curY);
						children[i].p1 = {x:xe, y:curY};
					}
				} else {
					// first layout starting with the largest chips (also largest angle)
					// when this fails then start at the epicenter of the failure - the most congested area
					var hasFailed = false;
					i = children.length-2;
					while(!hasFailed && i >=0) {
						if(Math.abs(children[i].p1.y-children[i+1].p1.y) < minDeltaY) hasFailed = true;
						i--;
					}
					if(hasFailed) {  // we ran out of space (vertical) now attack the most congested region first
						
						var mid = Math.floor((i+1)/2);
						var midChildAngle = children[mid].angle;
						var midY = children[mid].p1.y;
						var curY = midY;
						var xmult = 1;
						var wrapAround = false;
						for (var i=mid-1; i>=0; i--) {  // in decreasing size and angle
							angle = children[i].angle;
							if(angle > 90 && angle < 270 && !wrapAround) {
								xmult = -1;
							} else {
								//xmult = 1;
							}
							curY += xmult*minDeltaY; 
							if(!wrapAround) aval = angle;
							if(!wrapAround) {  // can only wrap around once.
								if(curY < minY) {
									// weve wrapped around at top
									wrapAround = true;
									xmult = -xmult;
									curY = minY;
									//aval is either 45 or 135
									aval = (angle <= 180)? 45: 135;
								} else {
									if(curY >= maxY) {
										// weve wrapped around at bottom
										wrapAround = true;
										xmult = -xmult;
										curY = maxY;
										// aval is either 215 or 300
										aval = (angle <= 270)? 315: 200;
									}
								}
							}
							xe = getXonCircle(aval, outerRad, curY);
	
							children[i].p1 = {x:xe , y:curY};
						}
						curY = midY;
						wrapAround = false;
							if(midChildAngle > 270) {
								xmult = -1;
							} else {
								xmult = 1;
							}
						for (var i=mid+1; i<children.length; i++) {
							angle = children[i].angle;
						
							curY += xmult*minDeltaY;
							if(curY > maxY || curY < minY) {
								// weve wrapped around at top or bottom
								wrapAround = true;
								xmult = -xmult;
								curY+= xmult*minDeltaY; // reset it
							}
							aval = angle;
							if(wrapAround) {
								aval = (curY<0)? 135: 305;  // just need an angle in the correct quadrant to determine which root to use
							}
							xe = getXonCircle(aval, outerRad, curY);
							var radY = children[i].p1.y;  // the y based on a straight radial direction
							if(xmult ==  1 && radY > curY && !wrapAround) break;
						  	if(xmult == -1 && radY < curY && !wrapAround) break;
							children[i].p1 = {x:xe , y:curY};
						}
					}
				}
				for (i=0; i<children.length; i++) {
					var objs = layoutRimLabel(this.canvas,children[i], hasRimChip);
					labelObjs.push(objs.callout);
				}

					// compensate for Safari bug
          // var browserVersion = parseFloat(parent.comm.BrowserDetect.version);
          // if (browserType.toLowerCase() == "safari" && browserVersion >= 5.1) {
						this.jContainer.css({display:"block"});
          // }

				// now add the "and xx additional lower ranking items "
				if(!hasRimChip && children.length != co.length) {
					// we're only showing a subset due to information explosion
					var ymax = -9999999;
					var xc;
					for (i=0; i<labelObjs.length; i++) {
						var bb = labelObjs[i].getBBox();
						if(bb.y > ymax) {
							ymax = bb.y;
							xc = bb.x + bb.width/2;
						}
					}

					var others = this.canvas.text(xc, ymax+18, "And " + (co.length-children.length) + " Additional Lower Ranked Items...");
					others.attr("text-anchor", "middle").attr({"font-size":"10pt", "fill":"#FF0000", "font-family":"sans-serif"});
					bb = others.getBBox();
					this.canvas.rect(bb.x-6, bb.y,bb.width+12, 12, 6).attr({"stroke": "none", "fill": "#FFFFFF", "fill-opacity": 0.6});
					others.toFront();

				}
			};
			
			this.hide = function() {
				this.canvas.clear();

				// compensate for Safari bug
        // var browserVersion = parseFloat(parent.comm.BrowserDetect.version);
        // if (browserType.toLowerCase() == "safari" && browserVersion >= 5.1) {
					this.jContainer.css({display:"nonet"});
        // }
			};
		}
		function layoutRimLabel(canvas, c, isCategory) {
			// what if rimChip doesn't exist??
			// fan out from the center point
			var lineAttr = {"stroke-width":1, "stroke":"#000000", "stroke-opacity":0.6};
			var textAttr = {"font-size":"10pt", "color":"#000000", "font-family":"sans-serif"};
			var maskAttr = {"stroke": "none", "fill": "#FFFFDD", "fill-opacity": 0.6};
			// disable consumer reports-like ranking icons in rimChip callouts  Sept 2011
			var iof = 0;
			/*
			if(c.o.topicArray == pathways || c.o.topicArray == diseases || c.o.topicArray == bioProcesses) {
				var iof = 1;  
			} else {
				var iof = 0;
			}
			*/
			var xOff = wheel.overlay.xOff;
			var yOff = wheel.overlay.yOff;
			
			var tAnchor = (c.p1.x <= 0)? "end": "start";
			if(c.p1.x == 0 && c.p1.y < 0) tAnchor = "start";  // way at the top
			var t = "M" + (xOff+c.p0.x) + ", " + (yOff+c.p0.y) + "L" + (xOff + c.p1.x) + ", " + (yOff + c.p1.y);
			var xmult = (tAnchor == "end")? -1: 1;
			var tieline= canvas.path(t).attr(lineAttr);
			
			// title case this... for some reason biological processes arent title cased by default... check out processes.js
			var callout = canvas.text(xOff+c.p1.x+iof*xmult*14, yOff+c.p1.y, c.o.name.toTitleCase()).attr(textAttr).attr("text-anchor", tAnchor);
			var bb = callout.getBBox();
			// note height+2....................
			var mask = canvas.rect(bb.x-4, bb.y, bb.width+8, bb.height+2, 6).attr(maskAttr);
			if(iof ==1) {  // has a rank icon
				var rankStats = c.o.getRank();
				var rank = rankStats.rank;
				var ic = rankIcon(Math.max(0,Math.min(3,Math.floor(rank*4))), canvas);
				ic.translate(xOff+c.p1.x+xmult*iof*6, yOff+c.p1.y);
			}
			callout.toFront();
			return {tieline:tieline, callout:callout, icon:ic, mask:mask};
		}
	

