
function GeneMatrix(divID) {
		// constructor for the gene matrix ...
		this.jContainer = x$("#" + divID);
		this.hi   = 768;
		this.wide = 1024;
		var innerHi = 768;
		this.minSpotSize = 30; // minimum diameter
		this.maxSpotSize = 100;
		this.minSpacing = 4;
		this.spacing = this.minSpacing;
		this.padding = this.minSpacing+2;
		this.radius = this.minSpotSize;
		this.ncols = null;
		this.nrows = null;
		this.graphics = {objs:[], indices:[]};
		this.canvas = Raphael(document.getElementById(divID), this.wide, this.hi);
		this.connLines = [];
    this.eventLines = [];
		this.eventLinesSelect = null;
		this.curHiliteList = [];

		this.calcRowCols = function(wide, hi) {
      var pageType = "processes";
		  
			if(hi != null) {
				this.hi = hi;
				this.wide = wide;
			}  else {
        // var size = calcContentSize();
        // this.hi = size.hi;
        // this.wide = size.wide;

        this.hi   = 768;
        this.wide = (1024 - 300); // RE Allow for the side list.
			}

			// make sure we don't go below a certain size
			if (this.hi < 240) this.hi = 240;
			if (this.wide < 385) this.wide = 385;

			this.canvas.setSize(this.wide, this.hi);

			var activeList = genes.listInContext();
			
			// This should all be static assigned RE.
      if (true) {
        // show only those that are in this topic
        var gl = genes.listInContext();
        activeList = [];
        for (var i=0; i<gl.length; i++) {
          var curg = genes.list[ gl[i]];
          if(pageType == "processes" && curg.bioProcesses.length>0) activeList.push(gl[i]);
          if(pageType == "diseases" && curg.diseases.length>0) activeList.push(gl[i]);
          if(pageType == "interactions" && curg.interactions.length > 0) activeList.push(gl[i]);         
          // if(pageType == "pathways" && curg.pathways.length>0) activeList.push(gl[i]);  // pathways doesn't play here
        }
      }
			var nc = activeList.length;
			nc = Math.max(nc, 1);  // in case its zero....
			var a = 1;  // quadratic formula coefficients
			var b = 2*this.minSpacing;
			var c = this.minSpacing*this.minSpacing-(this.hi-2)*(this.wide-2)/nc; // account for padding
			var det = Math.sqrt(Math.abs(b*b-4*a*c));
			var dminus = (-b-det)/(2*a); // isn't this always negative?
			var diameter  = (-b+det)/(2*a);
			if(diameter <= 0 && dminus > 0) diameter = dminus;
			if(diameter <= 0) diameter = this.maxSpotSize;
			
			this.ncols = Math.max(1,Math.floor((this.wide-2)/(diameter+this.spacing)));
			this.nrows = nc/this.ncols;
			if(this.nrows != Math.floor(this.nrows)) this.nrows++;

			// this might have changed the diameter.. recalculate
			
			this.nrows = Math.max(1, this.nrows);
			diameter = Math.min(this.maxSpotSize,(this.hi-2)/this.nrows -this.spacing);
			this.hPadding = (this.wide-diameter*this.ncols - (this.ncols-1)*this.spacing)/2;
			this.vPadding = this.padding;
			this.radius = diameter/2;
      var scope = this;
      setTimeout(function(){scope.render(gSelType);}, 1);
		};

		this.render = function(pt) {
      pageType = pt;
			var activeList = genes.listInContext();

      if (true) {
       // show only those that are in this topic
       var gl = genes.listInContext();
       activeList = [];
       for (var i=0; i<gl.length; i++) {
         var curg = genes.list[ gl[i]];
         if(pageType == "processes" && curg.bioProcesses.length > 0) activeList.push(gl[i]);
         if(pageType == "diseases" && curg.diseases.length>0) activeList.push(gl[i]);
         if(pageType == "interactions" && curg.interactions.length > 0) activeList.push(gl[i]);         
         // if(pageType == "pathways" && curg.pathways.length>0) activeList.push(gl[i]);
       }
      }
			activeList.sort(function(a,b) {
				if(genes.list[a].symbol.toUpperCase() < genes.list[b].symbol.toUpperCase()) {
					return -1;
				} else {
					return 1;
				}
			});
			
			var xc= this.radius + this.hPadding;
			var yc=-this.radius + this.vPadding;
			var bg = this.canvas.rect(0,0,this.wide, this.hi).attr("fill", "#DDDDDD").attr("stroke", "none");


      var oOpt = pageHeaders[pageType].viewState.orgBy;
      var sOpt = pageHeaders[pageType].viewState.sizeBy;
      var cOpt = pageHeaders[pageType].viewState.colorBy;  
			
			for (var i = 0; i < activeList.length; i++) {
				var index = activeList[i];
				if(i % this.ncols == 0) {
					xc = this.radius + this.hPadding;
					yc = yc + (this.radius * 2) + this.spacing;
				}
				var gi = new GeneIcon(this.canvas, xc, yc, this.radius, index, pageType, oOpt, sOpt, cOpt);
				this.graphics.objs.push(gi);
				this.graphics.indices.push(index);
				xc = xc + this.radius*2 + this.spacing;
			}
      selectionMgr.hilite();
      if(pageType == "interactions" && selectionMgr.geneList.length > 0) {
       var g = genes.list[selectionMgr.geneList[selectionMgr.geneList.length-1]];  // use the last one if there's a collection of them.
       this.drawConnections(g);
      }

	};


		this.clearConnections = function() {
      // for (var i=0; i<this.graphics.objs.length; i++) {
      //  var gicon = this.graphics.objs[i];
      //  gicon.neighborRing.hide();
      // }


			for ( var i in this.connLines) {
        // this.connLines[i].remove();
        // this.eventLines[i].graphic.remove();
			}
			this.connLines = [];
			this.eventLines = [];
			this.eventLinesSelect = null;
			this.canvas.safari();  // force rendering
		};

    // Used by Interactions to draw the connections
		this.drawConnections = function(g) {
			var clrs = [];
			clrs["upstream"] = {ring:"#FF8888", arc:"#FF0000"};
			clrs["downstream"] = {ring:"#8888FF", arc:"#0000FF"};
			clrs["both"] = {ring:"#FF88FF", arc:"#FF00FF"};
			this.clearConnections();
			if(g.inContext && g.graphics.matrix) {
				var gbb = g.graphics.matrix.neighborRing.getBBox();
				var ps = {x:gbb.x+gbb.width/2, y:gbb.y+gbb.height/2};
				var pe;
				g.graphics.matrix.neighborRing.attr("stroke", "#FF0000");
				g.graphics.matrix.neighborRing.show();
				var icnt = 0;
				for (var i=0; i<g.interactions.length; i++) {
					var conn = g.interactions[i];
					var nei = genes.list[conn.neighbor];
					if(nei.inContext && nei.graphics.matrix) {
						nei.graphics.matrix.neighborRing.attr("stroke", clrs[conn.neighborDirection].ring);
						nei.graphics.matrix.neighborRing.show();
						var nbb = nei.graphics.matrix.neighborRing.getBBox();
						pe = {x:nbb.x+nbb.width/2, y:nbb.y+nbb.height/2}; 
						// implemented subPath - the raphael version was way tooooo slow - tons of pow function calls
						var cline = this.canvas.path(subPath(ps,nbb.width/2, pe, nbb.width/2));
            cline.attr("stroke", clrs[conn.neighborDirection].arc).attr("stroke-width", 1);

						this.connLines[icnt] = cline;

						var eLine = this.canvas.path(subPath(ps,nbb.width/2, pe, nbb.width/2));
						this.eventLines[icnt] = {};
						this.eventLines[icnt].graphic = eLine;
						this.eventLines[icnt].gene = g;
						this.eventLines[icnt].neighbor = nei;

						eLine.attr({stroke: "#fff", "stroke-width": "5", opacity: "0.01", cursor: "pointer"});
						eLine.node.setAttribute('id', 'eLine' + icnt);
						eLine.toFront();

						var scope = this;

            // eLine.mouseover(function(e) {
            //  scope.hiliteCline(this);
            // });
            // 
            // eLine.mouseout(function(e) {
            //  scope.dehiliteCline(this);
            // });
            // 
            // eLine.click(function(e) {
            //  scope.selectCline(this);
            // });
						

            // iPad interactions
            eLine.touchstart(function(e) {
              scope.hiliteCline(this);
            });

						eLine.touchend(function(e) {
							scope.dehiliteCline(this);
						});


						// increment the counter
						icnt++;
					}
				}
			}
		};

		this.hiliteCline = function(cline){
			if (!this.eventLinesSelect || this.eventLinesSelect != cline) {
				var re = /\D*/;
				var idx = cline.node.getAttribute('id').replace(re, '');

				// change the matching connection line stroke
				this.connLines[idx].attr({"stroke-width": 5});
			}
		};

		this.dehiliteCline = function(cline, override){
			if (override || !this.eventLinesSelect || this.eventLinesSelect != cline) {
				var re = /\D*/;
				var idx = cline.node.getAttribute('id').replace(re, '');

				// change the matching connection line stroke
				this.connLines[idx].attr({"stroke-width": 1});
			}
		};

		this.selectCline = function(cline) {
			var re = /\D*/;
			var idx = cline.node.getAttribute('id').replace(re, '');

			// dehilite old select
			var oldSel = this.eventLinesSelect;
			if (oldSel && oldSel != cline) this.dehiliteCline(oldSel, true);

			// set new select
			this.eventLinesSelect = cline;

			// display related studies for both the gene and its connection
			var g = this.eventLines[idx].gene;
			var n = this.eventLines[idx].neighbor;
			parent.view.evidencePanel.set(g.getTopic(), [g, n], parent.comm.evidence, parent.view.pageType);
		};

		this.hiliteList = function(indices) {
			if(this.curHiliteList.length != 0) this.dehiliteList();
			for (var i=0; i<indices.length; i++) {
				var gi = genes.list[indices[i]].graphics.matrix;
				if(gi) {
					gi.hilite();
					this.curHiliteList.push(gi);
				}
			}
		};
		
		this.dehiliteList = function() {
			for (var i=0; i< this.curHiliteList.length; i++) {
				this.curHiliteList[i].dehilite();
			}
			this.curHiliteList.length = 0;
		};
		
		this.clear = function() {
			for (var i=0; i< this.graphics.objs.length; i++) {
				var indx = this.graphics.indices[i];
				genes.list[indx].graphics.matrix = null;  // remove the reference
			}
			this.canvas.clear();
			this.connLines = [];
			this.graphics.objs = [];
			this.graphics.indices = [];

		};
		
		this.reset = function() {
			this.clear();
			this.canvas.setSize(10, 10);   // a minimal size so it is not setting its parent's size			
		};
		
		this.resize = function(wide, hi) {
			this.calcRowCols(wide, hi);
			this.render(gSelType);
		};
		
		this.redo = function() {
			this.reset();
			this.calcRowCols(contentDivWidth, contentDivHeight);
			this.render(gSelType);
		};
		
		this.getGeneScreenLoc = function(index) {  // get the screen location of this geneIcon 
			var obj = genes.list[index].graphics.matrix.o;  // the raphael object
			var box = obj.getBBox();
			var l = this.jContainer.offset().left + 5;  // why 5?
			var t= this.jContainer.offset().top + 5;
			box.x += l;
			box.y += t;			
			return box;
		};

		this.zoomIn = function() {};
		this.zoomOut= function() {};
		this.resize = function(wide, hi) {   // this is pretty heavy handed... would rather scale and translate each object... but..
			this.clear();
			this.calcRowCols(wide, hi);
			//this.render();
		};
		this.remove = function() {};

		this.exportData = function() {
			nyi("Export Matrix");
		};
	}

	function subPath(ps, offs, pe, offe) {
		var dPath = "";
		var len = Math.sqrt((pe.x-ps.x)*(pe.x-ps.x) + (pe.y-ps.y)*(pe.y-ps.y));
		if(len > .0001)  {
			var linv = 1.0/len;
			var p0 = {x:ps.x+(pe.x-ps.x)*offs*linv, y:ps.y+(pe.y-ps.y)*offs*linv};
			var p1 = {x:ps.x+(pe.x-ps.x)*(len-offe)*linv, y:ps.y+(pe.y-ps.y)*(len-offe)*linv};
		} else {
			p0 = ps;
			p1 = pe;
		}
		dPath = "M " + p0.x + ", " + p0.y + "L " + p1.x + ", " + p1.y;
		return dPath;
	}
	
	
  //  GeneIcon Object - used in geneMatrix
  //

  	function GeneIcon(c, x, y, r, index, topic, oOpt, sOpt, cOpt) {
  	  
  		var ratio;  // small to big circles (radius)
  		var rots = new Array();
  		// angles at which to place dots  ... on a unit circle
  		rots[1] = [{x:0, y:0}];
  		rots[2] = [{x:0,y:-1}, {x:0, y:1}];
  		rots[3] = [{x:0,y:-1}, {x:-Math.sqrt(3)/2, y:0.5}, {x: Math.sqrt(3)/2, y: 0.5}];
  		rots[4] = [{x:Math.sqrt(2)/2, y:-Math.sqrt(2)/2},  {x:-Math.sqrt(2)/2, y:-Math.sqrt(2)/2},
  			       {x:-Math.sqrt(2)/2, y:Math.sqrt(2)/2},  {x: Math.sqrt(2)/2, y: Math.sqrt(2)/2}];

  		var ratios = new Array(1.0, 1.0, 0.4, 0.5, 0.4);
  		var ndMax;
  		var xx,yy,d;
  		var colors = [ "#F7931D", "#F05B93", "#39B54A", "#A67C52", "#77B3B7"]; // last one not used

  		this.isInTopic = false;
  		this.index = index;
  		this.o = c.set();
  		var circles  = c.set();
  		var bg = c.circle(0,0,r).attr("fill", "#FFFFFF").attr("stroke", "none");
  		circles.push(bg);
  		this.bg = bg;
  		var g = genes.list[index];

  		// the attributes are in legend.colorRange, legend.sizeRange, etc... use these to size, color and shape the icons....
  		var colorTopic = cOpt.split(" ")[0];
  		var colorMetric = cOpt.split(" ")[1];
  		var clr;
  		var fracTop = 0;
  		var fracBottom = 0;
  		if(colorMetric == "foldChange" || topic == "interactions") {
  			clrTop = g.getExpColor();  // this should be read from the legend widget
  			clrBottom = clrTop;
  		} else {
        if(colorTopic == "diseases") fracTop = g.diseases.length/genes.maxNumMembers["diseases"];
  			if(colorTopic == "processes") fracTop = g.bioProcesses.length/genes.maxNumMembers["processes"];
        if(colorTopic == "pathways") fracTop = g.pathways.length/genes.maxNumMembers["pathways"];
        if(colorTopic == "interactions") fracTop = g.neighborCount/genes.maxNumMembers["neighbors"];
  			clrTop = getColorByFraction(fracTop, false);
  			clrBottom = clrTop;
  		}
  		// add colored circles
  		switch(topic) {

  			case "diseases":  // disease evidence 
  				ndMax = 4; 
  				ratio = ratios[ndMax];
  				if(g.diseases.length > 0) {
  					 for (var i=0; i<ndMax; i++) {
  						if(g.evidenceType[i]) {   // does this gene have this dot?
  							xx = (1-ratio)*r*rots[ndMax][i].x;
  							yy = (1-ratio)*r*rots[ndMax][i].y;
  							d = c.circle(xx,yy, ratio*r);
  							d.attr("fill", colors[i]).attr("stroke", "none");
  							circles.push(d);							
  						}
  					 }
  					 this.isInTopic = true;
  				}
  				break;

  			case "interactions":
  				ndMax = 5; ///// hack  --- number of size choices
  				var numUp = 0;
  				var numDown = 0;
  				var bindFraction = 0;
  				numUp = g.upstreamInx/genes.maxNumMembers["neighbors"];  // fraction
  				numDown = g.downstreamInx/genes.maxNumMembers["neighbors"];
  				bindFraction = g.bindingInx/genes.maxNumMembers["neighbors"];
  				if(numUp > 0 || numDown > 0 || bindFraction > 0) this.isInTopic = true;
  				var upArc = Math.ceil(ndMax*numUp)*100/ndMax; // in degrees
  				var downArc = Math.ceil(ndMax*numDown)*100/ndMax; // in degrees
  				var upArc = numUp*150;  // max is 150 degrees
  				var downArc = numDown*150;
  				// calculate the stroke-width... based on r 
  				var sWidth = r/3;
  				if(bindFraction > 0) {
  					// binding visualization is in the center of the circle.
  					//The thickness of the "stroke" is a function of the number of binding relationships 
  					// few bindings = thin circle, lots-o-bindings = fully thick circle
  					// don't actually use a stroke, since that's slower than two circles to render
  					var bindRadius = r*5/6 - sWidth/2-1; // from r to r*5/6 is the up and down stream wedges .. binding goes inside them
  					var bindCircle = c.circle(0,0,bindRadius).attr("fill", clrTop).attr("stroke", "none");  // the colored circle
  					var brad2 = (1.0-bindFraction)*(bindRadius-1);
  					if(brad2 > 0) {
  						var bc2 = c.circle(0,0, brad2).attr("fill", "#FFFFFF").attr("stroke", "none");  // the white inner (mask) circle
  						this.o.push(bc2);
  					} 
  					this.o.push(bindCircle);
  				}
  				this.neighborRing = c.circle(0,0,r).attr("fill-opacity", 0).attr("stroke-width",
  						2).attr("stroke", "#FF0000").hide();
  				this.o.push(this.neighborRing);

  				if(upArc > 0) this.o.push(drawInterArc(c, 90-upArc/2, 90+upArc/2, clrTop, sWidth, r*5/6));
  				if(downArc > 0) this.o.push(drawInterArc(c, 270-downArc/2, 270+downArc/2, clrBottom, sWidth, r*5/6));
  				break;

  			case "processes":
  				// size by number of topic				
  				var siz = g.bioProcesses.length/genes.maxNumMembers["processes"];
  				if(siz > 0) {
  					this.isInTopic = true;
  					var sWidth = r/3;
  					var outerRadius = r*24/24 - sWidth/2-1; // from r to r*5/6 is the up and down stream wedges .. binding goes inside them
  					var outerCircle = c.circle(0,0,outerRadius).attr("fill", clrTop).attr("stroke", "none");  // the colored circle
  					var brad2 = (1.0-siz)*(outerRadius-2);
  					if(brad2 > 0) {
  						var bc2 = c.circle(0,0, brad2).attr("fill", "#FFFFFF").attr("stroke", "none");  // the white inner (mask) circle
  						this.o.push(bc2);
  					} 
  					this.o.push(outerCircle);
  				}
  				break;
  			default:

  		}

  		// add symbol name with white outline
  		var fillColor = (this.isInTopic)? "#FFFFFF": "#CCCCCC";
  		bg.attr("fill", fillColor);
  		this.o.push(circles);
  		var ltxt = genes.list[index].symbol == null ? genes.list[index].name : genes.list[index].symbol;
  		var lbl = gmText(c,ltxt,r*2, this.isInTopic);
  		if(lbl.outline) {
  			this.outline = lbl.outline;
  			this.o.push(this.outline);
  		};
  		
  		if(lbl.label) {
  			this.label = lbl.label;
  			this.o.push(this.label);
  		}

  		this.o.translate(x,y);
  		genes.list[index].graphics.matrix = this;  // store this for use in highlighting, etc

  		// need to account for isInTopic during selection and highlighting

  		this.select = function() {
  			if(this.isInTopic) {
  				if(this.outline) this.outline.attr("fill", "#4D4D4D").attr("opacity", 1).show();
  				if(this.label) this.label.attr("fill", "#FFFFFF").show();	
  			}
  			this.bg.attr("stroke", "#666666").attr("stroke-width", 3);
  		};

  		this.deselect = function() {
  			if(this.isInTopic) {
  				if(this.outline) this.outline.attr("fill", "#FFFFFF").attr("opacity", 0.7).show();
  				if(this.label) this.label.attr("fill", "#888888").show();	
  			}
  			this.bg.attr("stroke", "none");
  		};
  		
  		
  		// events

  		this.hilite = function() {
  			this.bg.attr({stroke:"#F00", "stroke-width":"3"});
  		};

  		this.dehilite = function() {
        // if(selectionMgr.isSelected(this.index, "genes")) {
        //  this.bg.attr("stroke", "#666666").attr("stroke-width", 3);
        // } else {
  				this.bg.attr({stroke:"none"});
        // }
  		};

      this.o.mouseover(function(event) {
       genes.list[index].graphics.matrix.hilite();
      });

      this.o.mouseout(function(event) {
       genes.list[index].graphics.matrix.dehilite();
      });

      // iPad Events
      this.o.touchstart(function(event) {
        genes.list[index].graphics.matrix.hilite();
      });
       
      this.o.touchend(function(event) {
        genes.list[index].graphics.matrix.dehilite();
      });

      // This works like a longTouch too 
      this.o.click(function(event) {
       if (gSelType != "interactions") {
         selectionMgr.set(genes.list[index]);
       } else {
         selectionMgr.set(genes.list[x$(this).attr("index")]);         
       }
       event.preventDefault();
      });
            
  	}

  	function drawInterArc(c, startAngle, endAngle, color, sWidth, curRad) {
  		var loc = getXYonCircle(startAngle, curRad);
  		var endpt = getXYonCircle(endAngle, curRad);

  		var o=c.path(genPathArc(loc.x, loc.y, curRad, 0, 0, 0, endpt.x, endpt.y));
  		o.attr("stroke", color);
  		o.attr("stroke-width", sWidth).attr("stroke-linecap", "butt");
  		return o;
  	}

  	function gmText(c, t, dmax, isInTopic) {
  		var fontSize;  // font size is dependent on the icon diameter
  		var fontColor = "#888888";
  		var outline;
  		var label;
  		var fillColor = (isInTopic)? "#FFFFFF": "#CCCCCC";
  		var minChar = 8;
  		fontSize = Math.floor(dmax/(0.6*minChar));
  		fontSize = Math.min(11, Math.max(8, fontSize))
  		var maxchar = Math.floor((dmax-10)/(0.6*fontSize));  // assumes font-size = 10
  		var o = {"font-family":"Verdana, Geneva, sans-serif", 
  				 "font-size":fontSize, 
  				 "text-anchor":"middle", "font-weight":"bold"};
  		var txt = t;
  		if(txt.indexOf("(") > -1) txt = txt.substring(0, txt.indexOf("("));
  		if(txt.indexOf("include") > -1) txt = txt.substring(0, txt.indexOf("include"));
  		var clipped = txt.substring(0,maxchar);
  		if(maxchar > 2) {
  			label = c.text(0,0, clipped).attr(o).attr("fill", fontColor).attr("stroke","none");
  			var bb = label.getBBox();
  			var width = bb.width+ bb.height;   // Math.min(bb.width+bb.height, dmax);
  			if(isInTopic) {
  				outline = c.rect(bb.x-bb.height/2, bb.y+1, width, bb.height, bb.height/2).attr("opacity", 0.7).attr("fill", fillColor).attr("stroke", "none");
  				outline.insertBefore(label);
  			}			
  		}
  		return {outline:outline, label:label};

  	}

