function roundNumber(num, dec) {  //http://stackoverflow.com/questions/2221167/round-number-in-javascript-to-n-decimal-places
    return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}	
	
//format a number into specified number of decimal places
Math.formatDecimals = function (num, digits) {
        //if no decimal places needed, we're done
        if (digits <= 0) {
                return Math.round(num); 
        } 
        //round the number to specified decimal places
        //e.g. 12.3456 to 3 digits (12.346) -> mult. by 1000, round, div. by 1000
        var tenToPower = Math.pow(10, digits);
        var cropped = String(Math.round(num * tenToPower) / tenToPower);

        //add decimal point if missing
        if (cropped.indexOf(".") == -1) {
                cropped += ".0";  //e.g. 5 -> 5.0 (at least one zero is needed)
        }

        //finally, force correct number of zeroes; add some if necessary
        var halves = cropped.split("."); //grab numbers to the right of the decimal
        //compare digits in right half of string to digits wanted
        var zerosNeeded = digits - halves[1].length; //number of zeros to add
        for (var i=1; i <= zerosNeeded; i++) {
                cropped += "0";
        }
        return(cropped);
} //Robert Penner May 2001 - source@robertpenner.com


//convert any number to scientific notation with specified significant digits
//e.g. .012345 -> 1.2345e-2 -- but 6.34e0 is displayed "6.34"
//requires function formatDecimals()
Math.toScientific = function (num, inDigs) {
        //deal with messy input values
		var sigDigs = (inDigs == null)? 3: inDigs;
        num = Number(num); //try to convert to a number
        if (isNaN(num)) return num; //garbage in, NaN out

        //find exponent using logarithm
        //e.g. log10(150) = 2.18 -- round down to 2 using floor()
        var exponent = Math.floor(Math.log(Math.abs(num)) / Math.LN10); 
        if (num == 0) exponent = 0; //handle glitch if the number is zero

        //find mantissa (e.g. "3.47" is mantissa of 3470; need to divide by 1000)
        var tenToPower = Math.pow(10, exponent);
        var mantissa = num / tenToPower;

        //force significant digits in mantissa
        //e.g. 3 sig digs: 5 -> 5.00, 7.1 -> 7.10, 4.2791 -> 4.28
        mantissa = Math.formatDecimals(mantissa, sigDigs-1); //use custom function
        var output = mantissa;
        //if exponent is zero, don't include e
        if (exponent != 0) {
                output += "e" + exponent;
        }
        return(output);
} //Robert Penner May 2001 - source@robertpenner.com


