function HistoryPanel(parentDivID, divID, height, winSlice, showTip, includeShowAllBtn) {
		this.fillColors = {
			bgfill: "#666666",
			navActive: "#70A807",
			navDisabled: "#666666",
			generic: "#BBBBBB",
			pathway: "#70A807",
			bioFunction: "#00BCAC",
			process: "#9E269A",
			disease: "#E79212",
			interaction: "#2285C3"
		};
		this.act = [];
		this.act["add"] = "Added";
		this.act["focusOn"] = "Filtered By";
		this.act["initialize"] = "Included all genes";
		this.act["remove"] = "Removed";
		this.act["setGeneList"] = "Set Gene List";
		this.act["restore"] = "Reverted to";
	
		this.parentDivID = parentDivID;
		this.divID = divID;
		this.includeShowAllBtn = (includeShowAllBtn == undefined ? false : includeShowAllBtn);
		this.jContainer = $("#" + divID);
		this.showTip = (showTip == undefined ? false : showTip);
		this.navWidth = 12;
		this.xPad = this.navWidth + 3;
		this.xOffset = 4;  // pixels
		this.yOffset = 4;
		this.yMult = 1.0/genes.list.length;
		this.itemWidth = 10;
		this.fixedSlice = (winSlice != undefined && winSlice.size > -1);
		this.winSlice = (winSlice != undefined ? winSlice : {start:0, size:-1});
		this.winStart = this.winSlice.start;
		this.wide = ($("#" + divID).width() -10 - (this.xPad * 2));
		this.hi = height > -1 ? height : 100;
		this.minItemHeight = 5;
		this.canvas = Raphael(document.getElementById(divID), this.wide, this.hi );
		this.set = this.canvas.set();
		this.bg = this.canvas.rect(this.xPad, 0, this.wide - this.xPad*2, this.hi -10).attr("fill", this.fillColors.bgfill).attr("stroke", "#999999");

		if (this.includeShowAllBtn) {
			$("#" + parentDivID).append('<span id="showAllGenesBtn" class="button helpShowAllGenes">Show All Genes</span>');
			$("#showAllGenesBtn").css("margin-left",this.xPad);
			// add the tooltip
			//if (window.hasOwnProperty("help")) help.init({'helpShowAllGenes': {'tip': 'Click to bring all of your DEGs back into view', 'url': '', 'title': ''}});
		}
		
		$("#showAllGenesBtn").click(function() {
			if(!$(this).hasClass("buttonDisabled")) {
				parent._gaq.push(['_trackEvent', 'filterHistory|'+parent.view.pageType+'Page', 'click', 'showAllGenes']);
				contextMgr.restore(0);
			}
		});
		
		this.resize = function(wide, hi) {
			this.hi = hi;
			//this.wide = this.winSlice.size == -1 ? (wide -10 - (this.xPad * 2)) : Math.min(wide, (this.itemWidth * (this.winSlice.size + 2) + this.xPad * 2));
			this.wide = this.fixedSlice ? Math.min(wide, (this.itemWidth * (this.winSlice.size + 2) + this.xPad * 2)) : (wide -10);
			//Recalculate slice size based on available width
			var s = Math.floor((this.wide - (this.xPad * 2) - this.xOffset*2) / (this.itemWidth + 1));  
			this.winSlice.size = s;
			
			var curpos = contextMgr.history.getCurrentPosition(); 
			this.winStart = ((curpos >= this.winSlice.size) ? (curpos - this.winSlice.size + 1) : 0);
			if (curpos < this.winSlice.size - 1) { // when the visible area widens to accommodate the entire history
				contextMgr.history.setCurrentPosition(contextMgr.history.size() - 1);
			}

			$("#" + this.divID).height(hi).width(this.wide);
			if (this.wide > this.canvas.width) {
				 // This is mainly to handle the case where the page was smaller when opened and then maximized. 
				 // As a result, the size of the canvas originally allocated will not be sufficient.
				$("#" + this.divID).html('');
				this.canvas = Raphael(document.getElementById(divID), this.wide, this.hi );
				this.set = this.canvas.set();
				this.bg = this.canvas.rect(this.xPad, 0, this.wide - this.xPad*2, this.hi -10).attr("fill", this.fillColors.bgfill).attr("stroke", "#999999");
			}
			this.bg.attr({width: this.wide - this.xPad*2, height: hi -10});
			
			this.render("resize");
		};

//__Tim
//		this.controls = new PanelControls("historyControls",
//			    [{imgSrc:"history", title:"previous context", isEnabled:true}]);

		this.jContainer.mouseover(function(e) {
//			__Tim
//			bioTip.forceHide();
		});
		
		this.appendItem = function(indx, start) {
			var index = (indx==null)? contextMgr.history.size()-1 : indx;
			if (start) {
				var x = this.xPad + this.xOffset + (index - start)*this.itemWidth + (index - start) + 2;
			} else {
				var x = this.xPad + this.xOffset + (index - this.winStart)*this.itemWidth + (index - this.winStart) + 2;
			}
			var hItem = contextMgr.history.get(index);
			var h = Math.max(2,hItem.context.length*this.yMult*(this.hi-10-2*this.yOffset));
			if (h < this.minItemHeight) { h = this.minItemHeight; }; // enforce a minimum height
			var y = this.hi - h -this.yOffset -10;
			var gobj = this.canvas.rect(x,y,this.itemWidth, h).attr("fill", this.fillColors.generic).attr("stroke-width", 0);
			gobj.itemIndex = index;
			this.colorHistoryitem(hItem, gobj);
//			if (!this.showTip) {
//				gobj.attr("title", hItem.getTipText());
//			}
			
			gobj.hover(function(e) {
				this.attr("fill", hiliteColor);
				if (historyPanel.showTip) {
					historyTip.showMaxTip(e);
				} else {
					historyTip.showDefaultTip(e);
				}
			}, function(e) {
				if (!e) e = window.event;
				var target = e.srcElement? e.srcElement : e.target;
				var hi = contextMgr.history.get(target.raphael.itemIndex);
				if (hi != undefined) {
					historyPanel.colorHistoryitem(hi, this);
				} else {
					this.attr("fill", historyPanel.itemColor);
				}
				if (historyPanel.showTip) {
					historyTip.hideMaxTip(e);
				} else {
					historyTip.hideDefaultTip(e);
				}
			});
			gobj.click(function(e) {
				parent._gaq.push(['_trackEvent', 'filterHistory|'+parent.view.pageType+'Page', 'click', 'historyItem']);
				contextMgr.restore(this.itemIndex);
			});
			this.set.push(gobj);
			// slide left if we're out of space.

			var bb = this.set.getBBox();
			if(bb.width > this.wide) {
				this.set.translate(-this.itemWidth-bb.x,0);
			}

			if(historyTip && !tourOptions.isOnTour) historyTip.set.toFront();
		};
		
		this.colorHistoryitem = function(hItem, gObj) {
			var fc = this.fillColors.generic;
			if (hItem.topicName != null && hItem.topicName.length > 0) {
				if (hItem.topicName.indexOf("pathway") >= 0) {
					fc = this.fillColors.pathway;
				} else if (hItem.topicName.indexOf("disease") >= 0) {
					fc = this.fillColors.disease;
				} else if (hItem.topicName.indexOf("rocess") >= 0) {
					fc = this.fillColors.process;
				} else if (hItem.topicName.indexOf("interaction") >= 0) {
					fc = this.fillColors.interaction;
				} else if (hItem.topicName.indexOf("function") >= 0) {
					fc = this.fillColors.bioFunction;
				} 
			}
			
			gObj.attr("fill", fc);
		};
		
		this.nav = new function() {
			this.canGoBack = function(hisp) {
				return contextMgr.history.getCurrentPosition() > hisp.winSlice.size - 1;
				//return hisp.winStart > 0;
			};
			this.canGoForward = function(hisp) {
				return contextMgr.history.getCurrentPosition() < (contextMgr.history.size() - 1);
				//return hisp.winStart < (contextMgr.history.size() - hisp.winSlice.size);
			};
		};
		
		this.drawNav = function(direction) {
			var h = this.hi -this.yOffset -10;
			if (direction == "back") {
				//var gobj = this.canvas.path("M0 30L60 60L60 0z");
				var gobj = this.canvas.path("M0 " + h/2 + "L" + this.navWidth + " " + h*0.75 + "L" + this.navWidth + " " + h*0.25 + "z");
				if (this.nav.canGoBack(this)) {
					gobj.attr("fill", this.fillColors.navActive);
				} else {
					gobj.attr("fill", this.fillColors.navDisabled);
				}
				this.set.push(gobj);
				gobj.hover(function(e){
					if (historyPanel.nav.canGoBack(historyPanel)) {
						this.attr("cursor", "pointer");
					}
				}).click(function(e) {
					if (historyPanel.nav.canGoBack(historyPanel)) {
						var curpos = contextMgr.history.getCurrentPosition();
						if (curpos - historyPanel.winSlice.size >= 3) {
							historyPanel.winStart = historyPanel.winStart - 3;
							contextMgr.history.setCurrentPosition(curpos - 3);
							historyPanel.render("navback", 3);
						} else {
							var ws = historyPanel.winStart;
							historyPanel.winStart = 0;
							contextMgr.history.setCurrentPosition(historyPanel.winSlice.size - 1);
							historyPanel.render("navback", ws);
						}
						
						parent._gaq.push(['_trackEvent', 'filterHistory|'+parent.view.pageType+'Page', 'click', 'navback']);
					}
				});
			} else {
				var x = this.wide;
				var gobj = this.canvas.path("M" + this.wide + " " + h/2 + "L" + (this.wide - this.navWidth) + " " + h*0.75 + "L" + (this.wide - this.navWidth) + " " + h*0.25 + "z");
				if (this.nav.canGoForward(this)) {
					gobj.attr("fill", this.fillColors.navActive);
				} else {
					gobj.attr("fill", this.fillColors.navDisabled);
				}
				this.set.push(gobj);
				gobj.hover(function(e){
					if (historyPanel.nav.canGoForward(historyPanel)) {
						this.attr("cursor", "pointer");
					}
				}).click(function(e) {
					if (historyPanel.nav.canGoForward(historyPanel)) {
						var curpos = contextMgr.history.getCurrentPosition();
						if (curpos + 3 <= contextMgr.history.size()) {
							historyPanel.winStart = historyPanel.winStart + 3;
							contextMgr.history.setCurrentPosition(curpos + 3);
							historyPanel.render("navforward", 3);
						} else {
							var ws = historyPanel.winStart;
							historyPanel.winStart = Math.max(0, contextMgr.history.size() - historyPanel.winSlice.size);
							contextMgr.history.setCurrentPosition(contextMgr.history.size() - 1);
							historyPanel.render("navforward", contextMgr.history.size() - ws);
						}
						//historyPanel.winStart++;
						parent._gaq.push(['_trackEvent', 'filterHistory|'+parent.view.pageType+'Page', 'click', 'navforward']);
					}
				});
			}
		};
		
		// for animation. 
		this.repaintHistoryItems = function(start, end, animationCount, direction) {
			
				for (var i=start; i< end; i++) {
					this.appendItem(i, start);
				};
				if (animationCount > 0) {
				//if (animationCount >= 1)
					if (direction == 'navback') {
						setTimeout(function() {
								historyPanel.render("animate", animationCount-1, start == 0 ? 0 : start-1, start == 0 ? end : end - 1, direction );
						}, 700);
					} else if (direction == 'navforward') {
						setTimeout(function() {
							historyPanel.render("animate", animationCount-1, 
									start >= (contextMgr.history.size() - historyPanel.winSlice.size)? start : start+1,
									end == contextMgr.history.size() ? end : end+1,
									direction );
						}, 700);
					}
			}
		};

		this.render = function(action, stepCount, fnRenderLabel, start, end, direction) {
			if (fnRenderLabel) {
				fnRenderLabel();
			} else if (parent.view.pageType == "context"){
				this.renderMaxLabel();
			} else {
				this.renderDefaultLabel();
			}
			
			if(this.set.length > 2) {
				this.set.remove();
				this.set = this.canvas.set();
			}
			
			if (action) {
				if (action == "addHistory") {
					if (this.winStart < contextMgr.history.size() - this.winSlice.size) {
						this.winStart = contextMgr.history.size() - this.winSlice.size;
					}
					contextMgr.history.setCurrentPosition(contextMgr.history.size() - 1); // always move to the end
				} else if (action == "removeHistory") {
					if (this.winStart < contextMgr.history.size() - this.winSlice.size - 1) {
						this.winStart = contextMgr.history.size() - this.winSlice.size;
					}
					contextMgr.history.setCurrentPosition(contextMgr.history.size() - 1);// always move to the end
				} else if (action == "resize") {
					var curpos = contextMgr.history.getCurrentPosition();
					if ((curpos + 1) >= this.winSlice.size) {
						this.winStart = (curpos + 1) - this.winSlice.size;
					} else {
						this.winStart = 0;
					}
					contextMgr.history.setCurrentPosition(this.winStart + this.winSlice.size - 1);
				}
			}
			
			this.drawNav("back");
			var count = this.winSlice.size == -1 ?  contextMgr.history.size() : 
				(this.winStart + this.winSlice.size) > contextMgr.history.size() ? contextMgr.history.size()  : this.winStart + this.winSlice.size;
			
//			if (action == "navback") {
//				this.repaintHistoryItems(this.winStart + stepCount, count + stepCount, stepCount, action);
//			} else if(action == "navforward") {
//				this.repaintHistoryItems(this.winStart - stepCount, count - stepCount, stepCount, action);
//			} else if (action == "animate") {
//				this.repaintHistoryItems(start, end, stepCount, direction); 
//			} else {
				for (var i=this.winStart; i< count; i++) {
					this.appendItem(i);
				};				
//			}
				
			this.drawNav("forward");
			
			//if (action != "navback" && action != "navforward" && action != "animate") {
			if (action == "addHistory" || action == "removeHistory") {
				var lastObj = this.set[this.set.length-2];  // glow animation on the most recent one.
				lastObj.attr("stroke-width", 6).attr("stroke", "#FF0000").attr("stroke-opacity", 1).attr("fill", "#FF0000");
				lastObj.animate({"stroke-width":0, "stroke-opacity":0},1500, function() {
					this.attr("stroke", "none");
					var hItem = contextMgr.history.get(contextMgr.history.size()-1);
					historyPanel.colorHistoryitem(hItem, this);
				});
			}
			
			//if (this.includeShowAllBtn) {
				if (genes.numberInContext() == genes.list.length) {
					$("#showAllGenesBtn").addClass("buttonDisabled");
				} else {
					//if ($("#showAllGenesBtn").hasClass("buttonDisabled")) {
						$("#showAllGenesBtn").removeClass("buttonDisabled");
					//}
				}
			//}
		};
		
		this.renderDefaultLabel = function() {
			var txt ='';
			var inCon = genes.numberInContext();
			var geneCount = genes.list.length;
			if (inCon == geneCount) {
				txt = "<span class='filterHistoryLabelCount'>Showing all " + geneCount + " DEGs</span>";
			} else {
				txt = "<span class='filterHistoryLabelAction'>Filtered to</span>";
				txt+= "<span class='filterHistoryLabelCount'>";
				txt += inCon + " of " + geneCount + " DEGs";
				txt+= "</span>";				
			}

			$("#historyLabel").width(this.wide).html(txt);
		};
		
		this.renderMaxLabel = function() {
			var hItem = contextMgr.history.get(contextMgr.history.size()-1);
			var txt = "Filter History";

			txt+= "<span style='padding-left:14px'> Last Action: <span style='font-style:normal'>" + this.act[hItem.action];
			if (hItem.action == "restore") {
				var ancestor = hItem.getRootAncestor();
				txt += " : ";
				
				//if(ancestor.action == "initialize" || ancestor.action == "setGeneList") {
					txt += this.act[ancestor.action];
				//}
				hItem = ancestor;
			}
			
			if(hItem.action != "initialize" && hItem.action != "setGeneList") {
				if(hItem.topicName) {
						txt+= "<b> ";
						if(hItem.topicName.indexOf("gene")>=0) {
							txt+= hItem.topicObj.symbol;
						} else {
							txt+= hItem.topicObj.getName();
						}
						txt+= " </b><span style='font-size:10px'>(";
						txt += hItem.getTopicDisplayText();
						txt+= ")</span>";
				}
			}
			txt+= "</span></span>";

			$("#historyLabel").html(txt);
		};

		this.resize(this.wide, this.hi);
	}

	function HistoryTip() {
		this.canvas = historyPanel.canvas;
		this.bg = this.canvas.rect(0,0,1,1,6,6);
		this.bg.attr("fill", "#FFFFFF").attr("stroke", "#333333").attr("opacity", "0.5");
		this.pointer = this.canvas.path("M0 0l0 1");
		this.pointer.attr("fill", "#FFFFFF").attr("stroke", "#333333").attr("opacity", "0.5");
		this.textObj = this.canvas.text(0,0, "").attr("text-anchor", "start");
		this.set = this.canvas.set();
		this.set.push(this.bg);
		this.set.push(this.pointer);
		this.set.push(this.textObj);
		this.set.hide();

		this.showMaxTip = function(e) {
			var hItem = contextMgr.history.get(e.currentTarget.raphael.itemIndex);
			var x = e.clientX;
			var y = e.clientY;
			var isOnLeft = false;
			var itemObj = e.currentTarget.raphael;
			var itemBB = itemObj.getBBox();
			var act = historyPanel.act;

			if (hItem) {
				var hr = hItem.timeStamp.getHours();
				hr = (hr<10)? "0"+hr: hr;
				var minutes = hItem.timeStamp.getMinutes();
				minutes = (minutes<10)? "0"+minutes: minutes;
	
				var txt = "Gene Count: " + hItem.context.length + "\n";
				txt+= hItem.getTipText();
				//txt+= "\n";
				//txt+= hr+ ":" + minutes + " " + hItem.timeStamp.toDateString();
				this.textObj.attr("text", txt);
				var bb = this.textObj.getBBox();
				this.bg.attr("x", bb.x-2);
				this.bg.attr("y", bb.y-2);
				this.bg.attr("width", bb.width+4);
				this.bg.attr("height", bb.height+4);
				var midHi = bb.y+bb.height/2;
				if(itemBB.x < historyPanel.wide/2) {
					xOff = itemBB.x+itemBB.width+4;  // show on right
					pdat = "M" + (bb.x-12) +" " + midHi + "l10 -5v10z";
					this.pointer.attr("path", pdat);
				} else {
					xOff = itemBB.x-bb.width-14;
					pdat = "M" + (bb.x+bb.width+12) +" " + midHi + "l-10 -5v10";
					this.pointer.attr("path", pdat);
				}
				bb = this.set.getBBox();
				this.set.translate(xOff-bb.x, (historyPanel.hi-bb.height)/2-bb.y);
				this.set.toFront();
				this.set.show();
			}

		};

		this.hideMaxTip = function(e) {
			this.set.hide();
		};
		
		this.showDefaultTip = function(e) {
			if (!e) e = window.event;
			var target = e.srcElement? e.srcElement : e.target;
			var hItem = contextMgr.history.get(target.raphael.itemIndex);
			var x = e.clientX;
			var y = e.clientY;
			var jContainer = $("#filterHistoryParent");
			var left = jContainer.parent().offset().left + x;
			var top =  jContainer.parent().offset().top;
			var count = hItem.context.length;
			var txt = count + " DEG" + (count > 1 ? "s" : "") + " - ";
			txt += hItem.getTipText();
			$("#hoverTip").css("left", x + 10).css("top", y + 10).html(txt).show();
		};
		
		this.hideDefaultTip = function() {
			$("#hoverTip").hide();
		};
	}

	function HistoryItem(action, topic, topicObj, items ,index, ancestor) {
		this.action = action;
		this.topicName = topic;
		this.topic = topicArrays[topic.toLowerCase()];
		this.topicObj = topicObj;  // topicName is this.topicObj.name  ... or null (if category)
		this.items = items;  // an array of gene indices
		this.context = [];
		this.timeStamp = new Date();
		this.gobj = null;
		this.index = index;
		this.ancestor = ancestor ? ancestor : null;
		
		var act = [];
		act["add"] = "Added";
		act["focusOn"] = "Filtered";
		act["initialize"] = "Included all genes";
		act["remove"] = "Removed";
		act["setGeneList"] = "Set Gene List";
		act["restore"] = "Reverted to";
		
		if(action == "initialize" || action == "restore" || action == "focusOn") {
			for (var i=0; i<genes.list.length; i++) { 
				genes.list[i].inContext = false;
			};
		}
		
		for (var i=0; i< items.length; i++) {
			if(action == "add" || action == "initialize" || action == "restore" || this.action == "focusOn")    genes.list[items[i]].inContext = true;
			else if(action == "remove") genes.list[items[i]].inContext = false;
		}
		/*if(action == "focusOn") {
			// go thr all the genes.list - if the gene is in the items[i] list
			for (i=0; i<genes.list.length; i++) {
				var isInList = false;
				for (var j=0; j<items.length; j++) {
					if(i == items[j]) isInList = true;   // don't change its context... could be in or out
					//genes.list[i].inContext = true;
				}
				if(!isInList) genes.list[i].inContext = false; // This ensures that only the intersection between the current context and the new "focus on" genes is the new context.
			} 
		}*/
		
		if(action == "setGeneList") {
			for (i=0; i< genes.list.length; i++) {
				genes.list[i].inContext = false;
			}
			for (i=0; i< items.length; i++) {
				genes.list[items[i]].inContext = true;
			}
		}
		
		for (i=0; i < genes.list.length; i++) {
			if(genes.list[i].inContext) this.context.push(i);
		}
		
		this.getRootAncestor = function() {
			var anc = null;
			if (this.ancestor != null) {
				anc = this.ancestor;
				while (anc.ancestor != null) {
					anc = anc.ancestor;
				}
			}
			
			return anc;
		};
		
		this.getTipText = function() {
			var txt =  act[this.action];
			if(this.action != "restore") {
				if (this.action != "initialize" && this.action != "setGeneList") {
					txt+= " " + this.getTopicDisplayText();
					if(this.topicObj) txt+= " : "+  this.topicObj.getName();
				}
			} else if (this.action == "restore"){
				var ancestor = this.getRootAncestor();
				txt+=  " : " + act[ancestor.action]; 
				if (ancestor.action != "initialize" && ancestor.action != "setGeneList") {
					txt+= " " + ancestor.getTopicDisplayText();
					if(ancestor.topicObj) txt+= " : "+  ancestor.topicObj.getName();
				}
			}
			
			return txt;
		};
		
		this.getTopicDisplayText = function() {
			var txt = "";
			switch(this.topicName) {
			case "proFamily":
				txt= "molecular function";
				break;
			case "location":
				txt= "cellular location";
				break;
			case "interaction":
				txt= "molecular interaction";
				break;
			case "process":
				txt= "biological process";
				break;
			case "adhocGenes":
				txt= "genes";
				break;
			default:
				txt= this.topicName;
			}
			
			return txt;
		};
	};
