Show:

File: ia\charts\CanvasBase.js

/**
 * <code>ia.CanvasBase</code> defines the basic layout behavior of chart or map.
 *
 * @author J Clare
 * @class ia.CanvasBase
 * @extends ia.CartesianSpace
 * @constructor
 * @param {String} id The id.
 */
ia.CanvasBase = function(id)
{
	ia.CanvasBase.baseConstructor.call(this, 0, 0, 100, 100);

	this._touchStart = 0;		// Timing of touch start.
	this._mouseDown = false; 	// Flag to indicate if the mouse is down. Used by the _mouseEventHandler() method.
	this._dragging = false;		// Flag to indicate if the mouse is being dragged. Used by the _mouseEventHandler() method.
	this._mouseDragged = false;	// Flag to indicate that the mouse was dragged. Used by the _mouseEventHandler() method.
	this._mouseOverMap = false;	// Flag to indicate if the mouse is over the map. Used by the _mouseEventHandler() method.
	this._pinching = false;		// Flag to indicate if the user is this._pinching the map. Used by the _mouseEventHandler() method.
	this._layers = new Array(); // Contains the map layers.
	this._mouseDownX = 0;		// Use these to test if the mouse has been dragged.
	this._mouseDownY = 0;

	this.id = id;
	this.isVisible = true;
	this.mouseX = 0;
	this.mouseY = 0;
	this.embeddedInGoogleMaps = false; 
	this.isDraggable = false;
	this.animationMode = false;

	// Create the container element.
	this.container = $j("<div id='"+id+"' class='ia-chart'>"); 

	this.mapContainer = $j("<div id='mapContainer'>"); 
	this.backgroundContainer = $j("<div id='backgroundContainer'>");
	this.layerContainer = $j("<div id='layercontainer'>")
	this.foregroundContainer = $j("<div id='foregroundContainer'>");
	
	this.mapContainer.append(this.backgroundContainer);
	this.mapContainer.append(this.layerContainer);
	this.mapContainer.append(this.foregroundContainer);
	this.container.append(this.mapContainer);

	// For styling map labels.
	this.labelStyle = $j("<div class='ia-map-labels' style='visibility:hidden'>");
	this.mapContainer.append(this.labelStyle);	
	
	this.canvas = this._createCanvas(this.backgroundContainer);
	this.context = this.canvas.getContext("2d");

	this.foregroundCanvas = this._createCanvas(this.foregroundContainer);
	this.foregroundContext = this.foregroundCanvas.getContext("2d");

	this.datatip = new ia.ChartTip(this.foregroundContainer);
	
	// Add listeners.
	this._addListeners();
	
	// Redraw the map on a resize - use a timeout to reduce number of redraws.
	var resizeTimeout;
	var mapReady = false;
	var me = this;
	this.container.resize(function(e) 
	{		
		clearTimeout(resizeTimeout);
		resizeTimeout = setTimeout(function()
		{
			clearTimeout(resizeTimeout);

			var w = me.container.width();
			var h = me.container.height();
			
			me.foregroundContainer.width(w);
			me.foregroundContainer.height(h);
						
			me.canvas.width = w;
			me.canvas.height = h;

			me.foregroundCanvas.width = w;
			me.foregroundCanvas.height = h;

			me.canvasX = 0;
			me.canvasY = 0;
			me.canvasWidth = w;
			me.canvasHeight = h;
			
			var e = new ia.Event(ia.Event.MAP_RESIZE, me);
			me.dispatchEvent(e);

			me.commitChanges();

			if (mapReady === false)
			{
				mapReady = true;
				var e = new ia.Event(ia.Event.MAP_READY, me);
				me.dispatchEvent(e);
			}

		}, 500);
	}.bind(this));
};
ia.extend(ia.CartesianSpace, ia.CanvasBase);

/**
 * Animation mode.
 *
 * @property animationMode
 * @type Boolean
 * @default false
 */
ia.CanvasBase.prototype.animationMode;

/**
 * The id.
 * 
 * @property id
 * @type String
 */
ia.CanvasBase.prototype.id;

/**
 * The canvas.
 * 
 * @property canvas
 * @type HTML Canvas
 */
ia.CanvasBase.prototype.canvas;

