Show:

File: ia\charts\layers\TimeLayer.js

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

	this.style = {fillStyle:'#ffffff', strokeStyle:'#ff0000', lineWidth:'2', lineJoin:'round'};
	this.selectedDate = "";
	this.drawLinesThroughMissingValues = true;
	this.highlightSelectedDate = false;
};
ia.extend(ia.ItemLayer, ia.TimeLayer);

/**
 * The selected date.
 *
 * @property selectedDate
 * @type String
 */
ia.TimeLayer.prototype.selectedDate;
/**
 * Should the selected date be highlighted.
 *
 * @property highlightSelectedDate
 * @type Boolean
 * @default false
 */
ia.TimeLayer.prototype.highlightSelectedDate;

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

/** 
 * The min bar value.
 *
 * @property minValue
 * @type Number
 */
ia.TimeLayer.prototype.minValue;

/** 
 * The max bar value.
 *
 * @property maxValue
 * @type Number
 */
ia.TimeLayer.prototype.maxValue;

/**
 * Specifies a color palette for the layer.
 *
 * @property colorPalette
 * @type ia.ColorPalette
 */
ia.TimeLayer.prototype.colorPalette;

/**
 * Should lines be drawn through missing data values.
 *
 * @property drawLinesThroughMissingValues
 * @type Boolean
 * @default true
 */
ia.TimeLayer.prototype.drawLinesThroughMissingValues;

/** 
 * Indicates that the axis should be updated and matched to the selected features.
 * 
 * @property matchAxisToSelectedData
 * @type Boolean
 * @default false
 */
ia.TimeLayer.prototype.matchAxisToSelectedData;

/** 
 * The marker size.
 * 
 * @property markerSize
 * @type Number
 * @default 7
 */
ia.TimeLayer.prototype.markerSize = 7;

/**
 * Updates the data.
 *
 * @method update
 */
ia.TimeLayer.prototype.update = function() 
{
	// Check if the data has changed
	if (this.map && this.dataChanged)
	{
		// Get the data.
		var data = this.getData();

		// Clear the items.
		this.itemArray = [];
		this.clearItems();
		
		this.minValue = Infinity;
		this.maxValue = -Infinity;

		if (data.dates)
		{
			// First off get the list of ids to loop through.
			var indData = data[data.dates[0]];
			var dateLength = data.dates.length;

			// Loop through the data.
			for (var id in indData)
			{
				// Get the data item.
				var dataItem = indData[id];
				
				// Create a new chart item.
				var chartItem = {};
				chartItem.id = dataItem.id;
				chartItem.name = dataItem.name;
				chartItem.state = ia.ItemLayer.UNSELECTED;
				if (this.selectionIds.indexOf(chartItem.id) !== -1 ) chartItem.state = ia.ItemLayer.SELECTED;
				chartItem.parent = this;
				chartItem.layer = this;

				this.items[dataItem.id] = chartItem;
				this.itemArray.push(chartItem);
				
				// Now add child items for each date.
				chartItem.childItems = [];
				for (var i = 0; i < dateLength; i++)
				{
					var date = data.dates[i];
					var dataItem = data[date][id];
				
					// Has to be a number to be displayed in a time chart
					var value = dataItem[this.dataField];
					if (ia.isNumber(value)) 
					{	
						var childItem = {};
						childItem.id = id+"~"+date;
						childItem.name = date;
						childItem.shape = new ia.Rectangle();
						childItem.hitArea = new ia.Rectangle();
						childItem.value = value;
						childItem.formattedValue = dataItem[this.dataField+"_formatted"];

						if (this.isComparison)
						{
							if (this.selectionIds.indexOf(chartItem.id) !== -1 ) 
							{
								// Get the min and max bar values for the layer.
								this.minValue = Math.min(this.minValue, value);
								this.maxValue = Math.max(this.maxValue, value);
								chartItem.state = ia.ItemLayer.SELECTED;
							}
							if (this.displayAll)
							{
								// Get the min and max bar values for the layer.
								this.minValue = Math.min(this.minValue, value);
								this.maxValue = Math.max(this.maxValue, value);
							}
						}
						else if ((this.matchAxisToSelectedData === false) ||
							(this.matchAxisToSelectedData === true && chartItem.state === ia.ItemLayer.SELECTED))
						{
							// Get the min and max bar values for the layer.
							this.minValue = Math.min(this.minValue, childItem.value);
							this.maxValue = Math.max(this.maxValue, childItem.value);
						}

						chartItem.childItems[chartItem.childItems.length] = childItem;
					}
					else chartItem.childItems[chartItem.childItems.length] = {name:date, value:undefined};
				}
			}
		}
		this.dataChanged = false;
	}
}

