Show:

File: ia\charts\layers\PlotLayer.js

/** 
 * The base class for scatter plot layers.
 *
 * @author J Clare
 * @class ia.PlotLayer
 * @extends ia.ItemLayer
 * @constructor
 */
ia.PlotLayer = function()
{
	ia.PlotLayer.baseConstructor.call(this);

	this._xData = new Object();
	this._yData = new Object();

	this.style = {fillStyle:'#ffffff', strokeStyle:'#888888', lineWidth:'2', lineJoin:'round'};
	this.xDataField = "value";
	this.yDataField = "value";
	this.correlationInfo = {};
	this.showCorrelationLine = false;
	this.pointSize = 6;
};
ia.extend(ia.ItemLayer, ia.PlotLayer);

/** 
 * The layer style.
 *
 * @property style
 * @type Object
 * @default {fillStyle:'#ffffff', strokeStyle:'#888888', lineWidth:'2', lineJoin:'round'}
 */
ia.PlotLayer.prototype.style;

/** 
 * The min x value.
 *
 * @property xMinValue
 * @type Number
 */
ia.PlotLayer.prototype.xMinValue;

/** 
 * The max x value.
 *
 * @property xMaxValue
 * @type Number
 */
ia.PlotLayer.prototype.xMaxValue;

/** 
 * The min y value.
 *
 * @property yMinValue
 * @type Number
 */
ia.PlotLayer.prototype.yMinValue;

/** 
 * The max y value.
 *
 * @property yMaxValue
 * @type Number
 */
ia.PlotLayer.prototype.yMaxValue;

/**
 * Specifies the field of the data provider that provides the x values.
 *
 * @property xDataField
 * @type String
 * @default "value"
 */
ia.PlotLayer.prototype.xDataField;

/**
 * Specifies the field of the data provider that provides the y values.
 *
 * @property yDataField
 * @type String
 * @default "value"
 */
ia.PlotLayer.prototype.yDataField;

/**
 * Correlation info.
 * 
 * An object with the properties o.correlationCoeff, o.rSquare, o.gradient, o.intercept or null.
 *
 * @property correlationInfo
 * @type Object
 */
ia.PlotLayer.prototype.correlationInfo;

/**
 * Should the correlation line be displayed.
 *
 * @property showCorrelationLine
 * @type Boolean
 * @default false
 */
ia.PlotLayer.prototype.showCorrelationLine;

/**
 * The point size.
 *
 * @property pointSize
 * @type Number
 * @default 6
 */
ia.PlotLayer.prototype.pointSize;

/**
 * Gets a data object for the x-axis.
 *
 * @method getXData
 * @return ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.getXData = function()
{
	return this._xData;
};

/**
 * Sets a data object for the x-axis.
 *
 * @method setXData
 * @param value ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.setXData = function(value)
{
	this._xData = value;
	this.dataChanged = true;
};

/**
 * Gets a data object for the y-axis.
 *
 * @method getYData
 * @return ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.getYData = function()
{
	return this._yData;
};

/**
 * Sets a data object for the y-axis.
 *
 * @method setYData
 * @param value ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.setYData = function(value)
{
	this._yData = value;
	this.dataChanged = true;
};

/**
 * Gets a data object for the color.
 *
 * @method getColorData
 * @return ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.getColorData = function()
{
	return this._colorData;
};

/**
 * Sets a data object for the color.
 *
 * @method setColorData
 * @return ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.setColorData = function(value)
{
	this._colorData = value;
	this.dataChanged = true;
};

/**
 * Gets a data object for the size.
 *
 * @method getSizeData
 * @return ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.getSizeData = function()
{
	return this._sizeData;
};

/**
 * Sets a data object for the size.
 *
 * @method setSizeData
 * @return ["eh11"]{id:"eh11", name:"polwarth", value:2345, associate1:25}
 * <br/>["eh12"]{id:"eh12", name:"morningside", value:4347, associate1:45}
 * <br/>["eh13"]{id:"eh13", name:"merchiston", value:2496, associate1:25}
 */
