Show:

File: ia\DataGroup.js

/** 
 * Responsible for holding information abut the data for a group of components.
 *
 * @author J Clare
 * @class ia.DataGroup
 * @extends ia.EventDispatcher
 * @constructor
 * @param {ia.Report} report The report object.
 * @param {Number} suffix The component suffix.
 */
ia.DataGroup = function(report, suffix)
{	
	ia.DataGroup.baseConstructor.call(this);

	//--------------------------------------------------------------------------
	//
	// Variables
	//
	//--------------------------------------------------------------------------
	
	var me = this;

	var dataChanged = true;
	var geogChanged = false;
	var themeChanged = false;
	var indicatorChanged = false;
	var filterChanged = false;
	var thematicChanged = false;

	var callbackFunction;

	// To hold the id, name, value and features of any applied filter.
	var filter = {id:"",name:"",value:"", features:[]};

	//--------------------------------------------------------------------------
	//
	// Properties
	//
	//--------------------------------------------------------------------------
	
	/** 
	 * The suffix for the data group.
	 * 
	 * @property suffix
	 * @type String
	 */
	this.suffix = suffix || "";

	/** 
	 * The map data.
	 * 
	 * @property mapData
	 * @type ia.MapData
	 */
	this.mapData;
	
	/** 
	 * The selected geography.
	 * 
	 * @property geography
	 * @type ia.Geography
 	 */
	this.geography;
	
	/** 
	 * The selected theme.
	 * 
	 * @property theme
	 * @type ia.Theme
 	 */
	this.theme;
	
	/** 
	 * The selected indicator.
	 * 
	 * @property indicator
	 * @type ia.Indicator
 	 */
	this.indicator;

	/** 
	 * The indicator data.
	 * 
	 * @property indicatorData
	 * @type Object
 	 */
	this.indicatorData;
	
	/** 
	 * The comparison data.
	 * 
	 * @property comparisonData
	 * @type Object
 	 */
	this.comparisonData;
	
	/** 
	 * The theme data.
	 * 
	 * @property themeData
	 * @type Object
 	 */
	this.themeData;

	/** 
	 * The comparison theme data.
	 * 
	 * @property comparisonThemeData
	 * @type Object
 	 */
	this.comparisonThemeData;
	
	/** 
	 * The thematic.
	 * 
	 * @property me.thematic
	 * @type ia.Thematic
 	 */
	this.thematic;
	
	/** 
	 * The comparison thematic.
	 * 
	 * @property comparisonThematic
	 * @type ia.Thematic
 	 */
	this.comparisonThematic;
	
	/** 
	 * holder for the legend settings.
	 * 
	 * @property legendSettings
	 * @type Object
 	 */
	this.legendSettings = {};

	//--------------------------------------------------------------------------
	//
	// Methods
	//
	//--------------------------------------------------------------------------

	/** 
	 * Loads a new indicator.
	 *
 	 * @method setData
	 * @param {String} geogId The geog id.
	 * @param {String} indicatorId The indicator id.
	 * @param {String} date The date.
	 */
	this.setData = function(geogId, indicatorId, date) 
	{
		// Check the indicator exists.
		var geog = report.data.getGeography(geogId);
		var ind  = geog.getIndicator(indicatorId, date);
		
		// If it doesnt just use first indicator.
		if (ind === undefined) 
		{
			var reverseDates = true;
			ind = geog.getFirstIndicator(reverseDates);
		}
		
		updateData(geogId, ind.id, ind.date);
	};

	/** 
	 * Sets a new filter.
	 *
 	 * @method setFilter
	 * @param {String} filterId The filter id.
	 * @param {String} filterValue The filter value.
	 */
	this.setFilter = function(filterId, filterValue) 
	{
		filterChanged = true;

		filter.id = filterId;
		filter.name = me.geography.getFilter(filterId).name;
		filter.value = filterValue;
		filter.features = me.geography.getFilteredFeatures(filter.id, filter.value);

		report.textSubstitution.setVariable("filterName"+suffix, filter.name);
		report.textSubstitution.setVariable("filterValue"+suffix, filter.value);

		onDataChanged();
		me.thematic.commitChanges();
	};

	/** 
	 * Filters the data group on a list of feature ids.
	 *
 	 * @method setFeatureFilter
	 * @param {String[]} featureList a list of feature ids.
	 */
	this.setFilteredFeatures = function(featureList) 
	{
		filterChanged = true;

		filter.id = "";
		filter.name = "";
		filter.value = "";
		filter.features = featureList.concat();

		report.textSubstitution.setVariable("filterName"+suffix, "");
		report.textSubstitution.setVariable("filterValue"+suffix, "");

		onDataChanged();
		me.thematic.commitChanges();
	};

	/** 
	 * Gets the list of filtered features.
	 *
 	 * @method getFilterFeatures
	 * @return {String[]} A list of feature ids.
	 */
	this.getFilteredFeatures = function() 
	{
		return filter.features;
	};

	/** 
	 * Clears the filter.
	 *
 	 * @method clearFilter
	 */
	this.clearFilter = function() 
	{
		filterChanged = true;

		filter.id = "";
		filter.name = "";
		filter.value = "";
		filter.features = [];

		report.textSubstitution.setVariable("filterName"+suffix, "");
		report.textSubstitution.setVariable("filterValue"+suffix, "");

		onDataChanged();
		me.thematic.commitChanges();
	};

	/** 
	 * Call this to update the DataGroup when the data.js has been changed.
	 *
 	 * @method update
 	 * @param {Function} callbackFnc Called on completion of function.
	 */
	this.update = function(callbackFnc) 
	{
		callbackFunction = callbackFnc; 
		initData();
	};

	/** 
	 * Builds the datagroup.
	 *
 	 * @method build
	 * @param {Function} callbackFnc Gets called when the DataGroup is complete.
	 */
	this.build = function(callbackFnc) 
	{
		callbackFunction = callbackFnc; 

		// Update text here to stop resizing of panels on startup.
		report.textSubstitution.setVariable("geogName"+suffix, " ");
		report.textSubstitution.setVariable("themeName"+suffix, " ");
		report.textSubstitution.setVariable("indicatorName"+suffix, " ");
		report.textSubstitution.setVariable("date"+suffix, " ");
		report.textSubstitution.setVariable("filterName"+suffix, "");
		report.textSubstitution.setVariable("filterValue"+suffix, "");
		report.textSubstitution.setVariable("legendType"+suffix, "");
		report.updateDynamicText(report.textSubstitution);

		// Thematics
		me.thematic = new ia.Thematic();
		me.thematic.noDataValue = report.locale.formatter.noDataValue;
		me.thematic.setDataField(report.config.getProperty("data"+suffix));
		me.thematic.addEventListener(ia.Event.THEME_CHANGED, function() 
		{
			me.comparisonThematic.commitChanges();
			thematicChanged = true;

			// Update the statictics substitution variables.
			updateStats();

			report.blockInteraction('onThemChangeProgress', false, function() 
			{ 
				render();
			});
		});

		// Categoric classifier.
		var paletteConfig = report.config.getMapPalette();
		var cClassifier = me.thematic.categoricClassifier;
		cClassifier.formatter = report.locale.formatter;
		cClassifier.colorPalette = paletteConfig.getColorScheme(paletteConfig.defaultSchemeId);

		me.legendSettings.schemeId = paletteConfig.defaultSchemeId;

		// Numeric classifier.
		if (suffix !== "") me.legendSettings.paletteId = report.config.getProperty("mapPalette"+suffix) || paletteConfig.defaultPaletteId;
		else me.legendSettings.paletteId = paletteConfig.defaultPaletteId;
		me.legendSettings.legendType = report.config.getProperty("legendClassifier"+suffix) || "quantile";
		
		var nClassifier = me.thematic.numericClassifier;
		nClassifier.noClasses = paletteConfig.noClasses;
		nClassifier.formatter = report.locale.formatter;
		nClassifier.classificationName = me.legendSettings.legendType;
		nClassifier.colorPalette = paletteConfig.getColorPalette(me.legendSettings.paletteId);
		nClassifier.sdLabels = [report.config.getProperty("sd1"),
					report.config.getProperty("sd2"),
					report.config.getProperty("sd3"),
					report.config.getProperty("sd4"),
					report.config.getProperty("sd5"),
					report.config.getProperty("sd6")];
		if (report.config.getProperty("sdSize")) nClassifier.getCalculator().sdSize = report.config.getProperty("sdSize");
		var legendPrecision = report.config.getProperty("legendPrecision")
		if (legendPrecision !== undefined && legendPrecision !== -1) nClassifier.precision = legendPrecision;

		// Comparison thematics.
		me.comparisonThematic = new ia.Thematic();
		me.comparisonThematic.setDataType(ia.Thematic.CATEGORIC);
		me.comparisonThematic.setDataField("id");

		// Initialise data.
		initData();
	};

	/** 
	 * Initialises the data for first use.
	 *
 	 * @method initData
	 * @private
	 */
	function initData()
	{
		dataChanged = true;

		// Use the index number to select a geography.
		var geog;
		var geogs = report.data.getGeographies();
		if (report.url.params["geog"+suffix]) geog = geogs[report.url.params["geog"+suffix]];
		if (geog === undefined) 
		{
			if ((report.config.template === ia.DOUBLE_GEOG_REPORT
			|| report.config.template === ia.DOUBLE_BASELAYER_REPORT
			|| report.config.template === ia.DOUBLE_BASELAYER_REPORT_NEW) && (suffix !== ""))
			{
				if (geogs.length >= suffix) geog = geogs[suffix-1];
				else geog = geogs[0];
			}
			else geog = geogs[0];
		}

		var geographyButton = report.getButton("geographyButton"+suffix)
		if (geographyButton)
		{
			if (geogs.length > 1) geographyButton.show();
			else geographyButton.hide();
		}

		var reverseDates = true;
		var c = report.config.getComponent("dataExplorer"+suffix);
		if (c)
		{
			reverseDates = c.getProperty("reverseDates");
			if (reverseDates === undefined) reverseDates = true;
		}

		var ind;
		if (report.url.params["indicator"+suffix]) 
		{
			if (report.url.params["date"+suffix]) ind = geog.getIndicator(report.url.params["indicator"+suffix], report.url.params["date"+suffix]);
			else ind = geog.getIndicator(report.url.params["indicator"+suffix], undefined, reverseDates);
		}
		if (ind === undefined) 
		{
			var ind = geog.getFirstIndicator(reverseDates);
			if (report.url.params["date"+suffix]) ind = geog.getIndicator(ind.id, report.url.params["date"+suffix]);
		}

		updateData(geog.id, ind.id, ind.date);
	};

	/** 
	 * Loads a new indicator.
	 *
 	 * @method updateData
	 * @param {String} geogId The geog id.
	 * @param {String} indicatorId The indicator id.
	 * @param {String} date The date.
	 * @private
	 */
	function updateData(geogId, indicatorId, date)
	{
		report.blockInteraction('onDataChangeProgress', true, function() 
		{
			var geog = report.data.getGeography(geogId);

			geog.loadIndicator(indicatorId, date, function(indicator)
			{
				// Check if geography changed.
				if (dataChanged || (indicator.geography.id !== me.indicator.geography.id)) 
				{
					onGeographyChanged(geog);

					// Check if all data need to be loaded.
					if (report.url.params["loadAllData"])
					{
						geog.loadData(function(g)
						{
							onIndicatorChanged(indicator);
						});
					}
					else  onIndicatorChanged(indicator);
				}
				else onIndicatorChanged(indicator);
			});
		});
	};
	
	/** 
	 * Called after an indicator has changed.
	 *
 	 * @method onIndicatorChanged
	 * @param {ia.Indicator} indicator The indicator.
	 * @private
	 */
	function onIndicatorChanged(indicator)
	{
		indicatorChanged = true;

		me.indicator = indicator;
		if (dataChanged || (me.theme.id !== indicator.theme.id)) themeChanged = true;
		me.theme = indicator.theme;
		me.geography = indicator.geography;

		onDataChanged();

		// Only set custom properties when a new indicator is loaded.
		checkForCustomThemeProperties(me.thematic, me.indicator);

		// Commit theme changes - this will also force a render();
		me.thematic.commitChanges();
	};

	/** 
	 * Called after a geography has changed.
	 *
 	 * @method onGeographyChanged
	 * @param {ia.Geography} indicator The indicator.
	 * @private
	 */
	function onGeographyChanged(geog)
	{
		geogChanged = true;
	
		if (dataChanged)
		{
			if (report.url.params["filteredFeatures"+suffix])
			{
				filter.features = report.url.params["filteredFeatures"+suffix].split(","); 
				filterChanged = true;
			}
			else if (report.url.params["filter"+suffix]) // filter=filter4,Crossroads
			{
				var arr = report.url.params["filter"+suffix].split(","); 
				var filterId = arr[0];
				var filterValue = arr[1];
				filterChanged = true;

				filter.id = filterId;
				filter.name = geog.getFilter(filterId).name;
				filter.value = filterValue;
				filter.features = geog.getFilteredFeatures(filter.id, filter.value);

				report.textSubstitution.setVariable("filterName"+suffix, filter.name);
				report.textSubstitution.setVariable("filterValue"+suffix, filter.value);
			}
		}
		else
		{
			// Clear the filter.
			filter.features = [];
			report.textSubstitution.setVariable("filterName"+suffix, "");
			report.textSubstitution.setVariable("filterValue"+suffix, "");
		}

		var filterBtn = report.getButton("filterButton"+suffix)
		if (filterBtn)
		{
			var filterList = geog.getFilters();
			if (filterList.length > 0) filterBtn.show()
			else filterBtn.hide();
		}

		if (me.mapData)
		{
			// This was part of a fix in case people make the base layer invisible.
			var layerIsVisible = me.mapData.baseLayer.getVisible();

			// Set previous active layer visibility to false.
			me.mapData.baseLayer.setVisible(false);

			// Set new active base layer - use the index of the geog
			// to get the correct base layer.
			// Default to first base layer if it cant be found
			me.mapData.baseLayer = me.mapData.baseLayers[geog.index];
			if (me.mapData.baseLayer === undefined) me.mapData.baseLayer = me.mapData.baseLayers[0]; 
			if (!me.mapData.baseLayer.isLoaded) me.mapData.baseLayer.loadSource();
			if (layerIsVisible) me.mapData.baseLayer.setVisible(true);

			// Set thematic and layer symbols according to layer geometry.
			var shp;
			var pal = me.thematic.numericClassifier.sizePalette;
			if (report.config.template === ia.BUBBLE_PLOT_REPORT && suffix === "2")
			{
				var c = report.config.getComponent("scatterPlot");
				if (c)
				{
					shp = "circle";
					me.thematic.setDataField(c.getProperty("sizeData"));
					me.legendSettings.legendType = "continuous";
					pal.minSize = c.getProperty("minBubbleSize");
					pal.maxSize = c.getProperty("maxBubbleSize");
					me.thematic.categoricClassifier.symbolSize = 8;
				}
			}
			else if (me.mapData.baseLayer.geometry === "point") // Point layer.
			{
				shp = report.config.getProperty("symbolShape"+suffix);
				pal.minSize = report.config.getProperty("minSymbolSize"+suffix);
				pal.maxSize = report.config.getProperty("maxSymbolSize"+suffix);
				me.thematic.categoricClassifier.symbolSize = me.mapData.baseLayer.symbolSize;
			}
			else if (me.mapData.baseLayer.geometry === "line") // Line layer.
			{
				shp = ia.Shape.LINE;

				pal.minSize = report.config.getProperty("minLineSize"+suffix);
				pal.maxSize = report.config.getProperty("maxLineSize"+suffix);
				me.thematic.categoricClassifier.symbolSize = me.mapData.baseLayer.style.lineWidth;
			}
			else // Polygon layer.
			{
				shp = ia.Shape.SQUARE;

				// Use base layer symbol size when its a polygon layer.
				pal.minSize = me.mapData.baseLayer.symbolSize;
				pal.maxSize = me.mapData.baseLayer.symbolSize;
				me.thematic.categoricClassifier.symbolSize = me.mapData.baseLayer.symbolSize;
			}
			me.mapData.baseLayer.symbol = shp;
			me.thematic.symbol = shp;
		}
	};

	/** 
	 * Called after the data have changed - this can be if a new indicator
	 * has been selected or if a filter has been applied.
	 *
 	 * @method onDataChanged
	 * @private
	 */
	function onDataChanged()
	{
		// Update the indicator data object used by the report components.
		if (filter.features.length > 0) 
		{
			me.indicatorData = me.indicator.getData(filter.features);
			me.themeData = me.theme.getData(me.indicator.id, filter.features);
		}
		else 
		{
			me.indicatorData = me.indicator.getData();
			me.themeData = me.theme.getData(me.indicator.id);
		}

		// Update the comparison data object used by the report components.
		me.comparisonData = me.indicator.getComparisonData();
		me.comparisonThemeData = me.theme.getComparisonData(me.indicator.id);
		me.comparisonThematic.setData(me.comparisonData);

		// Thematic.
		var dataType = me.indicator.getDataType(me.thematic.getDataField());
		me.thematic.setDataType(dataType);
		me.thematic.setData(me.indicatorData);
		
		// Substitution.
		report.textSubstitution.setVariable("geogName"+suffix, me.geography.name);
		report.textSubstitution.setVariable("themeName"+suffix, me.theme.getParentThemes());
		report.textSubstitution.setVariable("indicatorName"+suffix, me.indicator.name);
		report.textSubstitution.setVariable("date"+suffix, me.indicator.date);

		// Properties.
		var props = me.indicator.getProperties();
		for (var propName in props) {report.textSubstitution.setVariable(propName+""+suffix, props[propName]);}

		report.updateDynamicText(report.textSubstitution);
	};

	/** 
	 * Responsible for rendering all the components.
	 *
 	 * @method render
	 * @private
	 */
	function render()
	{ 
		// Events.
		if (geogChanged) 
			me.dispatchEvent(new ia.DataEvent(ia.DataEvent.GEOG_CHANGED, me, me.geography, me.theme, me.indicator));
		if (filterChanged) 
		{
			// Update report url filter params.
			if (filter.features.length > 0) report.url.params["filteredFeatures"+suffix] = filter.features.join(",");
			else report.url.params["filteredFeatures"+suffix] = "";

			me.dispatchEvent(new ia.FilterEvent(ia.FilterEvent.FILTER_CHANGED, me, filter.id, filter.name, filter.value, filter.features));
		}
		if (themeChanged)  
			me.dispatchEvent(new ia.DataEvent(ia.DataEvent.THEME_CHANGED, me, me.geography, me.theme, me.indicator));
		if (indicatorChanged) 
		{
			// Update report url data params.
			report.url.params["geog"+suffix] = me.geography.index;
			report.url.params["indicator"+suffix] = me.indicator.id;
			if (me.indicator.date) report.url.params["date"+suffix] = me.indicator.date;
			else report.url.params["date"+suffix] = ""; 

			me.dispatchEvent(new ia.DataEvent(ia.DataEvent.INDICATOR_CHANGED, me, me.geography, me.theme, me.indicator));
		}
		if (indicatorChanged || filterChanged) 
			me.dispatchEvent(new ia.DataEvent(ia.DataEvent.DATA_CHANGED, me, me.geography, me.theme, me.indicator));
		if (thematicChanged) 
		{
			updateThematicParams();
			me.dispatchEvent(new ia.Event(ia.Event.THEMATIC_CHANGED, me));
		}

		report.allowInteraction('onThemChangeProgress');
		report.allowInteraction('onDataChangeProgress');

		if (dataChanged && callbackFunction)  
		{
			dataChanged = false;
			callbackFunction.call(null);
		}

		geogChanged = false;
		filterChanged = false;
		indicatorChanged = false;
		themeChanged = false;
		thematicChanged = false;
	};

	/** 
	 * Checks if the loaded indicator has custom legend properties.
	 *
 	 * @method checkForCustomThemeProperties
	 * @param {ia.Thematic} me.thematic The me.thematic.
	 * @param {ia.Indicator} indicator The indicator.
	 * @private
	 */
	function checkForCustomThemeProperties(thematic, indicator)
	{
		var paletteConfig = report.config.getMapPalette();

		var customClassifier = indicator.getProperty(report.config.getProperty("customClassifierKey"+suffix));
		var customColours = indicator.getProperty(report.config.getProperty("customColoursKey"+suffix));
		var customPalette = indicator.getProperty(report.config.getProperty("customPaletteKey"+suffix));
		var customBreaks = indicator.getProperty(report.config.getProperty("customBreaksKey"+suffix));
		var customLabels = indicator.getProperty(report.config.getProperty("customLabelsKey"+suffix));

		// Added these for ECDC.
		var customNoClasses = indicator.getProperty("customNoClasses");

		// Default settings for categoric.
		var cClassifier = me.thematic.categoricClassifier;
		cClassifier.breaks = [];
		cClassifier.labels = [];
		cClassifier.colorPalette = paletteConfig.getColorScheme(me.legendSettings.schemeId);

		// Default settings for numeric.
		var nClassifier = me.thematic.numericClassifier;
		nClassifier.labels = [];
		nClassifier.classificationName = me.legendSettings.legendType;
		nClassifier.colorPalette = paletteConfig.getColorPalette(me.legendSettings.paletteId);

		if(indicator.type === ia.Thematic.CATEGORIC)
		{
			report.textSubstitution.setVariable("legendType"+suffix, "");
			report.updateDynamicText(report.textSubstitution);

			if (customBreaks) cClassifier.breaks = customBreaks.split(";");
			if (customLabels) cClassifier.labels = customLabels.split(";");
			if (customPalette) cClassifier.colorPalette = paletteConfig.getColorScheme(customPalette);
			if (customColours)
			{
				var colorList = customColours.split(";");
				cClassifier.colorPalette = new ia.ColorPalette(colorList);
			}
		}
		else
		{
			if (customClassifier) nClassifier.classificationName = customClassifier;

			report.textSubstitution.setVariable("legendType"+suffix, nClassifier.classificationName);
			report.updateDynamicText(report.textSubstitution);

			if (customBreaks)
			{
				var breaksList = customBreaks.split(";");
				function fncCustomBreaks(noClasses) {return breaksList;};
				nClassifier.getCalculator().addFunction("customClassifier", fncCustomBreaks);
				nClassifier.classificationName = "customClassifier";
			}

			if (customNoClasses) nClassifier.noClasses = customNoClasses;

			if (customLabels) nClassifier.labels  = customLabels.split(";");
			if (customPalette) nClassifier.colorPalette = paletteConfig.getColorPalette(customPalette);
			if (customColours)
			{
				var colorList = customColours.split(";");
				nClassifier.colorPalette = new ia.ColorPalette(colorList);
			}
		}
	};

	/** 
	 * Updates the params for the thematics.
	 *
 	 * @method updateThematicParams
	 * @private
	 */
	function updateThematicParams()
	{
		// Classification name
		report.url.params["prop_legendClassifier"+suffix] = me.thematic.numericClassifier.classificationName;

		// Map Palette settings
		if (suffix === "") 
		{
			report.url.params["pal_defaultPaletteId"] = me.legendSettings.paletteId;
			report.url.params["pal_defaultSchemeId"] = me.legendSettings.schemeId;
			report.url.params["pal_noClasses"] = me.thematic.numericClassifier.noClasses;
		}

		// Palett for second map.
		if (suffix === "2") report.url.params["prop_mapPalette2"] = me.legendSettings.paletteId;

		// Symbol sizes.
		var pal = me.thematic.numericClassifier.sizePalette;
		if (report.config.template === ia.BUBBLE_PLOT_REPORT && suffix === "2") // Bubble plot.
		{
			var c = report.config.getComponent("scatterPlot");
			if (c)
			{
				report.url.params["prop_minBubbleSize"+suffix] = pal.minSize;
				report.url.params["prop_maxBubbleSize"+suffix] = pal.maxSize;
			}
		}
		else if (me.mapData.baseLayer.geometry === "point") // Point layer.
		{
			report.url.params["prop_minSymbolSize"+suffix] = pal.minSize;
			report.url.params["prop_maxSymbolSize"+suffix] = pal.maxSize;
		}
		else if (me.mapData.baseLayer.geometry === "line") // Line layer.
		{
			report.url.params["prop_minLineSize"+suffix] = pal.minSize;
			report.url.params["prop_maxLineSize"+suffix] = pal.maxSize;
		}
	};

	/** 
	 * Updates the statictics substitution variables.
	 *
 	 * @method updateStats
	 * @private
	 */
	function updateStats()
	{
		// Stats Substitution.
		var dataType = me.indicator.getDataType(me.thematic.getDataField());
		if (dataType === ia.Thematic.CATEGORIC)
		{
			report.textSubstitution.setVariable("mean"+suffix, "");
			report.textSubstitution.setVariable("median"+suffix, "");
			report.textSubstitution.setVariable("sum"+suffix, "");
			report.textSubstitution.setVariable("minValue"+suffix, "");
			report.textSubstitution.setVariable("maxValue"+suffix, "");
			report.textSubstitution.setVariable("range"+suffix, "");
			report.textSubstitution.setVariable("lowerQuartile"+suffix, "");
			report.textSubstitution.setVariable("upperQuartile"+suffix, "");
			report.textSubstitution.setVariable("interquartileRange"+suffix, "");
			report.textSubstitution.setVariable("variance"+suffix, "");
			report.textSubstitution.setVariable("standardDeviation"+suffix, "");
		}
		else
		{
			var p = me.indicator.precision || 2;
			if (p === undefined) p = 2;
			var f = report.locale.formatter;
			var stats = me.thematic.numericClassifier.getCalculator().getStats();
			report.textSubstitution.setVariable("sum"+suffix, f.format(stats.sum, p));
			report.textSubstitution.setVariable("mean"+suffix, f.format(stats.mean, p));
			report.textSubstitution.setVariable("median"+suffix, f.format(stats.median, p));
			report.textSubstitution.setVariable("minValue"+suffix, f.format(stats.minValue, p));
			report.textSubstitution.setVariable("maxValue"+suffix, f.format(stats.maxValue, p));
			report.textSubstitution.setVariable("range"+suffix, f.format(stats.range, p));
			report.textSubstitution.setVariable("lowerQuartile"+suffix, f.format(stats.lowerQuartile, p));
			report.textSubstitution.setVariable("upperQuartile"+suffix, f.format(stats.upperQuartile, p));
			report.textSubstitution.setVariable("interquartileRange"+suffix, f.format(stats.interquartileRange, p));
			report.textSubstitution.setVariable("variance"+suffix, f.format(stats.variance, p));
			report.textSubstitution.setVariable("standardDeviation"+suffix, f.format(stats.standardDeviation, p));
		}
		report.updateDynamicText(report.textSubstitution);
	};
};
ia.extend(ia.EventDispatcher, ia.DataGroup);