/**
* 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);