ia.PlotLayer.prototype.setSizeData = function(value)
{
	this._sizeData = value;
	this.dataChanged = true;
};

/**
 * Updates the data.
 *
 * @method update
 */
ia.PlotLayer.prototype.update = function() 
{
	if (this.map && this.dataChanged)
	{
		// Clear the items.
		this.itemArray = [];
		this.clearItems();
		
		this.xMinValue = Infinity;
		this.xMaxValue = -Infinity;
		this.yMinValue = Infinity;
		this.yMaxValue = -Infinity;
		
		// Correlation Start.
		var x = 0;
		var sumOfX = 0;
		var sumOfXSquared = 0;
		var xSquared = 0;

		var y = 0;
		var sumOfY = 0;
		var sumOfYSquared = 0;
		var ySquared = 0;

		var xy = 0;
		var sumOfXY = 0;
		var sampleNo = 0;
		// Correlation End.

		// Loop through the data.
		for (var id in this._xData)
		{
			// Get the data item.
			var xDataItem = this._xData[id];
			var yDataItem = this._yData[id];
			
			// Has to be a number to be displayed in a bar chart
			var xValue = xDataItem[this.xDataField];
			var yValue = yDataItem[this.yDataField];
			if (ia.isNumber(xValue) && ia.isNumber(yValue)) 
			{	
				xValue = parseFloat(xValue);
				yValue = parseFloat(yValue);

				// Create a new chart item.
				var chartItem =  {};
				chartItem.id = xDataItem.id;
				chartItem.name = xDataItem.name;
				chartItem.xValue = xValue;
				chartItem.yValue = yValue;

				if (this._colorData !== undefined) 
				{
					var colorDataItem = this._colorData[id];
					chartItem.color = colorDataItem.color;
				}
				else chartItem.color = xDataItem.color;
				if (this._sizeData !== undefined)
				{
					var sizeDataItem = this._sizeData[id];
					chartItem.symbolSize = sizeDataItem.symbolSize;
				}
				else chartItem.symbolSize = this.pointSize;

				chartItem.state = ia.ItemLayer.UNSELECTED;
				if (this.selectionIds.indexOf(chartItem.id) !== -1 ) chartItem.state = ia.ItemLayer.SELECTED;
				chartItem.parent = this;
				chartItem.layer = this;

				chartItem.shape = new ia.Rectangle();
				chartItem.hitArea = new ia.Rectangle();
	
				this.items[id] = chartItem;
				this.itemArray.push(chartItem);
				
				// Correlation Start.
				xSquared = xValue * xValue;
				sumOfX = sumOfX + xValue;
				sumOfXSquared = sumOfXSquared + xSquared;

				ySquared = yValue * yValue;
				sumOfY = sumOfY + yValue;
				sumOfYSquared = sumOfYSquared + ySquared;

				xy = xValue * yValue;
				sumOfXY = sumOfXY + xy;
				sampleNo++;
				// Correlation End.

				// Get the min and max bar values for the layer.
				if (this.isComparison)
				{
					if (this.selectionIds.indexOf(chartItem.id) !== -1 ) 
					{
						// Get the min and max bar values for the layer.
						this.xMinValue = Math.min(this.xMinValue, xValue);
						this.xMaxValue = Math.max(this.xMaxValue, xValue);
						this.yMinValue = Math.min(this.yMinValue, yValue);
						this.yMaxValue = Math.max(this.yMaxValue, yValue);
						chartItem.state = ia.ItemLayer.SELECTED;
					}
					if (this.displayAll)
					{
						// Get the min and max bar values for the layer.
						this.xMinValue = Math.min(this.xMinValue, xValue);
						this.xMaxValue = Math.max(this.xMaxValue, xValue);
						this.yMinValue = Math.min(this.yMinValue, yValue);
						this.yMaxValue = Math.max(this.yMaxValue, yValue);
					}
				}
				else
				{
					// Get the min and max bar values for the layer.
					this.xMinValue = Math.min(this.xMinValue, xValue);
					this.xMaxValue = Math.max(this.xMaxValue, xValue);
					this.yMinValue = Math.min(this.yMinValue, yValue);
					this.yMaxValue = Math.max(this.yMaxValue, yValue);
				}
			}
		}

		//ia.log("Building correlation from [sumOfXSquared=" + sumOfXSquared + ",sumOfYSquared=" + sumOfYSquared + ",sampleNo=" + sampleNo + ",sumOfX=" + sumOfX + ",sumOfY=" + sumOfY + "]"); // DEBUG

		// Correlation Start.
		var varianceOfX = (sumOfXSquared / sampleNo) - (Math.pow((sumOfX / sampleNo),2));
		var varianceOfY = (sumOfYSquared / sampleNo) - (Math.pow((sumOfY / sampleNo),2));
		var coariance = (sumOfXY / sampleNo) - (sumOfX * (sumOfY / Math.pow((sampleNo),2)));
		var correlationCoefficient = coariance / Math.sqrt(varianceOfX * varianceOfY);
		//correlationCoefficient = Math.round(correlationCoefficient*100)/100;
		var gradient = ((sumOfX * sumOfY) - (sampleNo * sumOfXY)) / ((Math.pow(sumOfX,2)) - (sampleNo * sumOfXSquared));
		var intercept = ((sumOfX * sumOfXY) - (sumOfY * sumOfXSquared)) / ((Math.pow(sumOfX,2)) - (sampleNo * sumOfXSquared));
		//intercept = Math.round(intercept*100)/100;

		this.correlationInfo.correlationCoeff = correlationCoefficient;
		this.correlationInfo.rSquare = Math.pow(correlationCoefficient,2);
		this.correlationInfo.gradient = gradient;
		this.correlationInfo.intercept = intercept;

		//ia.log("[coefficient=" + this.correlationInfo.correlationCoeff + ",rSquare=" + this.correlationInfo.rSquare + ",gradient=" + this.correlationInfo.gradient + ",intercept=" + this.correlationInfo.intercept + "]"); // DEBUG
		// Correlation End.
		
		if (this._sizeData !== undefined)
		{
			var dir = -1;
			this.itemArray.sort(function(a, b)
			{
				if (a.symbolSize < b.symbolSize) return -dir;
				if (a.symbolSize > b.symbolSize) return dir;
				return 0; 
			});
		}
		
		this.dataChanged = false;
	}
};

