function Spread(base, x, y, displayNumber, types) {

	this.rand = function (low, high) {
		return low + Math.floor(Math.random() * ((high + 1) - low));
	}

	var self = this;

	this.base = base;
	if (base == undefined) {
		this.log("You must pass the base object (div) as the first parameter of the class.");
	}
	if (displayNumber < 0) {
		this.log("You must pass the base object (div) as the first parameter of the class.");
	}

	this.bucketId = "spreadBucket"+ this.rand(100000, 999999);
	this.canvasId = "canvas"+ this.rand(100000, 999999);
	this.bucket;
	this.satellites;
	this.labels;
	this.displayNumber = displayNumber;
	if (this.displayNumber > 20) {
		this.countSatellites = 20;
	} else {
		this.countSatellites = this.displayNumber;
	}
	this.types = types;
	this.radius;
	this.mouseOutRadius;
	this.base;
	this.satellitesAreOpen;
	this.satellitesAreOpening;
	this.satelliteAnims;
	this.linesTimeout;
	this.radiusMinimum = 30;
	this.radiusInterval = 2;
	this.staggerMinimum = 12;
	this.staggerRadiusMultiplier = .7;
	this.mouseOutRadiusExtention = 20;
	this.satelliteWidth = 23;
	this.satelliteHeight = 25;
	this.satelliteListVerticalSpacing = 0;
	this.baseWidth = 30;
	this.baseHeight = 30;
	this.satellitesArrangement;
	this.baseX;
	this.baseY;
	this.linesListWidthAdjustment = 2;
	this.spreadAnimationTime = .4;
	this.listAnimationTime = .2;
	this.hideAnimationTime = .1;
	this.globalInstanceIndex;
	this.nearbyBaseOpacity = .3;
	this.listBackgroundDiv;
	this.satellitesHaveBeenCreated = false;

	var offsetX, offsetY, targetX, targetY, targetPoint, degrees, radians, startX, startY, radiusInternal, i, intervalEndX, intervalEndY;

	this.load = function (x, y) {
		if (!this.globalInstanceIndex) {
			if (_spreadInstancesCurrentlyOpen && _spreadInstancesCurrentlyOpen instanceof Array) {
				// do nothing
			} else {
				_spreadInstancesCurrentlyOpen = new Array();
			}
		}
		this.loadBase(x, y);
	}

	this.spread = function () {
		this.satellitesAreOpening = true;
		window.setTimeout(function () { self.satellitesAreOpening = false; }, 100);
		this.hideOtherInstanceSatellites();

		if (this.satellitesHaveBeenCreated != true) {
			var html = "";


			// count the total of the types
			var total = 0;
			for (t in this.types) {
				total += parseInt(this.types[t]);
			}

			// figure out how many to show
			var totalToShow = total;
			if (totalToShow > 20) {
				totalToShow = 20;
			}

			// calculate the percentages, multiply by totalToShow, and find the type with the highest number
			var percentages = new Object();
			var showCountForTypes = new Object();
			var typeWithHighestPercentage;
			for (t in this.types) {
				percentages[t] = (parseInt(this.types[t]) / total);
				showCountForTypes[t] = Math.round(percentages[t] * totalToShow);
				if (typeWithHighestPercentage == undefined) {
					typeWithHighestPercentage = t;
				} else if (parseInt(this.types[typeWithHighestPercentage]) < this.types[t]) {
					typeWithHighestPercentage = t;
				}
			}

			// bundle them up into an array, one entry per count of each type
			var arrayOfTypes = Array();
			var x = 0;
 			for (t in showCountForTypes) {
	 			for (z = 0; z < showCountForTypes[t]; z++) {
					arrayOfTypes[x] = t;
					x++;
				}
				//log("Show "+ showCountForTypes[t] +" for t = "+ t);
			}


			for (i = 0; i < this.countSatellites; i++) {
				html += this.satelliteWithIndexHTML(i, arrayOfTypes[i]); // add pinId, label
			}
			this.bucket.innerHTML += html;
			for (i = 0; i < this.countSatellites; i++) {
				this.satellites[i] = document.getElementById(this.bucketId +"_satellite"+ i);
				this.satellites[i].style.width = this.satelliteWidth +"px";
				this.satellites[i].style.height = this.satelliteHeight +"px";
				this.satellites[i].onselectstart = function() { return(false); };
				//this.labels[i] = document.getElementById("label"+ i);
				//this.labels[i].onselectstart = function() { return(false); };
			}
			this.satellitesHaveBeenCreated = true;
		}


		if (!this.satellitesAreOpen) {
			this.globalInstanceIndex = _spreadInstancesCurrentlyOpen.push(this) - 1;
			this.satellitesArrangement = "spread";
			/*
		    YAHOO.util.Event.on(window, 'mousemove', function(e) {
				var baseFixedPositionX = this.findPosX(self.base);
				var baseFixedPositionY = this.findPosY(self.base);
				var distance = Math.sqrt(Math.pow(e.pageX - baseFixedPositionX, 2) + Math.pow(e.pageY - baseFixedPositionX, 2));
				this.log("distance is "+ distance);
		        if (distance > self.mouseOutRadius) {
					self.hideSatellites();
				}
		    });
			*/

			// dim nearby spread bases
			for (var i in spreads) {
				if (spreads[i] != undefined) {
					if (spreads[i] != this) {
						var distance = Math.sqrt(Math.pow(spreads[i].baseX - self.baseX, 2) + Math.pow(spreads[i].baseY - self.baseY, 2));
				        if (distance < self.mouseOutRadius + (this.baseWidth / 2)) {
							spreads[i].base.style.opacity = this.nearbyBaseOpacity;
						}
					}
				}
			}

			this.satellitesAreOpen = true;
			startX = this.findPosX(this.base) + (this.baseWidth / 2);
			startY = this.findPosY(this.base) + (this.baseHeight / 2);
			this.base.style.zIndex = 200;
			for (i = 0; i < this.satellites.length; i++) {
				radiusInternal = this.radius;
				if (this.satellites.length >= this.staggerMinimum && i % 2 != 0) {
					radiusInternal = this.radius * this.staggerRadiusMultiplier;
				}
				degrees = (360 / this.satellites.length) * i;
				radians = this.radiansWithDegrees(degrees);
				offsetX = radiusInternal * Math.cos(radians);
				offsetY = radiusInternal * Math.sin(radians);
				targetX = startX + offsetX - (this.satelliteWidth / 2);
				targetY = startY + offsetY - (this.satelliteHeight / 2);
				if (this.satelliteAnims[i]) {
					this.satelliteAnims[i].stop();
				}
			    this.satelliteAnims[i] = new YAHOO.util.Anim(this.satellites[i], {
			        left: { from: startX, to: targetX }
			        ,top: { from: startY, to: targetY }
			    }, this.spreadAnimationTime, YAHOO.util.Easing.bounceOut);
				this.satellites[i].style.display = "block";
				this.satellites[i].style.zIndex = 199;
				this.satelliteAnims[i].animate();
			}
			this.linesTimeout = window.setTimeout(function () { self.drawLinesSpread(); }, (this.spreadAnimationTime / 2) * 1000);
		}
	}

	this.drawLinesSpread = function () {
		if (this.satellitesArrangement == "spread") {
			var canvas = this.canvasElement();
			canvas.style.top = (this.baseY - this.radius) +"px";
			canvas.style.left = (this.baseX - this.radius) +"px";
			canvas.width = this.radius * 2;
			canvas.height = this.radius * 2;
			if (canvas.getContext){
				var ctx = canvas.getContext('2d');
				for (i = 0; i < this.countSatellites; i++) {
					ctx.beginPath();
					ctx.moveTo(this.radius, this.radius);

					radiusInternal = this.radius - (this.satelliteWidth / 2);
					if (this.satellites.length >= this.staggerMinimum && i % 2 != 0) {
						radiusInternal = this.radius * this.staggerRadiusMultiplier;
					}
					degrees = Math.floor((360 / this.satellites.length) * i);
					radians = this.radiansWithDegrees(degrees);
					offsetX = radiusInternal * Math.cos(radians);
					offsetY = radiusInternal * Math.sin(radians);
					targetX = this.radius + offsetX;
					targetY = this.radius + offsetY;

					ctx.lineTo(targetX, targetY);
					ctx.closePath();
					ctx.strokeStyle = "rgba(255, 255, 255, .5)";
					ctx.stroke();

					var adjustX = 0;
					var adjustY = 0;
					if (degrees >= 270) {
						adjustX = 1;
						adjustY = 1;
					} else if (degrees >= 180) {
						adjustX = 1;
						adjustY = -1;
					} else if (degrees >= 90) {
						adjustX = -1;
						adjustY = -1;
					} else {
						adjustX = -1;
						adjustY = 1;
					}
					ctx.beginPath();
					ctx.moveTo(this.radius + adjustX, this.radius + adjustY);
					ctx.lineTo(targetX + adjustX, targetY + adjustY);
					ctx.closePath();
					ctx.strokeStyle = "rgba(0, 0, 0, .5)";
					ctx.stroke();
				}
			} else {
				this.log("Your browser does not support the canvas element.");
			}
		}
	}

	this.listBackground = function () {
		if (!this.listBackgroundDiv) {
			var o = document.getElementById(this.bucketId +"_back");
			if (o == undefined) {
				var o = document.createElement(this.bucketId +"_back");
				o.style.position = "absolute";
				o.style.top = "0px";
				o.style.left = "0px";
				o.style.width = "0px";
				o.style.height = "0px";
				o.style.background = "#000000";
				o.style.opacity = ".85";
				o.style.zIndex = "198";
				this.bucket.appendChild(o);
			}
			this.listBackgroundDiv = o;
		}
		return this.listBackgroundDiv;
	}

	this.clearListBackground = function () {
		if (this.listBackgroundDiv) {
			this.listBackgroundDiv.style.top = "0px";
			this.listBackgroundDiv.style.left = "0px";
			this.listBackgroundDiv.style.width = "0px";
			this.listBackgroundDiv.style.height = "0px";
		}
	}

	this.list = function () {
		this.satellitesAreOpening = true;
		window.setTimeout(function () { self.satellitesAreOpening = false; }, 100);
		this.clearCanvas();
		//YAHOO.util.Event.removeListener(window, 'mousemove');
		this.satellitesArrangement = "list";
		this.undimAllSpreadBases();
		startY = this.findPosY(this.base) + (this.baseHeight / 2) - (((this.satellites.length) * (this.satelliteHeight + this.satelliteListVerticalSpacing)) / 2) + (this.satelliteListVerticalSpacing / 2);
		if (startY < 0 + this.satelliteHeight + this.satelliteListVerticalSpacing) {
			startY = this.satelliteHeight + this.satelliteListVerticalSpacing;
		}
		targetX = this.findPosX(this.base) + (this.baseWidth * 1.5);

		this.listBackground();
		this.listBackgroundDiv.style.top = (startY - 3) +"px";
		this.listBackgroundDiv.style.left = (targetX - 4) +"px";
		this.listBackgroundDiv.style.width = "200px";
		this.listBackgroundDiv.style.height = ((this.satellites.length * (this.satelliteHeight + this.satelliteListVerticalSpacing)) + 6) +"px";

		for (i = 0; i < this.satellites.length; i++) {
			targetY = startY + (i * (this.satelliteHeight + this.satelliteListVerticalSpacing));
			if (this.satelliteAnims[i]) {
				this.satelliteAnims[i].stop();
			}
		    this.satelliteAnims[i] = new YAHOO.util.Anim(this.satellites[i], {
		        left: { to: targetX }
		        ,top: { to: targetY }
		    }, this.listAnimationTime, YAHOO.util.Easing.easeNone);
			this.satelliteAnims[i].i = i;
			this.satelliteAnims[i].labelX = targetX + this.satelliteWidth + this.satelliteListVerticalSpacing;
			this.satelliteAnims[i].labelY = targetY;
			this.satelliteAnims[i].onComplete.subscribe(function() {
				if (this.i == self.satellites.length - 1) {
					self.drawLinesList(startY, targetX - self.baseX);
				}
			});
			this.satelliteAnims[i].animate();
			//self.labels[i].style.left = this.satelliteAnims[i].labelX +"px";
			//self.labels[i].style.top = this.satelliteAnims[i].labelY +"px";
			//self.labels[i].style.display = "block";
		}
		/*
	    YAHOO.util.Event.on(window, 'mousemove', function(e) {
	        if (e.pageX < self.baseX - self.baseWidth || e.pageX > self.baseX + 240 || e.pageY < startY || e.pageY > startY + (self.satellites.length * (self.satelliteHeight + self.satelliteListVerticalSpacing))) {
				self.hideSatellites();
			}
	    });
		*/
	}

	this.drawLinesList = function (startY, width) {
		var canvas = this.canvasElement();
		canvas.style.top = (startY + (this.satelliteHeight / 2)) +"px";
		canvas.style.left = this.baseX +"px";
		canvas.width = width + this.linesListWidthAdjustment;
		canvas.height = ((this.satelliteHeight + this.satelliteListVerticalSpacing) * this.countSatellites);
		if (canvas.getContext){
			var ctx = canvas.getContext('2d');
			ctx.strokeStyle = "rgba(255, 255, 255, .4)";
			for (i = 0; i < this.countSatellites; i++) {
				ctx.beginPath();
				ctx.moveTo(0, this.baseY - startY - (this.satelliteHeight / 2));
				ctx.lineTo(width + this.linesListWidthAdjustment, i * (this.satelliteHeight + this.satelliteListVerticalSpacing));
				ctx.closePath();
				ctx.stroke();
			}
		} else {
			this.log("Your browser does not support the canvas element.");
		}
	}

	this.loadBase = function (x, y) {
		this.clearCanvas();
		this.satellites = new Array();
		this.labels = new Array();
		this.radius = this.radiusMinimum + (this.countSatellites * this.radiusInterval);
		this.mouseOutRadius = this.radius + this.mouseOutRadiusExtention;
		this.satellitesAreOpen = false;
		this.satelliteAnims = new Array();
		//this.base.style.top = this.rand(100, 500) +"px";
		//this.base.style.left = this.rand(200, 700) +"px";
		//this.log("setting top left "+ x +" "+ y);
		this.base.innerHTML = this.displayNumber;
		if (parseInt(this.displayNumber) > 99999) {
			this.base.className = "base digits6";
			this.baseWidth = 50;
		} else if (parseInt(this.displayNumber) > 9999) {
			this.base.className = "base digits5";
			this.baseWidth = 44;
		} else if (parseInt(this.displayNumber) > 999) {
			this.base.className = "base digits4";
			this.baseWidth = 40;
		} else if (parseInt(this.displayNumber) > 99) {
			this.base.className = "base digits3";
			this.baseWidth = 34;
		}
		this.base.style.top = (y - (this.baseHeight / 2)) +"px";
		this.base.style.left = (x - (this.baseWidth / 2)) +"px";
		this.base.onselectstart = function() { return(false); };
		this.baseX = this.findPosX(this.base) + (this.baseWidth / 2);
		this.baseY = this.findPosY(this.base) + (this.baseHeight / 2);
		//var microdate1 = new Date;
		//var microtime1 = microdate1.getTime();
		this.createBucket();
		this.bucket.innerHTML = "";
		//var microdate2 = new Date;
		//var microtime2 = microdate2.getTime();
		//this.log(microtime2 - microtime1);
		//YAHOO.util.Event.removeListener(this.base, 'mouseover');
		YAHOO.util.Event.removeListener(this.base, 'mouseup');
		//YAHOO.util.Event.removeListener(window, 'mousemove');
		//YAHOO.util.Event.on(this.base, 'mouseover', function () {
		//	self.spread();
		//});
	    YAHOO.util.Event.on(this.base, 'mouseup', function() {
			if (self.satellitesArrangement == "spread") {
				//self.list(); // let's skip the list for now.
				self.hideSatellites();
			} else if (self.satellitesArrangement == "list") {
				self.hideSatellites();
			} else {
				if (self.satellitesAreOpen) {
					self.hideSatellites();
				} else {
					self.spread();
				}
			}
			return false;
		});
	}

	this.satelliteWithIndex = function (i) {
		if (i > -1) {
			if (!this.satellites[i]) {
				//this.createBucket();
				this.bucket.innerHTML += "<div id=\""+ this.bucketId +"_satellite"+ i +"\" class=\"satellite\"></div>";
				this.satellites[i] = document.getElementById(this.bucketId +"_satellite"+ i);
				//this.satellites[i] = document.createElement('div');
				//this.satellites[i].id = this.bucketId +"_satellite"+ i;
				//this.satellites[i].className = "satellite";
				//this.bucket.appendChild(this.satellites[i]);
			}
		}
		return this.satellites[i];
	}

	this.satelliteWithIndexHTML = function (i, type, pinId, label) {
		var html = "";
		if (i > -1) {
			if (!this.satellites[i]) {
				//getInfoWindowHtml(point, checkedNauticalMarkers, lastImageTileMarker);
				html += "<div id=\""+ this.bucketId +"_satellite"+ i +"\" class=\"satellite markerType"+ type +"\" onClick=\"processSatelliteClick('"+ this.baseX +"', '"+ this.baseY +"', this.style.left.replace(/px/, ''), this.style.top.replace(/px/, ''), '"+ type +"', '"+ i +"');\"></div>";
			}
		}
		return html;
	}

	this.createBucket = function () {
		if (!this.bucket) {
			this.bucket = document.getElementById(this.bucketId);
			if (!this.bucket) {
				//document.body.innerHTML += "<div id=\""+ this.bucketId +"\"></div>";
				//this.bucket = document.getElementById(this.bucketId);
				this.bucket = document.createElement('div');
				this.bucket.id = this.bucketId;
				document.body.appendChild(this.bucket);
			}
		}
		return this.bucket;
	}

	this.hideOtherInstanceSatellites = function () {
		if (_spreadInstancesCurrentlyOpen) {
			for (i = 0; i < _spreadInstancesCurrentlyOpen.length; i++) {
				if (i != this.globalInstanceIndex && _spreadInstancesCurrentlyOpen[i] != false) {
					_spreadInstancesCurrentlyOpen[i].hideSatellites();
				}
			}
		}
	}

	this.undimAllSpreadBases = function () {
		// undim all spread bases
		for (var i in spreads) {
			if (spreads[i] != undefined) {
				spreads[i].base.style.opacity = "1";
			}
		}
	}

	this.hideSatellites = function () {
		_spreadInstancesCurrentlyOpen[this.globalInstanceIndex] = false;
		this.globalInstanceIndex = false;
		//YAHOO.util.Event.removeListener(window, 'mousemove');
		this.satellitesAreOpen = false;
		this.satellitesArrangement = "";
		this.undimAllSpreadBases();
		var endX = this.findPosX(this.base) + (this.baseWidth / 2);
		var endY = this.findPosY(this.base) + (this.baseHeight / 2);
		this.base.style.zIndex = 190;
		window.clearTimeout(this.linesTimeout);
		for (i = 0; i < this.satellites.length; i++) {
			//this.labels[i].style.display = "none";
			this.satellites[i].style.zIndex = 189;
			intervalEndX = endX - (this.satelliteWidth / 2);
			intervalEndY = endY - (this.satelliteHeight / 2);
			var anim = new YAHOO.util.Anim(this.satellites[i], {
				left: { to: intervalEndX }
				,top: { to: intervalEndY }
			}, this.hideAnimationTime);
			anim.onComplete.subscribe(this.hideSatellite);
			if (this.satellites[i].style.top == "-100px") {
				this.hideSatellite(this.satellites[i]);
			} else {
				anim.animate();
			}
			this.satelliteAnims[i].stop();
		}
		this.clearCanvas();
		this.clearListBackground();
	}

	this.hideSatellite = function (o) {
		var el;
		if (self.satellitesArrangement == "" && self.satellitesAreOpen == false) {
			if (o.id != undefined) {
				el = o;
			} else {
				el = this.getEl();
			}
			el.style.display = "none";
			el.style.top = "-100px";
			el.style.left = "-100px";
		}
	}

	this.findPosX = function (obj) {
		if (obj.style.position == "absolute") {
			return parseInt(obj.style.left.replace(/px/g,""));
		}
		var curleft = 0;
		if(obj.offsetParent)
			while(1) {
				curleft += obj.offsetLeft;
				if(!obj.offsetParent)
					break;
				obj = obj.offsetParent;
			} else if(obj.x)
				curleft += obj.x;
		return curleft;
	}

	this.findPosY = function (obj) {
		if (obj.style.position == "absolute") {
			return parseInt(obj.style.top.replace(/px/g,""));
		}
		var curtop = 0;
		if(obj.offsetParent)
			while(1) {
				curtop += obj.offsetTop;
				if(!obj.offsetParent)
					break;
				obj = obj.offsetParent;
			} else if(obj.y)
				curtop += obj.y;
		return curtop;
	}

	this.radiansWithDegrees = function (degrees) {
		return (Math.PI/180) * degrees;
	}

	this.clearCanvas = function () {
		var canvas = document.getElementById(this.canvasId);
		if (canvas) {
			canvas.parentNode.removeChild(canvas);
		}
	}

	this.canvasElement = function () {
		this.clearCanvas();
		var canvasBucket = document.getElementById("canvas-bucket");
		var canvas = document.createElement("canvas");
		canvas.id = this.canvasId;
		canvas.className = "canvas";
		canvas.style.zIndex = 197;
		canvasBucket.appendChild(canvas);
		return canvas;
	}

	this.log = function (str) {
		/*
		if (console != undefined) {
			console.log(str);
		} else if (window.console != undefined) {
			window.console.log(str);
		}
		*/
	}

	this.destroy = function () {
		if (this.base) {
			if (this.base.parentNode) {
				this.base.parentNode.removeChild(this.base);
			}
		}
		if (this.bucket) {
			if (this.bucket.parentNode) {
				this.bucket.parentNode.removeChild(this.bucket); // removing bucket also removes child satellites and list background
			}
		}
		var canvas = document.getElementById(this.canvasId);
		if (canvas) {
			if (canvas.parentNode) {
				canvas.parentNode.removeChild(canvas);
			}
		}
	}

	return this.load(x, y);
}