/**
 * The canvas context.
 * 
 * @property context
 * @type HTML Canvas Context
 */
ia.CanvasBase.prototype.context;

/**
 * The foreground canvas for drawing elements on top of the map/chart.
 * 
 * @property canvas
 * @type HTML Canvas
 */
ia.CanvasBase.prototype.foregroundCanvas;

/**
 * The foreground context for drawing elements on top of the map/chart.
 * 
 * @property context
 * @type HTML Canvas Context
 */
ia.CanvasBase.prototype.foregroundContext;

/**
 * The data tip.
 * 
 * @property datatip
 * @type ia.ChartTip
 */
ia.CanvasBase.prototype.datatip;

/**
 * The container that holds the object.
 * 
 * @property container
 * @type JQUERY Element
 */
ia.CanvasBase.prototype.container;

/**
 * The container that holds all the divs.
 * 
 * @property mapContainer
 * @type JQUERY Element
 */
ia.CanvasBase.prototype.mapContainer; 

/**
 * Containers the map canvas.
 * 
 * @property backgroundContainer
 * @type JQUERY Element
 */
ia.CanvasBase.prototype.backgroundContainer; 

/**
 * The container that holds the layers.
 * 
 * @property layerContainer
 * @type JQUERY Element
 */
ia.CanvasBase.prototype.layerContainer; 

/**
 * Containers selection / highlight canvas.
 * 
 * @property foregroundContainer
 * @type JQUERY Element
 */
ia.CanvasBase.prototype.foregroundContainer; 

/**
 * The chart visibility.
 * 
 * @property isVisible
 * @type Boolean
 * @default true
 */
ia.CanvasBase.prototype.isVisible;

/** 
 * The x coordinate of the mouse (pixel units) relative to the map origin.
 * 
 * @property mouseX
 * @type Number
 * @default 0
 */
ia.CanvasBase.prototype.mouseX;

/** 
 * The y coordinate of the mouse (pixel units) relative to the map origin.
 * 
 * @property mouseY
 * @type Number
 * @default 0
 */
ia.CanvasBase.prototype.mouseY;

/**
 * Indicates if embedded in google maps.
 * 
 * @property embeddedInGoogleMaps
 * @type Boolean
 * @default false
 */
ia.CanvasBase.prototype.embeddedInGoogleMaps; 

/**
 * Indicates if its draggable ie Its a map - used by touch events.
 * 
 * @property isDraggable
 * @type Boolean
 * @default false
 */
ia.CanvasBase.prototype.isDraggable;

/** 
 * Creates a new canvas.
 *
 * @method _createCanvas
 * @return {HTML Canvas} The canvas.
 * @private
 */
ia.CanvasBase.prototype._createCanvas = function(container)
{
	var canvas = document.createElement('canvas');
	canvas.width = container.width();
	canvas.height = container.height();
	$j(canvas).css({ position: 'absolute', left: 0, top: 0 });
	container.append($j(canvas));
	return canvas;
};

/** 
 * Adds the listeners.
 *
 * @method _addListeners
 * @private
 */
ia.CanvasBase.prototype._addListeners = function()
{
	if (ia.IS_TOUCH_DEVICE)
	{
		this.mapContainer.on("touchstart.canvasbase touchmove.canvasbase touchend.canvasbase", this._touchEventHandler.bind(this));
	}
	else
	{
		// Add mouse listeners.
		this.mapContainer.on("mousemove.canvasbase mouseup.canvasbase mouseenter.canvasbase mouseleave.canvasbase mousedown.canvasbase click.canvasbase", this._mouseEventHandler.bind(this)); 


		// FF doesn't recognize mousewheel as of FF3.x.
		var mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";

		// Mouse wheel for all other browsers.
		if(document.addEventListener) document.addEventListener(mousewheelevt, this._mouseEventHandler.bind(this), false);
	}
};

/**
 * Responsible for handling mouse events and then
 * dispatching the relevant map event. 
 * 
 * @method _mouseEventHandler
 * @param {MouseEvent} event Dispatched by the mouse.
 * @private
 */