/**
 * Renders the data.
 *
 * @method render
 */
ia.PlotLayer.prototype.render = function() 
{
	// Clear the canvas.
	this.clear();
	
	// Correlation line.
	if (this.showCorrelationLine)
	{
		// Clip.
   		this.context.save();
	    this.context.rect(this.map.canvasX, this.map.canvasY, this.map.canvasWidth, this.map.canvasHeight);
	    this.context.clip();

		this.context.strokeStyle = ia.Color.toRGBA(this.selectionColor, 0.3);
		this.context.lineWidth = 2;
		var x1 = this.map.getPixelX(this.map.getBBox().getXMin());
		var x2 = this.map.getPixelX(this.map.getBBox().getXMax());
		var y1 = this.map.getPixelY((this.map.getBBox().getXMin() * this.correlationInfo.gradient) + this.correlationInfo.intercept);
		var y2 = this.map.getPixelY((this.map.getBBox().getXMax() * this.correlationInfo.gradient) + this.correlationInfo.intercept);
		this.context.beginPath();
			 this.context.moveTo(x1, y1);
			 this.context.lineTo(x2, y2);
		this.context.stroke();

   		this.context.restore();
	}
	
	// Reset the context styles in case the layer styles has changed.
	for (var p in this.style) 
	{
		this.context[p] = this.style[p];
		this.selectionContext[p] = this.style[p];
		this.highlightContext[p] = this.style[p];
	} 
	this.highlightContext.strokeStyle = this.highlightColor;
	this.selectionContext.strokeStyle = this.selectionColor;
	this.highlightContext.fillStyle = "rgba(0, 0, 0, 0)";
	this.selectionContext.fillStyle = "rgba(0, 0, 0, 0)";

	// Render the items.
	var nItems = this.itemArray.length;
	for (var i = 0; i < nItems; i++) 
	{
		var chartItem = this.itemArray[i];
		this._setItemShape(chartItem);
		this._renderItem(chartItem);
	}

	// Render the selection.
	this.renderSelection();
};

