Show:

File: ia\data\Theme.js

/** 
 * Contains information about a ia.Theme.
 *
 * @author J Clare
 * @class ia.Theme
 * @extends ia.BaseData
 * @constructor
 * @param {ia.Geography} geography The geography the theme belongs to.
 * @param {ia.Theme} theme The parent theme the theme belongs to or undefined if its a child of the geography.
 * @param {JSON} data The json data describing the object.
 */
ia.Theme = function(geography, theme, data)
{
	var parent = theme || geography;
	ia.Theme.baseConstructor.call(this, data, parent);
	
	this.hasThemes = false;
	this.geography = geography;
	this.parseData(data);
};
ia.extend(ia.BaseData, ia.Theme);

/** 
 * Indicates if this theme contains nested themes.
 *
 * @property hasThemes
 * @type Boolean
 * @default false
 */
ia.Theme.prototype.hasThemes;

/** 
 * Parses the JSON data.
 *
 * @method parseData
 * @param {JSON} data The json data describing the object.
 */
ia.Theme.prototype.parseData = function(data) 
{
	// Parse the JSON data.
	if (this.data.fileName !== undefined) this.fileName = this.geography.reportData.path + this.data.fileName;

	// Themes.
	this._themeArray = [];
	var themes = this.data.themes;
	if (themes !== undefined) 
	{
		var n = themes.length;
		for (var i = 0; i < n; i++) 
		{ 
			this.hasThemes = true;
			this._themeArray[i] = new ia.Theme(this.geography, this, themes[i]);
		}
	}

	// Indicators.
	this._indicatorArray = [];
	this._indicatorHash = {};
	this._indicatorDateHash = {};
	var indicators = this.data.indicators;
	if (indicators !== undefined) 
	{
		var dateArray = [];
		var prevId;

		var n = indicators.length;
		for (var i = 0; i < n; i++) 
		{ 
			var ind = new ia.Indicator(this.geography, this, indicators[i]);
			this._indicatorArray[i] = ind;

			if (ind.date)
			{
				this._indicatorHash[ind.id+"_"+ind.date] = ind;

				if (ind.id === prevId) 
					dateArray.push(ind.date);
				else 
				{
					dateArray = [];
					dateArray.push(ind.date);
				}

				prevId = ind.id;
				this._indicatorDateHash[ind.id] = dateArray;
			}
			else this._indicatorHash[ind.id] = ind;
		}
	}
};

/** 
 * Returns the themes contained in the ia.Theme.  
 *
 * @method getThemes
 * @return {ia.Theme[]} An array of themes.
 */	
ia.Theme.prototype.getThemes = function() {return this._themeArray;};

/** 
 * Returns a list of the parent theme names including this one.  
 *
 * @method getParentThemes
 * @return {ia.Theme[]} An array of parent theme names.
 */	
ia.Theme.prototype.getParentThemes = function() 
{
	var hasThemes;
	var themeArray = [this.name];
	var thm = this.parent;
	while (thm.hasThemes === true) 
	{
		themeArray.push(thm.name);
		thm = thm.parent;
	}
	return themeArray.reverse();
};

/** 
 * Returns an array of data objects that are children of this object.
 *
 * @method getChildren
 * @return {Object[]} An array of data objects.
 */	
ia.Theme.prototype.getChildren = function() 
{
	if (this.hasThemes) 
	{
		var childArray = this._indicatorArray.concat(this._themeArray)
		return childArray;
	}
	else return this._indicatorArray;
};

/** 
 * Gets the themes indicator data.
 *
 * @method getData
 * @param {String} indicatorId An optional indicator id.
 * @param {String[]} featureIds An optional list of feature ids to get data for.
 * @return {Object[]} An array of data objects.
 */	
ia.Theme.prototype.getData = function(indicatorId, featureIds) 
{
	// Hashtable to contain the data
	var dataHash = {};
	dataHash.dates = []

	// Get the indicators with the given id.
	var indList;
	if (indicatorId) indList = this.getIndicators(indicatorId);
	else indList = this.getIndicators(); 
	
	var n = indList.length;
	for (var i = 0; i < n; i++) 
	{ 
		var ind = indList[i];
		if (ind.date !== undefined)
		{
			dataHash.dates[dataHash.dates.length] = ind.date;
			dataHash[ind.date] = ind.getData(featureIds);
		}
		else
		{
			dataHash.dates[dataHash.dates.length] = ind.name;
			dataHash[ind.name] = ind.getData(featureIds);
		}
	}

	return dataHash;
};