ia.CanvasBase.prototype._mouseEventHandler = function(event)
{
	// Check if parent panel is visible.
	var isPanelVisible = true;
	var parentPanel = $j("#widget-"+this.id);
	if (parentPanel)
	{
		if (parentPanel.css("visibility") === "hidden") isPanelVisible = false;
	}

	if (this.isVisible && isPanelVisible)
	{
		var eventType;
	
		// Set the map mouse coords.
		this.mouseX = event.pageX - this.container.offset().left;
		this.mouseY = event.pageY - this.container.offset().top;
			
		// Check if mouse is over the map.
		// this handles when the map is resized and cusrsor is
		// on the map but an enter event wasnt fired.
		if ((this.mouseX > this.canvasX && this.mouseX < (this.canvasX + this.canvasWidth))
		&& (this.mouseY >  this.canvasY && this.mouseY < (this.canvasY + this.canvasHeight)))
		{
			this._mouseOverMap = true;
		}
		else
		{
			this._mouseOverMap = false;
		}

		// Mouse event logic.
		if (event.type === "mouseenter") 
		{
			eventType = ia.MapMouseEvent.MAP_MOUSE_OVER;
			if (this._dragging) $j(document).off(".canvasbasedoc");
			this._mouseOverMap = true;
		}
		else if (event.type === "mouseleave") 
		{	
			eventType = ia.MapMouseEvent.MAP_MOUSE_OUT;
			if (this._dragging) $j(document).on("mousemove.canvasbasedoc mouseup.canvasbasedoc",  this._mouseEventHandler.bind(this));
			this._mouseOverMap = false;
		}
		else if (event.type === "mousemove")
		{
			this._mouseOverMap = true;
			
			// If the mouse was held down and moved over the map it
			// becomes a ia.MapMouseEvent.MAP_MOUSE_DRAG event, otherwise its a ia.MapMouseEvent.MAP_MOUSE_MOVE  
			// event, as long as the mouse is over the map.
			// A ia.MapMouseEvent.MAP_MOUSE_DRAG event can continue after the mouse is 
			// dragged outside the map.

			if (this._mouseDown && (this._mouseDownX != event.pageX || this._mouseDownY != event.pageY))
			{
				eventType = ia.MapMouseEvent.MAP_MOUSE_DRAG;
				this._dragging = true;
			}
			else if (this._mouseOverMap)
			{
				eventType = ia.MapMouseEvent.MAP_MOUSE_MOVE;
			}
		}
		else if (event.type === "mouseup") 
		{
			if (this._dragging)
			{
				eventType = ia.MapMouseEvent.MAP_MOUSE_DRAG_UP;
				$j(document).off(".canvasbasedoc");
				this._dragging = false;
				this._mouseDragged = true;
			}
			else if (this._mouseOverMap) eventType = ia.MapMouseEvent.MAP_MOUSE_UP;
			this._mouseDown = false;
		}
		else if (event.type === "mousedown") 
		{			 
			eventType = ia.MapMouseEvent.MAP_MOUSE_DOWN;

			this._mouseDownX = event.pageX;
			this._mouseDownY = event.pageY;

			this._mouseDragged = false;
			this._mouseDown = true;
		}
		else if (event.type === "mousewheel" 
			|| event.type === "onmousewheel"
			|| event.type === "DOMMouseScroll") 
		{
			if (this._mouseOverMap) event.preventDefault();
			eventType = ia.MapMouseEvent.MAP_MOUSE_WHEEL;
		}
		else if (event.type === "click") 
		{
			if (this._mouseDragged === false) eventType = ia.MapMouseEvent.MAP_MOUSE_CLICK;

			// Fix for when google maps stops propagation of events during this._dragging.
			this._dragging = false;
			this._mouseDown = false;
			this._mouseDragged = false;
		}

		if (eventType != null)
		{	
			var dataX = this.getDataX(this.mouseX);
			var dataY = this.getDataY(this.mouseY);
			var e = new ia.MapMouseEvent(this, event, eventType, dataX, dataY, this.mouseX, this.mouseY, undefined, undefined, event.pageX, event.pageY);
			this.dispatchEvent(e);
		}
	}
};

/**
 * Responsible for handling touch events and then
 * dispatching the relevant map event. 
 * 
 * @method _touchEventHandler
 * @param {TouchEvent} event Dispatched by the mouse.
 * @private
 */