/**
 * Sets an items dimensions.
 *
 * @method _setItemShape
 * @param {Object} item The item.
 * @private
 */
ia.PlotLayer.prototype._setItemShape = function(item)
{
	// Calculate the bounding box of the chart item.
	var x = this.map.getPixelX(item.xValue); 
	var y = this.map.getPixelY(item.yValue); 	
	var w = item.symbolSize;	
	var h = item.symbolSize;

	// Reset the pixel drawing area for the point.
	item.shape.x = x;
	item.shape.y = y;
	item.shape.width = w;
	item.shape.height = h;

	// Reset the pixel hit area for the point.
	w = Math.max(15, w);	
	h = Math.max(15, h);
	if (ia.IS_TOUCH_DEVICE) // Larger hit area for touch devices.
	{
		w = Math.max(30, w);	
		h = Math.max(30, h);
	}
	item.hitArea.x = x - (w / 2);
	item.hitArea.y = y - (h / 2);
	item.hitArea.width = w;
	item.hitArea.height = h;
};

/**
 * Renders the item to the given context.
 *
 * @method _renderItem
 * @param {Object} item The item.
 * @private
 */
ia.PlotLayer.prototype._renderItem = function(item)
{
	if (this._colorData !== undefined) // Identifies bubble plot.
	{
		this.context.fillStyle = item.color;
		this.context.strokeStyle = "#CCCCCC";
		this.context.lineWidth = 1;
	}
	this._drawItem(item, this.context, item.symbolSize);
};
	
/**
 * Selects the item.
 *
 * @method selectItem
 * @param {Object} item The item.
 */
ia.PlotLayer.prototype.selectItem = function(item)
{	
	this._drawItem(item, this.selectionContext, item.symbolSize);
};

/**
 * Highlights the item.
 *
 * @method highlightItem
 * @param {Object} item The item.
 */
ia.PlotLayer.prototype.highlightItem = function(item)
{	
	// Draw crosshairs.
	this.highlightContext.strokeStyle = ia.Color.toRGBA(this.highlightColor, 0.3);
	var x1 = this.map.getPixelX(this.map.getBBox().getXMin());
	var x2 = this.map.getPixelX(this.map.getBBox().getXMax());
	var y1 = this.map.getPixelY(this.map.getBBox().getYMin());
	var y2 = this.map.getPixelY(this.map.getBBox().getYMax());
	this.highlightContext.beginPath();
		 this.highlightContext.moveTo(item.shape.x, y1);
		 this.highlightContext.lineTo(item.shape.x, y2);
		 this.highlightContext.moveTo(x1, item.shape.y);
		 this.highlightContext.lineTo(x2, item.shape.y);
	this.highlightContext.stroke();
	
	this.highlightContext.strokeStyle = this.highlightColor;
	this._drawItem(item, this.highlightContext, item.symbolSize);
};

/**
 * Does the actual drawing.
 *
 * @method _drawItem
 * @param {Object} item The item.
 * @param {HTML Canvas Context} ctx The context to render to.
 * @private
 */
ia.PlotLayer.prototype._drawItem = function(item, ctx, radius)
{
	ctx.beginPath();
		 ia.Shape.drawCircle(ctx, item.shape.x, item.shape.y, radius);
	ctx.fill();
	ctx.stroke();
};

/**
 * Runs a hit test on an item. 
 * 
 * @method hitItem
 * @param {Object} item The item to hit test.
 * @param {ia.MapMouseEvent} event An <code>ia.MapMouseEvent</code>.
 */