/** 
 * Gets the themes indicator data with the given date.
 *
 * @method getDataForDate
 * @param {String} date The date.
 * @param {String[]} featureIds An optional list of feature ids to get data for.
 * @return {Object[]} An array of data objects.
 */	
ia.Theme.prototype.getDataForDate = function(date, featureIds) 
{
	// Hashtable to contain the data
	var dataHash = {};
	dataHash.dates = [];

	// Get the indicators.
	var indList = this.getIndicators(); 
	var n = indList.length;
	for (var i = 0; i < n; i++) 
	{ 
		var ind = indList[i];
		if (ind.date === date)
		{
			dataHash.dates[dataHash.dates.length] = ind.name;
			dataHash[ind.name] = ind.getData(featureIds);
		}
	}

	return dataHash;
};

/** 
 * Gets the themes indicator comparison data.
 *
 * @method getComparisonData
 * @param {String} indicatorId The indicator id.
 * @return {Object[]} An array of data objects.
 */	
ia.Theme.prototype.getComparisonData = function(indicatorId) 
{
	// Hashtable to contain the data
	var dataHash = {};
	dataHash.dates = []

	// Get the indicators with the given id.
	var indList = this.getIndicators(indicatorId);
	
	var n = indList.length;
	for (var i = 0; i < n; i++) 
	{ 
		var ind = indList[i];
		if (ind.date !== undefined)
		{
			dataHash.dates[i] = ind.date;
			dataHash[ind.date] = ind.getComparisonData();
		}
	}

	return dataHash;
};

/** 
 * Gets the themes indicators with the given date.
 *
 * @method getComparisonDataForDate
 * @param {String} date The date.
 * @return {Object[]} An array of data objects.
 */	
ia.Theme.prototype.getComparisonDataForDate = function(date) 
{
	// Hashtable to contain the data
	var dataHash = {};
	dataHash.dates = [];

	// Get the indicators.
	var indList = this.getIndicators(); 
	var n = indList.length;
	for (var i = 0; i < n; i++) 
	{ 
		var ind = indList[i];
		if (ind.date === date)
		{
			dataHash.dates[dataHash.dates.length] = ind.name;
			dataHash[ind.name] = ind.getComparisonData();
		}
	}

	return dataHash;
};

/** 
 * Returns the indicators contained in the theme
 * or with the given id if that is supplied. 
 *
 * @method getIndicators
 * @param {String} indicatorId An optional indicator id.
 * @return {ia.Indicator[]} An array of indicators.
 */	
ia.Theme.prototype.getIndicators = function(indicatorId) 
{
	if (indicatorId === null) return this._indicatorArray;
	else
	{
		var dates = this.getIndicatorDates(indicatorId);
		var indArray = [];
		if (dates !== undefined)
		{
			var n = dates.length;
			for (var i = 0; i < n; i++) 
			{ 
				indArray.push(this.getIndicator(indicatorId, dates[i]));
			}
		}
		else
		{
			indArray.push(this.getIndicator(indicatorId));
		}

		return indArray;
	}
};

/** 
 * Returns the indicators with the given date. 
 * Will include the latest date of an indicator if the specified date is unavailable.
 *
 * @method getIndicators
 * @param {String} date The date.
 * @return {ia.Indicator[]} An array of indicators.
 */	
ia.Theme.prototype.getIndicatorsWithDate = function(date) 
{
	var indicatorIds = this.getIndicatorIds();
	var indicators = new Array();
	for (var i = 0; i < indicatorIds.length; i++) 
	{ 
		var id = indicatorIds[i];
		indicators[indicators.length] = this.getIndicator(id, date);
	}
	return indicators;
};

/** 
 * Returns the first indicator - this can be within a nested ia.Theme.
 * Where dates are used the first indicator is the one with the most recent date
 * 
 * @method getFirstIndicator
 * @param {Boolean} reverseDates Should the dates be reversed.
 * @return {ia.Indicator} The indicator.
 */