// Color Utils


	
	function getColorByFraction(f, useExpression) {
		// chartruse, yellow, orange, hot pink
		var clrs = new Array();
		clrs[0] =   new Array( "#87a631", "#8caa2d", "#8baa2c", "#88a82b", "#93ab2e", "#abb335", "#c5bb3c", 
							   "#ddc143", "#e7c545", "#e3c645", "#e1c043", "#e3b442", "#e5a440", "#e6973e", 
							   "#e6923d", "#e5963d", "#e8923d", "#ed853c", "#f2753b", "#f66739", "#f86237", 
							   "#f76635", "#f76038", "#f55241", "#f2424a", "#f03254", "#ef2859", "#ef275a", 
							   "#ef265b", "#ef2a57");

		// slateblue, cyan, lemon, melon, darkpink
		clrs[1] =   new Array( "#525b6a", "#55606d", "#55606d", "#55616e", "#54707a", "#518e94", "#4cafb0", 
		                       "#4bccc6", "#48d6d1", "#46d0ce", "#56d0be", "#79dda2", "#a0eb83", "#c3f568", 
		                       "#d0fe5c", "#c8ff60", "#caf164", "#dbcb65", "#ec9f69", "#fc766b", "#ff646d",
		                       "#ff686e", "#ff686c", "#f25f67", "#de5860", "#cc515a", "#c34c56", "#c34c56", 
		                       "#c34c56", "#c25257");
		// sea blue, sea foam, light cyan, light yellow, orange
		clrs[2] =   new Array( "#225278", "#225278", "#225379", "#22547a", "#205e80", "#19718b", "#138296", 
		                       "#1194a2", "#109ba6", "#0f96a2", "#259fab", "#7ed5da", "#a9eef0", "#b8f8f9", 
							   "#adf3f6", "#aff1f1", "#c4f7ef", "#dafceb", "#eeffe6", "#f7ffed", "#f6fffa", 
							   "#f3fee4", "#f2dfa7", "#efb65f", "#ec901c", "#eb7c00", "#eb7c00", "#eb7c01", 
							   "#eb7c00", "#eb7c00");
		// blue, green, yellow, orange, pink
		clrs[3] =   new Array( "#0F6E91", "#87A631","#E7C545","#E8923D", "#EF275A", "#EF275A");
		// a version of 3 with more in-betweens
		clrs[4] =   new Array( "#0F6E91", "#478864", "#79a393", "#92af37", "#c4ba3e", "#E7C545", "#e7b142", "#e89d3f", "#e98142", "#ec544e", "#EF275A", "#EF275A");
		
		clrs[5] =   new Array( "#225278", "#33a2b6","#d6df79","#eb7c00", "#eb7c00");
		var whichTable = (useExpression)? 5: 4;

		if(useExpression) {
			if(f<=-1.0) { // use first two colors for {rangeMin,-1}
				var recip = (genes.fcRange.min == -1.0)? 1: (-1.0-genes.fcRange.min);
				var frac = (f-genes.fcRange.min)/recip;
				if(frac<0.5) {
					return clrs[whichTable][0];
				} else {
					return clrs[whichTable][1];
				}
			} else {
				if(f>=1) { // forth and fifth colors for {1, rangeMax}
					recip = (genes.fcRange.max == 1)? 1: (genes.fcRange.max-1);
					var frac = (f-1)/recip;
					if(frac<0.5) {
						return clrs[whichTable][3];
					} else {
						return clrs[whichTable][4];
					}
				} else { // third color for {-1,1}
					return clrs[whichTable][2];
				}
			}
		} else {
			var ind = Math.min(clrs[whichTable].length-1,Math.floor(f*(clrs[whichTable].length-1)));
			return clrs[whichTable][ind];
		}
	}
	
	// Used in Interaction
  function getXYonCircle(theta, radius) {
  	var rad = Math.PI/180;
  	var x = Math.cos(theta*rad)*radius;
  	var y = -Math.sin(theta*rad)*radius;
  	return {x:x, y:y};
  }

	// Used in Interaction  
  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;
  }

  	
  // note inverse... min -> 3... max -> 0
	function quartile(valIn, range) {
		var valOut = valIn;
		var delRange = range.max - range.min;
		if(valIn > 0.75*delRange + range.min) return 0;
		if(valIn > 0.50*delRange + range.min) return 1;
		if(valIn > 0.25*delRange + range.min) return 2;
		return 3;
	}
	
	//colorList is an array of hex color values in string format.
	function getGradientRange(colorList) {
		var max = parseInt(colorList[0]), min = parseInt(colorList[0]);
		for (var i in colorList) {
			var cval = parseInt(colorList[i]);
			if(cval > max) {
				max = cval;
			}
			if (cval < min) {
				min = cval;
			}
		}
		
		return ['0x' + max.toString(16), '0x' + min.toString(16)];
	}
	
	// Mergesort - taken from "http://en.literateprograms.org/Merge_sort_%28JavaScript%29"
  function merge_sort(array,comparison) {
  	if (array.length < 2)
  		return array;
  	var middle = Math.ceil(array.length/2);
  	return ms_merge(merge_sort(array.slice(0,middle),comparison),
  			merge_sort(array.slice(middle),comparison),
  			comparison);
  }

  // renamed merge to ms_merge to avoid any potential collisions 
  function ms_merge(left,right,comparison) {
  	var result = new Array();
  	
  	while((left.length > 0) && (right.length > 0)) {
  		if (comparison(left[0],right[0]) < 0) {
        result.push(left.shift()); 
  		} else {
  			result.push(right.shift());
			}
  	}

  	while(left.length > 0) {
  	  result.push(left.shift());
  	}

  	while(right.length > 0) {
  	  result.push(right.shift());
  	}
  	return result;
  }
  // return intersection of arrays
  // TODO - RE Test to see if we can just go with indexOf
  var arrayContains = Array.prototype.indexOf ?
      function(arr, val) {
          return arr.indexOf(val) > -1;
      } :
      function(arr, val) {
          var i = arr.length;
          while (i--) {
              if (arr[i] === val) {
                  return true;
              }
          }
          return false;
  };
        
  function arrayIntersection(arrays) {
      var val, arrayCount, firstArray, i, j, intersection = [], missing;
      if (arguments.length >1) arrays = Array.prototype.slice.call(arguments);

      // Search for common values
      firstArr = arrays.pop();
      if (firstArr) {
          j = firstArr.length;
          arrayCount = arrays.length;
          while (j--) {
              val = firstArr[j];
              missing = false;

              // Check val is present in each remaining array
              i = arrayCount;
              while (!missing && i--) {
                  if ( !arrayContains(arrays[i], val) ) {
                      missing = true;
                  }
              }
              if (!missing) {
                  intersection.push(val);
              }
          }
      }
      return intersection;
  }  