ia.PlotLayer.prototype.hitItem = function(item, event)
{
	if (item.hitArea.intersects(event.x,event.y)) return true;
	else return false;
};

/** 
 * Displays the tip for the passed item
 * 
 * @method showTip
 * @param {Object} item The map item.	 
 * @param {ia.ItemEvent} event An <code>ia.ItemEvent</code>.
 */
ia.PlotLayer.prototype.showTip = function(item, event)
{
	this.map.datatip.text(this.tipFunction(item));

	var px,py;
	if (ia.IS_TOUCH_DEVICE)
	{
		px = event.x - (this.map.datatip.getWidth() / 2);
		py = event.y - (this.map.datatip.getHeight() + 30);
	}
	else
	{
		px = item.shape.x + item.shape.width / 2;
		py = item.shape.y - item.shape.height / 2 - this.map.datatip.getHeight();
	}

	this.map.datatip.position(px, py);
	this.map.datatip.show();
};
	
/** 
 * Get correlation information.
 * 
 * @method _getCorrelation
 * @return {Object} An object (o) with the properties o.correlationCoeff, o.rSquare, o.gradient, o.intercept or null.
 * @private
 */
ia.PlotLayer.prototype._getCorrelation = function() 
{		
	var x = 0;
	var sumOfX = 0;
	var sumOfXSquared = 0;
	var xSquared = 0;

	var y = 0;
	var sumOfY = 0;
	var sumOfYSquared = 0;
	var ySquared = 0;

	var xy = 0;
	var sumOfXY = 0;
	var sampleNo = 0;

	// Loop through the data.
	for (var id in xData)
	{
		// Get the data item.
		var xDataItem = xData[id];
		var yDataItem = yData[id];

		// Has to be a number to be displayed in a bar chart
		var xValue = xDataItem[xDataField];
		var yValue = yDataItem[yDataField];

		if (ia.isNumber(xValue) && ia.isNumber(yValue)) 
		{	
			xSquared = xValue * xValue;
			sumOfX = sumOfX + xValue;
			sumOfXSquared = sumOfXSquared + xSquared;

			ySquared = yValue * yValue;
			sumOfY = sumOfY + yValue;
			sumOfYSquared = sumOfYSquared + ySquared;

			xy = xValue * yValue;
			sumOfXY = sumOfXY + xy;
			sampleNo++;

		}
	}

	//ia.log("Building correlation from [sumOfXSquared=" + sumOfXSquared + ",sumOfYSquared=" + sumOfYSquared + ",sampleNo=" + sampleNo + ",sumOfX=" + sumOfX + ",sumOfY=" + sumOfY + "]"); // DEBUG

	var varianceOfX = (sumOfXSquared / sampleNo) - (Math.pow((sumOfX / sampleNo),2));
	var varianceOfY = (sumOfYSquared / sampleNo) - (Math.pow((sumOfY / sampleNo),2));
	var coariance = (sumOfXY / sampleNo) - (sumOfX * (sumOfY / Math.pow((sampleNo),2)));
	var correlationCoefficient = coariance / Math.sqrt(varianceOfX * varianceOfY);
	correlationCoefficient = Math.round(correlationCoefficient*100)/100;
	var gradient = ((sumOfX * sumOfY) - (sampleNo * sumOfXY)) / ((Math.pow(sumOfX,2)) - (sampleNo * sumOfXSquared));
	var intercept = ((sumOfX * sumOfXY) - (sumOfY * sumOfXSquared)) / ((Math.pow(sumOfX,2)) - (sampleNo * sumOfXSquared));
	intercept = Math.round(intercept*100)/100;

	var o = new Object()
	o.correlationCoeff = correlationCoefficient;
	o.rSquare = Math.pow(correlationCoefficient,2);
	o.gradient = gradient;
	o.intercept = intercept;

	ia.log("[coefficient=" + o.correlationCoeff + ",rSquare=" + o.rSquare + ",gradient=" + o.gradient + ",intercept=" + o.intercept + "]"); // DEBUG
	return o;
};