ia.Theme.prototype.getFirstIndicator = function(reverseDates) 
{
	if (reverseDates === undefined) reverseDates = true;

	if (this._indicatorArray.length > 0)
	{
		var ind = this._indicatorArray[0];
		var indicatorList = this.getIndicators(ind.id);

		if (reverseDates) return indicatorList[indicatorList.length-1];
		else return indicatorList[0];
	}
	else if (this.hasThemes) // Nested themes.
	{
		var n = this._themeArray.length;
		for (var i = 0; i < n; i++)
		{ 
			var thm = this._themeArray[i];
			return thm.getFirstIndicator(reverseDates);
		}
	}
};

/** 
 * Returns the indicator that corresponds to the id and date provided.
 * Will include the latest date of an indicator if the specified date is unavailable.
 * 
 * @method getIndicator
 * @param {String} id The indicator id.
 * @param {String} date An optional date.
 * @return {ia.Indicator} The indicator.
 * @param {Boolean} reverseDates Should the dates be reversed.
 */
ia.Theme.prototype.getIndicator = function(id, date, reverseDates) 
{
	if (reverseDates === undefined) reverseDates = true;

	if (date !== undefined && date !== "" && this._indicatorHash[id+"_"+date] !== undefined)
	{
		return this._indicatorHash[id+"_"+date];
	}
	else if (this._indicatorHash[id] !== undefined)
	{
		return this._indicatorHash[id];
	}
	else // Try getting latest date for indicator
	{
		var dates = this.getIndicatorDates(id);
		if (dates) 
		{
			if (reverseDates) return this._indicatorHash[id+"_"+dates[dates.length-1]];
			else return this._indicatorHash[id+"_"+dates[0]];
		}
	}
};

/** 
 * Includes indicators in nested themes.
 * 
 * @method getNestedIndicator
 * @param {String} id The indicator id.
 * @param {String} date An optional date.
 * @return {ia.Indicator} The indicator.
 * @param {Boolean} reverseDates Should the dates be reversed.
 */
ia.Theme.prototype.getNestedIndicator = function(id, date, reverseDates) 
{
	var ind = this.getIndicator(id, date, reverseDates);
	if (ind !== undefined) return ind;

	if (this.hasThemes) // Nested themes.
	{
		var n = this._themeArray.length;
		for (var i = 0; i < n; i++) 
		{ 
			var thm = this._themeArray[i];
			ind = thm.getNestedIndicator(id, date, reverseDates);
			if (ind !== undefined) return ind;
		}
	}
};

/** 
 * Returns a list of dates for the indicator that matches
 * the id provided.
 * 
 * @method getIndicatorDates
 * @param {String} indicatorId The indicator id.
 * @return {String[]} A list of dates.
 */
ia.Theme.prototype.getIndicatorDates = function(indicatorId) 
{
	return this._indicatorDateHash[indicatorId];
};

/** 
 * Returns a list of indicator ids contained in the theme.
 * 
 * @method getIndicatorIds
 * @return {String[]} A list of indicator ids.
 */
ia.Theme.prototype.getIndicatorIds = function() 
{
	var n = this._indicatorArray.length;
	var indicatorIds = [];
	for (var i = 0; i < n; i++) 
	{ 
		var ind = this._indicatorArray[i];
		if (indicatorIds.indexOf(ind.id) === -1)
		indicatorIds.push(ind.id);
	}
	return indicatorIds;
};

/** 
 * Returns a list of indicator names contained in the theme.
 * 
 * @method getIndicatorNames
 * @return {String[]} A list of indicator names.
 */
ia.Theme.prototype.getIndicatorNames = function() 
{
	var n = this._indicatorArray.length;
	var indicatorNames = [];
	for (var i = 0; i < n; i++) 
	{ 
		var ind = this._indicatorArray[i];
		if (indicatorNames.indexOf(ind.name) === -1)
		indicatorNames.push(ind.name);
	}
	return indicatorNames;
};

/** 
 * Loads all data in the theme.
 * 
 * @method loadData
 * @param {Function} callbackFnc The callbackFnc gets called with the loaded object as the parameter.
 */