ia.CanvasBase.prototype._touchEventHandler = function(event)
{ 
	if (this.isVisible)
	{
		var eventType;
		if (!this.embeddedInGoogleMaps) event.preventDefault();

		var mx,my,mx2,my2;

		if (event.originalEvent.touches.length === 2)
		{
			var touch = event.originalEvent.touches[0];
			var touch2 = event.originalEvent.touches[1];

			mx = touch.pageX - this.container.offset().left;
			my = touch.pageY - this.container.offset().top;
			mx2 = touch2.pageX - this.container.offset().left;
			my2 = touch2.pageY - this.container.offset().top;

			if (event.type === "touchstart") 
			{		
				eventType = ia.MapMouseEvent.MAP_PINCH_DOWN;
			}
			else if (event.type === "touchmove")
			{
				eventType = ia.MapMouseEvent.MAP_PINCH_MOVE;
				this._pinching = true;
			}
		}	
		else
		{
			var touch = event.originalEvent.changedTouches[0];

			// Get the map mouse coords.
			mx = touch.pageX - this.container.offset().left;
			my = touch.pageY - this.container.offset().top;
			mx2 = null;
			my2 = null; 

			if (event.type === "touchstart") 
			{			 
				this._touchStart = new Date().getTime();
				eventType = ia.MapMouseEvent.MAP_MOUSE_DOWN;
			}
			else if (event.type === "touchmove")
			{
				// One finger left down after pinch so convert to drag.
				if (this._pinching)
				{
					this.mouseX = mx;
					this.mouseY = my;

					mouseDownCoords = this.mouseCoords(); 
					mouseDownPixels = new ia.Point(this.mouseX, this.mouseY);

					this._pinching = false;
					this._dragging = true;
					doPan = true;
				}
				// Reduce sensitivity, so we can have a mouse click.
				else 
				{
					if (this.isDraggable) eventType = ia.MapMouseEvent.MAP_MOUSE_DRAG;
					else eventType = ia.MapMouseEvent.MAP_MOUSE_MOVE;
					this._dragging = true;
				}
			}
			else if (event.type === "touchend") 
			{
				var tEnd = new Date().getTime();
				var touchTime = tEnd - this._touchStart;

				if (this._dragging)
				{
					if (this.isDraggable) eventType = ia.MapMouseEvent.MAP_MOUSE_DRAG_UP;
					else eventType = ia.MapMouseEvent.MAP_MOUSE_UP;
					this._dragging = false;
				}
				else if (this._pinching)
				{
					eventType = ia.MapMouseEvent.MAP_PINCH_UP;
					if (event.originalEvent.touches.length === 0) this._pinching = false;
				}
				else if (touchTime < 500)
				{
					eventType = ia.MapMouseEvent.MAP_MOUSE_CLICK;
				}
				else
				{
					eventType = ia.MapMouseEvent.MAP_MOUSE_UP;
				}
			}
		}

		if (eventType != null)
		{	
			if (mx2 != null)
			{
				//this.mouseX = Math.min(mx, mx2) + (Math.abs(mx - mx2)/2);
				//this.mouseY = Math.min(my, my2) + (Math.abs(my - my2)/2);
				this.mouseX = Math.min(mx, mx2);
				this.mouseY = Math.min(my, my2);
			}
			else
			{
				this.mouseX = mx;
				this.mouseY = my;
			}
			var dataX = this.getDataX(this.mouseX);
			var dataY = this.getDataY(this.mouseY);
			var e = new ia.MapMouseEvent(this, event, eventType, dataX, dataY, mx, my, mx2, touch.pageX, touch.pageX);
			this.dispatchEvent(e);
		}
	}
};

/** 
 * Returns the cursor position in data units.
 * 
 * @method mouseCoords
 * @return {ia.Point} The cursor position (data units).
 */
ia.CanvasBase.prototype.mouseCoords = function()  
{
	var dataX = this.getDataX(this.mouseX);
	var dataY = this.getDataY(this.mouseY);
	var p = new ia.Point(dataX, dataY);
	return p;
};

/** 
 * Exports as an image in a new page.
 * 
 * @method exportData
 * @param {String} txt Any text to go with image.
 */