function removeLastInstanceOfPattern(txt, pattern) {
  var result = txt;
	var index = -1;
	if (txt != null && pattern != null) {
		index =  txt.lastIndexOf(pattern);
		if (index > -1) {
			result = txt.slice(0,index);
			if (index < (txt.length - pattern.length - 1)) {
				result += txt.slice(index + pattern.length,  txt.length - 1);
			}
		}
	}

	return result;
}  


// Return a formatted string for a date;
//	var formatString =  "#DD#/#MM#/#YYYY# #hh#:#mm#:#ss#" ;
function getFormattedDate(format, dObj){
	var formatString =  "#DD# #MMM# #YYYY#";
	var dateObject;
	if (arguments.length > 1) {
		formatString = arguments[0];
		dateObject = arguments[1];
	} else {
		dateObject = arguments[0];
	}
	
    var YYYY,YY,MMMM,MMM,MM,M,DDDD,DDD,DD,D,hhh,hh,h,mm,m,ss,s,ampm,AMPM,dMod,th;
    YY = ((YYYY=dateObject.getFullYear())+"").slice(-2);
    MM = (M=dateObject.getMonth()+1)<10?('0'+M):M;
    MMM = (MMMM=["January","February","March","April","May","June","July","August","September","October","November","December"][M-1]).substring(0,3);
    DD = (D=dateObject.getDate())<10?('0'+D):D;
    DDD = (DDDD=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][dateObject.getDay()]).substring(0,3);
    th=(D>=10&&D<=20)?'th':((dMod=D%10)==1)?'st':(dMod==2)?'nd':(dMod==3)?'rd':'th';
    formatString = formatString.replace("#YYYY#",YYYY).replace("#YY#",YY).replace("#MMMM#",MMMM).replace("#MMM#",MMM).replace("#MM#",MM).replace("#M#",M).replace("#DDDD#",DDDD).replace("#DDD#",DDD).replace("#DD#",DD).replace("#D#",D).replace("#th#",th);

    h=(hhh=dateObject.getHours());
    if (h==0) h=24;
    if (h>12) h-=12;
    hh = h<10?('0'+h):h;
    AMPM=(ampm=hhh<12?'am':'pm').toUpperCase();
    mm=(m=dateObject.getMinutes())<10?('0'+m):m;
    ss=(s=dateObject.getSeconds())<10?('0'+s):s;
    return formatString.replace("#hhh#",hhh).replace("#hh#",hh).replace("#h#",h).replace("#mm#",mm).replace("#m#",m).replace("#ss#",ss).replace("#s#",s).replace("#ampm#",ampm).replace("#AMPM#",AMPM);
}