ia.Theme.prototype.loadData = function(callbackFnc) 
{
	var me = this;

	// Load the theme if there isnt any data yet.
	if (me.hasData === false) 
	{
		ia.File.load(
		{
			url: me.fileName,
			dataType: "json", 
			onSuccess:function(json)
			{
				me.data = json;
				me.hasData = true;
				me.parseData();

				if (this.hasThemes) // Load any nested themes.
				{
					var thmCount = 0;
					function onThemeReady()
					{
						thmCount++;
						if (thmCount === me._themeArray.length) callbackFnc.call(null, me); // Return after all nested themes have loaded.
						else
						{
							// Load next theme.
							var thm = me._themeArray[thmCount];
							thm.loadData(onThemeReady);
						}
					};
				
					// Load first theme.
					if (this._themeArray.length > 0)
					{
						var thm = this._themeArray[0];
						thm.loadData(onThemeReady);
					}
				}
				else callbackFnc.call(null, me); // Return after theme data has loaded.
			}
		});
	}
	else callbackFnc.call(null, me);  // Return because theme already has data.
};

/** 
 * Loads the indicator that corresponds to the id and date provided.
 * The indicator can be in a nested ia.Theme.
 * Returns the indicator via the supplied callback function.
 * 
 * @method loadIndicator
 * @param {String} id The indicator id.
 * @param {String} date An optional date.
 * @param {Function} callbackFnc The callbackFnc gets called with the loaded object as the parameter.
 */
ia.Theme.prototype.loadIndicator = function(id, date, callbackFnc) 
{
	var me = this;
	var ind = me.getIndicator(id, date);
	if (ind !== undefined) 
	{
		// Check if the theme has been loaded.
		if (me.hasData === false) 
		{
			// Load the theme if there isnt any data yet.
			me.loadData(function()
			{
				// Have to get indicator again because it will have 
				// had the data added to it.
				var ind = me.getIndicator(id, date)
				callbackFnc.call(null, ind);
			})
		}
		else callbackFnc.call(null, ind); // Indicator is already loaded so just return.
	}
};

/** 
 * A hashtable of the indicators/associates for a feature - used for profiles.
 *
 * <p>The returned data has the following structure:</p>
 *
 * <p>["t0"]{id:"t0", name:"theme0", type:"parent"}
 * <br/>["i0"]{id:"i0", name:"indicator0", value:2345, value_formatted:2345, associate1:25, associate1_formatted:25, type:"leaf"}
 * <br/>["i1"]{id:"i1", name:"indicator1", value:4347, value_formatted:4347, associate1:45, associate1_formatted:45, type:"leaf"}
 * <br/>["i2"]{id:"i2", name:"indicator1", value:2496, value_formatted:2496, associate1:25, associate1_formatted:25, type:"leaf"}</p>
 *
 * @method getProfileData
 * @param {String[]} featureIds A list of feature ids to get data for.
 * @param {Object[]} themeArray The array to add the theme objects to.
 * @param {String} date An optional date.
 * @param {Boolean} useLatestDate Use the latest date if the specified date is unavailable?
 */	