ia.CanvasBase.prototype.exportData = function(txt)
{
	for (var i = 0; i < this._layers.length; i++) 
	{
		var layer = this._layers[i];

		if (layer.geometry !== "image")
		{
			this.context.drawImage(layer.canvas,0,0);
			if (layer.selectionCanvas) this.context.drawImage(layer.selectionCanvas,0,0);
			if (layer.labelCanvas) this.context.drawImage(layer.labelCanvas,0,0);
		}
	}

	/*var dataURL = this.canvas.toDataURL("image/png");
	window.open(dataURL, "_blank");*/
	
	var dataURL = this.canvas.toDataURL("image/png")
	window.open().document.write('<div style="font-family:Verdana;font-size:12px;color:#888888;">'+txt+'</div><p><img style="border-width:1px;border-style:solid;border-color:#DCDCDC;" src="'+dataURL+'"/></p>');
	
	this.clear();
	this.render();
};

/** 
 * Clears and renders the map.
 *
 * @method render
 */
ia.CanvasBase.prototype.render = function()
{
	this.datatip.hide();
	for (var i = 0; i < this._layers.length; i++) {this._layers[i].render();}
};

/** 
 * Clears the canvas.
 *
 * @method clear
 */
ia.CanvasBase.prototype.clear = function()  
{
	this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
	for (var i = 0; i < this._layers.length; i++) {this._layers[i].clear();}
};

/** 
 * Clears the foreground canvas.
 *
 * @method clear
 */
ia.CanvasBase.prototype.clearForeground = function()  
{
	this.foregroundContext.clearRect(0, 0, this.foregroundCanvas.width, this.foregroundCanvas.height);
};

/** 
 * Adds a layer to the map.
 * 
 * @method addLayer
 * @param {ia.LayerBase} layer The layer to add.
 */
ia.CanvasBase.prototype.addLayer = function(layer) 
{
	this._layers.push(layer);
	layer.setMap(this, this.layerContainer);
};

/** 
 * Gets the map layers.
 * 
 * @method getLayers
 * @return {ia.LayerBase[]} The layers in the map.
 */
ia.CanvasBase.prototype.getLayers = function() 
{
	return this._layers;
};

/** 
 * Removes all layers from the map.
 * 
 * @method removeLayers
 */
ia.CanvasBase.prototype.removeLayers = function() 
{
	for (var i = 0; i < this._layers.length; i++)  
	{
		var layer = this._layers[i];
		layer.removeCanvases();
		this.removeListener(layer);
	}
	this._layers = new Array();
};

/** 
 * Gets the map layer with the given id.
 * 
 * @method getLayer
 * @param {String} id The layer id.
 * @return {ia.LayerBase} The layer, or undefined.
 */
ia.CanvasBase.prototype.getLayer = function(id) 
{
	for (var i = 0; i < this._layers.length; i++)  
	{
		var layer = this._layers[i]
		if (layer.id === id) return layer;
	}
	return undefined;
};

/** 
 * Starts a map drag.
 * 
 * @method startDrag
 */
ia.CanvasBase.prototype.startDrag = function()  
{
	// Fix for when google maps stops propagation of events during this._dragging.
	this._dragging = true;
};

/** 
 * Ends a map drag.
 * 
 * @method endDrag
 */
ia.CanvasBase.prototype.endDrag = function()  
{
	$j(document).off(".canvasbasedoc");
	this._dragging = false;
	this._mouseDragged = true;
	this._mouseDown = false;
};

/**
 * Toggles the chart visibility.
 * 
 * @method toggle
 */
ia.CanvasBase.prototype.toggle = function(visible)
{
	if (this.isVisible) this.hide();
	else this.show();
};

/**
 * Hides the chart.
 * 
 * @method hide
 */
ia.CanvasBase.prototype.hide = function()
{
	if (this.isVisible)
	{
		var me = this;
		me.isVisible = false;
		me.container.stop();
		me.container.animate({opacity: 0}, function() {me.clear();});
	}
};

/**
 * Shows the chart.
 * 
 * @method show
 */
ia.CanvasBase.prototype.show = function()
{
	if (!this.isVisible)
	{
		this.isVisible = true;
		this.container.stop();
		this.container.animate({opacity: 1}, function() {});
	}
};