/**
 * Renders the data.
 *
 * @method render
 */
ia.TimeLayer.prototype.render = function() 
{
	// Clear the canvas.
	this.clear();

	// 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 = ia.Color.toRGBA(this.highlightColor);
	this.selectionContext.strokeStyle = ia.Color.toRGBA(this.selectionColor);

	// Set the items shape.
	var nItems = this.itemArray.length;
	for (var i = 0; i < nItems; i++) 
	{
		var chartItem = this.itemArray[i];

		var nChildItems = chartItem.childItems.length;
		for (var j = 0; j < nChildItems; j++)
		{
			var childItem = chartItem.childItems[j];
			if (childItem.value !== undefined) this._setItemShape(childItem, j, nChildItems);
		}
	}

	// Render selected date region.
	if (this.highlightSelectedDate)
	{
		if (this.getData().dates)
		{
			this.context.fillStyle = ia.Color.toRGBA('#cccccc', 0.2);
			this.context.lineWidth = 0;
		
			var dates = this.getData().dates;
			var index = dates.indexOf(this.selectedDate);
			if (index !== -1)
			{
				var x, y, w, h;
				if (this.map.orientation === "vertical")
				{
					x = this.map.canvasX + ((index / dates.length) * this.map.canvasWidth);
					y = this.map.canvasY;
					w = this.map.canvasWidth / dates.length;
					h = this.map.canvasHeight; 	 
				}
				else
				{
					x = this.map.canvasX;
					y = this.map.canvasY + ((index / dates.length) * this.map.canvasHeight);
					w = this.map.canvasWidth;
					h = this.map.canvasHeight / dates.length; 	 
				}
				this.context.beginPath();
					this.context.rect(x, y, w, h);
				this.context.fill();
			}
		}
	}

	// Render the selection (which in this case is the only thing displayed on the chart).
	this.renderSelection();
};

/**
 * Sets an items dimensions.
 *
 * @method _setItemShape
 * @param {Object} item The item.
 * @param {Number} index The index of the item in the item array.
 * @private
 */
ia.TimeLayer.prototype._setItemShape = function(item, index, nItems)
{
	if (this.map.orientation === "vertical")
	{
		var startX = this.map.canvasX;
		var hSpace = this.map.canvasWidth;
		if (this.map.centerXAxisLabels)
		{
			var indent = this.map.canvasWidth / (nItems * 2);
			startX = this.map.canvasX + indent; 
			hSpace = this.map.canvasWidth - (indent * 2);
		}
		var x = startX + (index / (nItems - 1)) * hSpace;
		var y = this.map.getPixelY(item.value); 	
	}
	else
	{
		var startY = this.map.canvasY;
		var vSpace = this.map.canvasHeight;
		if (this.map.centerYAxisLabels)
		{
			var indent = this.map.canvasHeight / (nItems * 2);
			startY = this.map.canvasY + indent; 
			vSpace = this.map.canvasHeight - (indent * 2);
		}
		var x = this.map.getPixelX(item.value); 	
		var y = startY + (index / (nItems - 1)) * vSpace;	
	}	
	var w = this.markerSize;	
	var h = this.markerSize;
	
	// 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.
	if (ia.IS_TOUCH_DEVICE) // Larger hit area for touch devices.
	{
		w = 30;	
		h = 30;
	}
	else
	{
		w = w * 2;	
		h = h * 2;
	}
	item.hitArea.x = x - (w / 2);
	item.hitArea.y = y - (h / 2);
	item.hitArea.width = w;
	item.hitArea.height = h;
};