ia.Theme.prototype.getProfileData = function(featureIds, themeArray, date, useLatestDate) 
{
	// Theme.
	var thmObj = {};
	thmObj.id = this.id;
	thmObj.name = this.name;
	thmObj.indicators = [];
	themeArray[themeArray.length] = thmObj;
	
	if (this.hasThemes) // Nested themes.
	{
		var n = this._themeArray.length;
		for (var i = 0; i < n; i++) 
		{ 
			var thm = this._themeArray[i];
			thm.getProfileData(featureIds, themeArray, date, useLatestDate);
		}
	}
	
	// Indicators.
	var indList;

	if (useLatestDate && date !== undefined) // Selected date.
	{
		indList = this.getIndicatorsWithDate(date);
	}
	else if (useLatestDate && date === undefined) // Most recent date.
	{
		var date = this.getFirstIndicator(true).date;
		indList = this.getIndicatorsWithDate(date);
	}
	else // All dates.
	{
		indList = this.getIndicators();
	}

	var iLength = indList.length;
	for (var i = 0; i < iLength; i++) 
	{ 
		var ind = indList[i];

		var indObj = {};
		indObj.id = ind.id;
		indObj.name = ind.name;
		indObj.date = ind.date;
		indObj.type = ind.type;
		indObj.href = ind.href;
		indObj.features = [];
		thmObj.indicators[thmObj.indicators.length] = indObj;
		
		// Features.
		var fLength = featureIds.length;
		for (var j = 0; j < fLength; j++) 
		{ 
			// Feature.
			var feature = this.geography.getFeature(featureIds[j]);
		
			var feaObj = {};
			feaObj.id = feature.id;
			feaObj.name = feature.name;
			feaObj.value = ind.getValue(feature.id);
			feaObj.value_formatted = ind.getFormattedValue(feature.id);

			// Associates
			var associates = ind.getAssociates();
			var aLength = associates.length;
			for (var k = 0; k < aLength; k++) 
			{		
				var associate = associates[k];
				feaObj[associate.name] = associate.getValue(feature.id);
				feaObj[associate.name+"_formatted"] = associate.getFormattedValue(feature.id);
				feaObj[associate.name+"_type"] = associate.type;
			}

			// Properties
			var props = ind.getProperties();
			for (var propName in props) 
			{
				feaObj[propName] = props[propName];
				feaObj[propName+"_formatted"] = props[propName];
				feaObj[propName+"_type"] = ia.Thematic.CATEGORIC;
			}

			indObj.features[indObj.features.length] = feaObj;
		}
	}
};

/** 
 * A hashtable of the indicators/associates for a feature - used by feature card.
 *
 * @method getFeatureData
 * @param {Object} feature A feature  object.
 * @param {String} date An optional date.
 * @return {JSON} As described above.
 */	
ia.Theme.prototype.getFeatureData = function(feature, date) 
{
	// Theme.
	var thmObj = {};
	thmObj.id = this.id;
	thmObj.name = this.name;
	thmObj.href = this.href;
	thmObj.precision = this.precision;
	thmObj.themes = [];
	thmObj.indicators = [];
	
	if (this.hasThemes) // Nested themes.
	{
		var n = this._themeArray.length;
		for (var i = 0; i < n; i++) 
		{ 
			var thm = this._themeArray[i];
			if (thm.hasData)
			{
				var nestedThmObj = thm.getFeatureData(feature, date);
				thmObj.themes[thmObj.themes.length] = nestedThmObj;
			}
		}
	}
	
	// Indicators.
	var indList = this.getIndicators();
	var iLength = indList.length;
	for (var i = 0; i < iLength; i++) 
	{ 
		var ind = indList[i];
		if (date === undefined || date === ind.date)
		{
			var indObj = {};
			indObj.id = ind.id;
			indObj.name = ind.name;
			indObj.date = ind.date;
			indObj.type = ind.type;
			indObj.href = ind.href;
			indObj.precision = ind.precision;
			indObj.value = ind.getValue(feature.id);
			indObj.formattedValue = ind.getFormattedValue(feature.id);

			indObj.associates = [];
			indObj.properties = [];

			// Associates
			var associates = ind.getAssociates();
			var aLength = associates.length;
			for (var k = 0; k < aLength; k++) 
			{		
				var associate = associates[k];
				var assObj = {};
				assObj.name = associate.name;
				assObj.type = associate.type;
				assObj.precision = associate.precision;
				assObj.value = associate.getValue(feature.id);
				assObj.formattedValue = associate.getFormattedValue(feature.id);
				indObj.associates[indObj.associates.length] = assObj;
			}

			// Limits
			var lowerLimits = ind.getLowerLimits();
			if (lowerLimits) 
			{
				var lmtObj = {};
				lmtObj.value = lowerLimits.getValue(feature.id);
				lmtObj.precision = lowerLimits.precision;
				lmtObj.formattedValue = lowerLimits.getFormattedValue(feature.id);
				indObj.lowerLimit = lmtObj;
			}
			var upperLimits = ind.getUpperLimits();
			if (upperLimits) 
			{
				var lmtObj = {};
				lmtObj.value = upperLimits.getValue(feature.id);
				lmtObj.precision = upperLimits.precision;
				lmtObj.formattedValue = upperLimits.getFormattedValue(feature.id);
				indObj.upperLimit = lmtObj;
			}

			// Properties
			var props = ind.getProperties();
			for (var propName in props) 
			{
				var propObj = {};
				propObj.name = propName;
				propObj.value = props[propName];
				indObj.properties[indObj.properties.length] = propObj;
			}

			thmObj.indicators[thmObj.indicators.length] = indObj;
		}
	}

	return thmObj;
};

/** 
 * A hashtable of the indicators/associates for a list of features - used by feature card.
 *
 * @param {string[]} featureIds A list of feature ids to get data for.
 * @param {string} date An optional date.
 * @return {JSON} As described above.
 */	
ia.Theme.prototype.getIndicatorData = function(featureIds, date) 
{
	// Theme.
	var thmObj = {};
	thmObj.id = this.id;
	thmObj.name = this.name;
	thmObj.href = this.href;
	thmObj.precision = this.precision;
	thmObj.themes = [];
	thmObj.indicators = [];
	
	if (this.hasThemes) // Nested themes.
	{
		var n = this._themeArray.length;
		for (var i = 0; i < n; i++) 
		{ 
			var thm = this._themeArray[i];
			if (thm.hasData)
			{
				var nestedThmObj = thm.getIndicatorData(feature, date);
				thmObj.themes[thmObj.themes.length] = nestedThmObj;
			}
		}
	}
	
	// Indicators.
	var indList = this.getIndicators();
	var iLength = indList.length;
	for (var i = 0; i < iLength; i++) 
	{ 
		var ind = indList[i];
		if (date === undefined || date === ind.date)
		{
			var indObj = {};
			indObj.id = ind.id;
			indObj.name = ind.name;
			indObj.date = ind.date;
			indObj.type = ind.type;
			indObj.href = ind.href;
			indObj.precision = ind.precision;

			// Indicator Properties.
			indObj.properties = [];
			var props = ind.getProperties();
			for (var propName in props) 
			{
				var propObj = {};
				propObj.name = propName;
				propObj.value = props[propName];
				indObj.properties[indObj.properties.length] = propObj;
			}

			// Features.
			indObj.features = [];
			var fLength = featureIds.length;
			for (var j = 0; j < fLength; j++) 
			{ 
				// Feature.
				var feature = this.geography.getFeature(featureIds[j]);

				var featObj = {};
				featObj.id = feature.id;
				featObj.name = feature.name;
				featObj.href = feature.href;
				featObj.value = ind.getValue(feature.id);
				featObj.formattedValue = ind.getFormattedValue(feature.id);

				featObj.associates = [];
				featObj.properties = [];

				// Feature Properties
				var props = feature.getProperties();
				for (var propName in props) 
				{
					var propObj = {};
					propObj.name = propName;
					propObj.value = props[propName];
					featObj.properties[featObj.properties.length] = propObj;
				}

				// Associates
				var associates = ind.getAssociates();
				var aLength = associates.length;
				for (var k = 0; k < aLength; k++) 
				{		
					var associate = associates[k];
					var assObj = {};
					assObj.name = associate.name;
					assObj.type = associate.type;
					assObj.precision = associate.precision;
					assObj.value = associate.getValue(feature.id);
					assObj.formattedValue = associate.getFormattedValue(feature.id);
					featObj.associates[featObj.associates.length] = assObj;
				}

				// Limits
				var lowerLimits = ind.getLowerLimits();
				if (lowerLimits) 
				{
					var lmtObj = {};
					lmtObj.value = lowerLimits.getValue(feature.id);
					lmtObj.precision = lowerLimits.precision;
					lmtObj.formattedValue = lowerLimits.getFormattedValue(feature.id);
					featObj.lowerLimit = lmtObj;
				}
				var upperLimits = ind.getUpperLimits();
				if (upperLimits) 
				{
					var lmtObj = {};
					lmtObj.value = upperLimits.getValue(feature.id);
					lmtObj.precision = upperLimits.precision;
					lmtObj.formattedValue = upperLimits.getFormattedValue(feature.id);
					featObj.upperLimit = lmtObj;
				}

				indObj.features[indObj.features.length] = featObj;
			}

			thmObj.indicators[thmObj.indicators.length] = indObj;
		}
	}

	return thmObj;
};