/**
 * Selects the item.
 *
 * @method selectItem
 * @param {Object} item The item.
 */
ia.TimeLayer.prototype.selectItem = function(item)
{	
	var n = this.selectionIds.length;
	
	if (this.colorPalette)
	{
		var colorList = this.colorPalette.getColors(n);
		var index = this.selectionIds.indexOf(item.id);
		this.selectionContext.strokeStyle = colorList[index];	
	}
	
	this._drawItem(item, this.selectionContext);
};

/**
 * Highlights the item.
 *
 * @method highlightItem
 * @param {Object} item The item.
 */
ia.TimeLayer.prototype.highlightItem = function(item)
{	
	// Clip.
	if (!ia.IS_IE_TEN)
	{
		this.highlightContext.beginPath();
	    this.highlightContext.rect(this.map.canvasX, this.map.canvasY, this.map.canvasWidth, this.map.canvasHeight);
	    this.highlightContext.clip();
	}
	
	this._drawItem(item, this.highlightContext);
};

/**
 * Does the actual drawing.
 *
 * @method _drawItem
 * @param {Object} item The item.
 * @param {HTML Canvas Context} ctx The context to render to.
 * @private
 */
ia.TimeLayer.prototype._drawItem = function(item, ctx)
{
	var doMoveTo = true; // Takes into account first value and missing values.

	var n = item.childItems.length;

	ctx.beginPath();
	for (var i = 0; i < n; i++)
	{
		var childItem = item.childItems[i];
		if (childItem.value !== undefined) 
		{
			if (doMoveTo) 
				ctx.moveTo(childItem.shape.x, childItem.shape.y);
			else 
				ctx.lineTo(childItem.shape.x, childItem.shape.y);
			doMoveTo = false;
		}
		else if (this.drawLinesThroughMissingValues !== true) doMoveTo = true;
		if (this.map.animationMode && childItem.name === this.selectedDate) break;
	}
	ctx.stroke();
			
	for (var i = 0; i < n; i++)
	{
		var childItem = item.childItems[i];
		if (childItem.value !== undefined)
		{
			ctx.beginPath();
				 ia.Shape.drawCircle(ctx, childItem.shape.x, childItem.shape.y, childItem.shape.width);
			ctx.fill();
			ctx.stroke();
		}
		if (this.map.animationMode && childItem.name === this.selectedDate) break;
	}
};

/**
 * 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.TimeLayer.prototype.hitItem = function(item, event)
{
	if (this.isSelected(item.id))
	{
		var n = item.childItems.length;
		for (var i = 0; i < n; i++)
		{
			var childItem = item.childItems[i];
			if (childItem.value !== undefined) 
			{
				if (childItem.hitArea.intersects(event.x,event.y)) return true;
			}
		}
	}
	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.TimeLayer.prototype.showTip = function(item, event)
{
	var n = item.childItems.length;
	for (var i = 0; i < n; i++)
	{
		var childItem = item.childItems[i];
		if (childItem.value !== undefined) 
		{
			if (childItem.hitArea.intersects(event.x,event.y)) 
			{
				// Tip replacement.
				if (this.tip !== "")
				{
					var label = this.tip;
					label = label.split("${name}").join(item.name);
					label = label.split("${date}").join(childItem.name);
					label = label.split("${value}").join(childItem.formattedValue);
					this.map.datatip.text(label);
				}
				else this.map.datatip.text(item.name+" : "+childItem.name+" : "+childItem.formattedValue);

				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 = (childItem.shape.x + childItem.shape.width / 2) - (this.map.datatip.getWidth() / 2);
					py = (childItem.shape.y - childItem.shape.height / 2) - (this.map.datatip.getHeight() + 5);
				}
				
				this.map.datatip.position(px, py);
				this.map.datatip.show();
				break;
			}
		}
	}
};