/**
* A class for rendering a data table.
* for rendering a data table.
*
* @author J Clare
* @class ia.Profile
* @extends ia.EventDispatcher
* @constructor
* @param {String} id The id of the table.
* @param {Boolean} useMouseClick Indicates if an event should be fired when an indicator is clicked.
* @param {Function} callbackFunction Called when a selection is made.
*/
ia.Profile = function(id, useMouseClick, callbackFunction)
{
ia.Profile.baseConstructor.call(this);
this.displayMode = "Selected date";
this.displayDatesInProfile = false;
this.symbols = [];
this.targets = [];
this.breaks = [];
this.id = id;
this._useMouseClick = useMouseClick;
this._data = undefined;
this._isCollapsed = false;
this._tip = undefined;
this._renderTimeout = null;
this._featureIds = []; // A list of feature ids.
this._collapseIds = []; // A list of collapsed ids.
// A div to contain the table and allow correct scrolling.
this.container = $j("<div id='"+id+"' class='ia-table'>");
this._containerId = id;
this.$tableCorner = $j("<span class='ia-table-header-corner'>");
this.container.append(this.$tableCorner);
// The table used to render the headers.
this.$tableHeaders = $j("<table>");
this.container.append(this.$tableHeaders);
// The table used to render the data.
this.$tableContainer = $j("<div id='"+id+"-container' class='ia-table-scrollbox'>");
this._scrollBox = new ia.ScrollBox(this.$tableContainer);
this.container.append(this.$tableContainer);
this.$table = $j("<table>");
this.$tableContainer.append(this.$table);
this._addMouseEvents();
if (callbackFunction) this.callbackFunction = callbackFunction;
var resizeTimeout;
var me = this;
this.container.resize(function(e)
{
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function()
{
clearTimeout(resizeTimeout);
me._size();
}, 250);
});
};
ia.extend(ia.EventDispatcher, ia.Profile);
/**
* The id.
*
* @property id
* @type String
*/
ia.Profile.prototype.id;
/**
* The callback function.
*
* @property callbackFunction
* @type Function
*/
ia.Profile.prototype.callbackFunction;
/**
* Specifies a color palette for the bars.
*
* @property colorPalette
* @type ia.ColorPalette
*/
ia.Profile.prototype.colorPalette;
/**
* Specifies a geography.
*
* @property geography
* @type ia.Geography
*/
ia.Profile.prototype.geography;
/**
* Specifies a selected indicator.
*
* @property indicator
* @type ia.Indicator
*/
ia.Profile.prototype.indicator;
/**
* Display indicators for "Selected date","Most recent date", "All dates"?
*
* @property displayMode
* @type String
* @default Selected date
*/
ia.Profile.prototype.displayMode;
/**
* Display the dates in the profile?
*
* @property displayDatesInProfile
* @type Boolean
* @default false
*/
ia.Profile.prototype.displayDatesInProfile;
/**
* The container that holds the object.
*
* @property container
* @type JQUERY Element
*/
ia.Profile.prototype.container;
/**
* The columns to be rendered.
*
* The columns are an array with the following structure:
* They dictate which columns should be used from the data.
* The id points to the id in the data.
*
* [{id:"name", label:"Features", type:"categoric"},
* {id:"value", label:"Indicator", type:"numeric"},
* {id:"associate1", label:"Associate 1", type:"numeric'"},
* {id:"associate2", label:"Associate 2", type:"categoric'"}]
*
* @property columns
* @type JSON
*/
ia.Profile.prototype.columns;
/**
* A list of objects that describe symbols.
* [{shape:"arrow down", color:"#EC6768", size:"12", label:"Poor", value:"--"}]
*
* @property symbols
* @type Object[]
*/
ia.Profile.prototype.symbols;
/**
* A list of objects that describe targets.
* [{shape:"vertical line", color:"#EC6768", size:"12", label:"Poor", data:"target"}]
*
* @property targets
* @type Object[]
*/
ia.Profile.prototype.targets;
/**
* A list of objects that describe breaks for the performance chart.
* [{color:"#EC6768", label:"Poor"}]
*
* @property breaks
* @type Object[]
*/
ia.Profile.prototype.breaks;
/**
* An object that describe a bar.
* {color:"#EC6768", height:"12", data:"diff"}
*
* @property bar
* @type Object
*/
ia.Profile.prototype.bar;
/**
* Sizes all the element to make the scrolling work.
*
* @method _size
* @private
*/
ia.Profile.prototype._size = function()
{
var containerWidth = this.container.width()
var scrollBarWidth = this.$tableContainer.get(0).offsetWidth - this.$tableContainer.get(0).clientWidth;
var tableWidth = containerWidth - scrollBarWidth
this.$table.width(tableWidth);
this.$tableHeaders.width(tableWidth);
var containerHeight = this.container.height();
var headerHeight = this.$tableHeaders.outerHeight();
var contentHeight = containerHeight - headerHeight;
this.$tableContainer.height(contentHeight);
var bgcolor = $j(".ia-table-header").css("background-color");
this.$tableCorner.css("background-color",bgcolor)
this.$tableCorner.width(scrollBarWidth);
this.$tableCorner.height(headerHeight);
this._renderCanvasColumns();
};
/**
* Expands the passed theme ids.
*
* @method expandThemes
* @param String[] expandedThemeIds The theme levels that should be expanded.
*/
ia.Profile.prototype.expandThemes = function(expandedThemeIds)
{
if (this._data)
{
var me = this;
var n = expandedThemeIds.length;
this._isCollapsed = false;
this._collapseIds = [];
var themes = this._data.themes;
$j.each(themes, function(tIndex, theme)
{
var isCollapsed = true;
for (var i = 0; i < n; i++)
{
if (theme.id === expandedThemeIds[i])
{
isCollapsed = false;
me.$table.find("td[id='"+theme.id+"']").removeClass("ia-profile-theme-expand").addClass("ia-profile-theme-collapse");
me.$table.find("tbody[id='"+theme.id+"-children']").show();
break;
}
}
if (isCollapsed === true)
{
me._collapseIds[me._collapseIds.length] = theme.id;
me.$table.find("td[id='"+theme.id+"']").removeClass("ia-profile-theme-collapse").addClass("ia-profile-theme-expand");
me.$table.find("tbody[id='"+theme.id+"-children']").hide();
}
});
}
};
/**
* Collapse / expands the table.
*
* @method toggleTree
*/
ia.Profile.prototype.toggleTree = function()
{
if (this._data)
{
var me = this;
this._isCollapsed = !this._isCollapsed;
this._collapseIds = [];
var themes = this._data.themes;
if (this._isCollapsed)
{
$j.each(themes, function(tIndex, theme)
{
me._collapseIds[me._collapseIds.length] = theme.id;
me.$table.find("td[id='"+theme.id+"']").removeClass("ia-profile-theme-collapse").addClass("ia-profile-theme-expand");
me.$table.find("tbody[id='"+theme.id+"-children']").hide();
});
}
else
{
$j.each(themes, function(tIndex, theme)
{
me.$table.find("td[id='"+theme.id+"']").removeClass("ia-profile-theme-expand").addClass("ia-profile-theme-collapse");
me.$table.find("tbody[id='"+theme.id+"-children']").show();
});
}
}
};
/**
* Renders the table.
*
* @method render
*/
ia.Profile.prototype.render = function()
{
this._renderTimeout = null;
if (this.displayMode === undefined) this.displayMode = "Selected date";
if (this.displayMode === "Selected date")
this._data = this.geography.getProfileData(this._featureIds, this.indicator.date, true);
else if (this.displayMode === "Most recent date")
this._data = this.geography.getProfileData(this._featureIds, undefined, true);
else // All Dates.
this._data = this.geography.getProfileData(this._featureIds);
// Empty the previous table.
this.$table.empty();
this.$tableHeaders.empty();
// Add the col section.
var thead = $j("<thead>");
var tr = $j("<tr>");
thead.append(tr)
this.$tableHeaders.append(thead)
// Iterate through each col.
var nCol = this.columns.length;
for (var i = 0; i < nCol; i++)
{
var col = this.columns[i];
var colWidth = col.width*100;
if (col.id === "performance")
{
// The col label.
var w = colWidth * 0.15
var thLabel = $j("<th id='"+col.id+"' class='ia-table-header' style='width:"+w+"%'>");
tr.append(thLabel);
w = colWidth * 0.7
var thLabel = $j("<th id='"+col.id+"' class='ia-table-header' style='width:"+w+"%;border-left-width:0px'>");
thLabel.html(col.label);
tr.append(thLabel);
w = colWidth * 0.15
var thLabel = $j("<th id='"+col.id+"' class='ia-table-header' style='width:"+w+"%;border-left-width:0px'>");
tr.append(thLabel);
}
else
{
// The col label.
var thLabel = $j("<th id='"+col.id+"' class='ia-table-header' style='width:"+colWidth+"%'>");
thLabel.html(col.label);
tr.append(thLabel);
}
// Area Profile.
if (col.id === "profile")
{
var midValue = this.bar.minValue + ((this.bar.maxValue - this.bar.minValue)/2);
var div = $j("<div style='display:table;width:100%'>");
var leftDiv = $j("<div style='display:table-cell;width:30%;text-align:left'>").html(this.bar.minValue);
var centreDiv = $j("<div style='display:table-cell;width:40%'>").html(midValue);
var rightDiv = $j("<div style='display:table-cell;width:30%;text-align:right'>").html(this.bar.maxValue);
div.append(leftDiv);
div.append(centreDiv);
div.append(rightDiv);
thLabel.append(div);
// Calculate the canvas height.
col.canvasHeight = this.bar.height;
var n = this.targets.length;
for (var j = 0; j < n; j++)
{
var target = this.targets[j];
col.canvasHeight = Math.max(col.canvasHeight, target.size);
}
}
// Election.
else if (col.id === "election")
{
// Calculate the canvas height.
col.canvasHeight = this.bar.height;
var n = this.targets.length;
for (var j = 0; j < n; j++)
{
var target = this.targets[j];
col.canvasHeight = Math.max(col.canvasHeight, target.size);
}
}
// Performance.
else if (col.id === "performance")
{
// Calculate the canvas height.
col.canvasHeight = this.bar.height;
var n = this.targets.length;
for (var j = 0; j < n; j++)
{
var target = this.targets[j];
col.canvasHeight = Math.max(col.canvasHeight, target.size);
}
// Add a bit more so breaks are visible
col.canvasHeight = col.canvasHeight + 8;
}
// Health.
else if (col.id.indexOf("health") !== -1)
{
var div = $j("<div style='display:table;width:100%'>");
var leftDiv = $j("<div style='display:table-cell;width:30%;text-align:left'>").html(this.bar.minValue);
var centreDiv = $j("<div style='display:table-cell;width:40%'>").html(this.bar.midValue);
var rightDiv = $j("<div style='display:table-cell;width:30%;text-align:right'>").html(this.bar.maxValue);
div.append(leftDiv);
div.append(centreDiv);
div.append(rightDiv);
thLabel.append(div);
// Calculate the canvas height.
col.canvasHeight = 0;
var n = this.targets.length;
for (var j = 0; j < n; j++)
{
var target = this.targets[j];
col.canvasHeight = Math.max(col.canvasHeight, target.size);
}
var n = this.symbols.length;
for (var j = 0; j < n; j++)
{
var symbol = this.symbols[j];
col.canvasHeight = Math.max(col.canvasHeight, symbol.size);
}
// Symbol column properties.
var propsString = col.id.substring(col.id.indexOf("(")+1, col.id.indexOf(")"));
var propsArray = propsString.split(",");
col.symbolAlign = "right"; // default value.
for (var j = 0; j < propsArray.length; j++)
{
var prop = propsArray[j].split(":");
var propName = prop[0];
var propValue = prop[1];
col[propName] = propValue;
}
}
// Symbol column.
else if (col.id.indexOf("symbol(") !== -1)
{
// Symbol column properties.
var propsString = col.id.substring(col.id.indexOf("(")+1, col.id.indexOf(")"));
var propsArray = propsString.split(",");
col.symbolAlign = "right"; // default value.
for (var j = 0; j < propsArray.length; j++)
{
var prop = propsArray[j].split(":");
var propName = prop[0];
var propValue = prop[1];
col[propName] = propValue;
}
}
}
// Dummy hidden row to maintain column widths - which are lost if theme colspan is applied first.
var tr = $j("<tr class='ia-table-row'>");
this.$table.append(tr);
var adjustedNCol = nCol;
for (var j = 0; j < nCol; j++)
{
var col = this.columns[j];
var colWidth = col.width*100;
if (col.id === "performance" || col.id.indexOf("health") !== -1)
{
adjustedNCol = nCol + 2;
var w = colWidth * 0.15
var td = $j("<td class='ia-table-cell' style='width:"+w+"%;padding-top:0px;padding-bottom:0px;margin-top:0px;margin-bottom:0px'>");
tr.append(td);
w = colWidth * 0.7
var td = $j("<td class='ia-table-cell' style='width:"+w+"%;padding-top:0px;padding-bottom:0px;margin-top:0px;margin-bottom:0px'>");
tr.append(td);
w = colWidth * 0.15
var td = $j("<td class='ia-table-cell' style='width:"+w+"%;padding-top:0px;padding-bottom:0px;margin-top:0px;margin-bottom:0px'>");
tr.append(td);
}
else
{
var td = $j("<td class='ia-table-cell' style='width:"+colWidth+"%;padding-top:0px;padding-bottom:0px;margin-top:0px;margin-bottom:0px'>");
tr.append(td);
}
}
// Themes.
var themes = this._data.themes;
var me = this;
$j.each(themes, function(tIndex, theme)
{
var tBody = $j("<tbody>");
me.$table.append(tBody);
var tr = $j("<tr class='ia-table-row'>");
tBody.append(tr);
var td = $j("<td id='"+theme.id+"' class='ia-table-cell ia-profile-theme-name-cell ia-profile-theme-collapse ia-profile-row' colspan='"+adjustedNCol+"'>");
td.html(theme.name);
tr.append(td);
var tBody = $j("<tbody id='"+theme.id+"-children'>");
me.$table.append(tBody);
// Indicators.
var indicators = theme.indicators;
$j.each(indicators, function(iIndex, indicator)
{
var features = indicator.features;
// No features selected.
if (features.length === 0)
{
// Style even and odd rows differently.
if (iIndex%2 === 0)
var tr = $j("<tr class='ia-table-row ia-profile-row ia-table-row-even indicator-row'>");
else
var tr = $j("<tr class='ia-table-row ia-profile-row ia-table-row-odd indicator-row'>");
if (indicator.date !== undefined) tr.data('linkId', indicator.id+"~"+indicator.date);
else tr.data('linkId', indicator.id);
tBody.append(tr);
// Iterate through each table cell in the row - match to columns.
for (var j = 0; j < nCol; j++)
{
var col = me.columns[j];
var colWidth = col.width*100;
if (col.id === "performance" || col.id.indexOf("health") !== -1)
{
var w = colWidth * 0.15
var td = $j("<td class='ia-table-cell' style='width:"+w+"%'>");
tr.append(td);
w = colWidth * 0.7
var td = $j("<td class='ia-table-cell' style='width:"+w+"%;border-left-width:0px'>");
tr.append(td);
w = colWidth * 0.15
var td = $j("<td class='ia-table-cell' style='width:"+w+"%;border-left-width:0px'>");
tr.append(td);
}
else if (col.id === "indicatorName")
{
var $notesBtn;
var td = $j("<td class='ia-profile-indicator-name-cell' style='width:"+colWidth+"%'>");
if (me.displayDatesInProfile)
{
if (indicator.href)
{
$notesBtn = $j("<span class='ia-table-notes-icon'>");
td.append($notesBtn);
}
if (indicator.date) td.append(indicator.name+" ("+indicator.date+")");
else td.append(indicator.name);
}
else
{
if (indicator.href)
{
$notesBtn = $j("<span class='ia-table-notes-icon'>");
td.append($notesBtn);
}
td.append(indicator.name);
}
tr.append(td);
if (indicator.href)
{
(function() // Execute immediately
{
var link = indicator.href;
$notesBtn.bind(ia.CLICK_TYPE, function(e)
{
e.stopPropagation();
window.open(link, "_blank");
});
$notesBtn.bind('mouseover touchstart', function(e)
{
e.stopPropagation();
});
})();
}
}
else
{
var td = $j("<td class='ia-table-cell' style='width:"+colWidth+"%'>");
tr.append(td);
}
}
}
// Features.
$j.each(features, function(fIndex, feature)
{
// Style even and odd rows differently.
if (iIndex%2 === 0)
var tr = $j("<tr class='ia-table-row ia-profile-row ia-table-row-even indicator-row'>");
else
var tr = $j("<tr class='ia-table-row ia-profile-row ia-table-row-odd indicator-row'>");
if (indicator.date !== undefined) tr.data('linkId', indicator.id+"~"+indicator.date);
else tr.data('linkId', indicator.id);
tBody.append(tr);
// Columns.
for (var j = 0; j < nCol; j++)
{
var col = me.columns[j];
var formattedData = feature[col.formattedId];
var colWidth = col.width*100;
// First indicator row and with indicator name column.
if (fIndex === 0 && col.id === "indicatorName")
{
var $notesBtn;
var td = $j("<td class='ia-profile-indicator-name-cell' rowspan='"+features.length+"' style='width:"+colWidth+"%'>");
if (me.displayDatesInProfile)
{
if (indicator.href)
{
$notesBtn = $j("<span class='ia-table-notes-icon'>");
td.append($notesBtn);
}
if (indicator.date) td.append(indicator.name+" ("+indicator.date+")");
else td.append(indicator.name);
}
else
{
if (indicator.href)
{
$notesBtn = $j("<span class='ia-table-notes-icon'>");
td.append($notesBtn);
}
td.append(indicator.name);
}
tr.append(td);
if (indicator.href)
{
(function() // Execute immediately
{
var link = indicator.href;
$notesBtn.bind(ia.CLICK_TYPE, function(e)
{
e.stopPropagation();
window.open(link, "_blank");
});
$notesBtn.bind('mouseover touchstart', function(e)
{
e.stopPropagation();
});
})();
}
}
// Feature name column
else if (col.id === "name")
{
var td = $j("<td class='ia-table-cell ia-table-cell-categoric' style='width:"+colWidth+"%'>");
td.html(formattedData);
tr.append(td);
}
// Area Profile.
else if (col.id === "profile" || col.id === "election")
{
var td = $j("<td class='ia-table-cell ia-profile-cell' style='width:"+colWidth+"%'>");
var canvas = document.createElement('canvas');
canvas.width = 0;
canvas.height = 0;
td.append($j(canvas));
// Attach data values.
var barValue = feature[me.bar.data];
var symbolValue = feature[me.bar.symbolValue];
td.data('id', feature.id);
td.data('barValue', barValue);
td.data('canvasHeight', col.canvasHeight);
td.data('symbolValue', symbolValue);
// Attach target data values.
var n = me.targets.length;
for (var i = 0; i < n; i++)
{
var target = me.targets[i];
var targetValue = feature[target.data];
td.data('targetValue'+i, targetValue);
}
tr.append(td);
}
// Peformance.
else if (col.id === "performance" || col.id.indexOf("health") !== -1)
{
var w = colWidth * 0.15
var minValueCell = $j("<td class='ia-table-cell' style='text-align:right' style='width:"+w+"%'>");
tr.append(minValueCell);
w = colWidth * 0.7
var td = $j("<td class='ia-table-cell ia-performance-cell' style='width:"+w+"%;border-left-width:0px'>");
if (col.id === "performance") td.addClass('ia-performance-cell');
else if (col.id.indexOf("health") !== -1)
{
td.addClass('ia-health-cell');
var symbolValue = feature[col.symbolValue];
var areaValue = feature[col.areaValue];
var nationalValue = feature[col.nationalValue];
td.data('symbolValue', symbolValue);
td.data('areaValue', areaValue);
td.data('nationalValue', nationalValue);
}
tr.append(td);
var canvas = document.createElement('canvas');
canvas.width = 0;
canvas.height = 0;
td.append($j(canvas));
w = colWidth * 0.15
var maxValueCell = $j("<td class='ia-table-cell' style='text-align:left;border-left-width:0px' style='width:"+w+"%'>");
tr.append(maxValueCell);
// Attach data values.
var barValue = feature[me.bar.data];
var breaksFlip = feature[me.bar.breaksFlip];
td.data('id', feature.id);
td.data('barValue', barValue);
td.data('breaksFlip', breaksFlip);
td.data('canvasHeight', col.canvasHeight);
// Attach target data values.
var n = me.targets.length;
for (var i = 0; i < n; i++)
{
var target = me.targets[i];
var targetValue = feature[target.data];
td.data('targetValue'+i, targetValue);
}
// Attach breaks values.
if (me.bar.breaksData)
{
var breaksString = feature[me.bar.breaksData];
if (breaksString !== undefined)
{
var breaksArray = breaksString.split(",");
var minValue = Infinity;
var maxValue = -Infinity;
var n = breaksArray.length;
for (var i = 0; i < n; i++)
{
var breakValue = breaksArray[i];
minValue = Math.min(minValue, breakValue);
maxValue = Math.max(maxValue, breakValue);
td.data('breakValue'+i, breakValue);
}
td.data('minValue', minValue);
td.data('maxValue', maxValue);
minValueCell.html(minValue);
maxValueCell.html(maxValue);
}
}
}
// Symbol column.
else if (col.id.indexOf("symbol(") !== -1)
{
var symbolValue = feature[col.symbolValue];
if (col.textValue !== undefined)
{
var textValue = feature[col.textValue+"_formatted"];
var td = $j("<td class='ia-table-cell ia-symbol-cell' style='width:"+colWidth+"%'>");
td.data('symbolValue', symbolValue);
var div = $j("<div style='display:table'>");
td.append(div);
var textDiv = $j("<div style='display:table-cell;vertical-align:middle;width:100%'>")
if (textValue !== undefined) textDiv.html(textValue+" ");
var type = indicator.type;
if (col.textValue !== "value") type = feature[col.textValue+"_type"];
if (type === "categoric") textDiv.addClass("ia-table-cell-categoric");
else textDiv.addClass("ia-table-cell-numeric");
var symbolDiv = $j("<div style='display:table-cell;vertical-align:middle'>");
var canvas = document.createElement('canvas');
canvas.width = 0;
canvas.height = 0;
symbolDiv.append($j(canvas));
if (col.symbolAlign === "right")
{
div.append(textDiv);
div.append(symbolDiv);
}
else if (col.symbolAlign === "left")
{
div.append(symbolDiv);
div.append(textDiv);
}
}
else
{
var td = $j("<td class='ia-table-cell ia-symbol-cell' style='width:"+colWidth+"%'>");
td.data('symbolValue', symbolValue);
var symbolDiv = $j("<div style='vertical-align:middle;text-align:"+col.symbolAlign+";width:100%'>");
var canvas = document.createElement('canvas');
canvas.width = 0;
canvas.height = 0;
symbolDiv.append($j(canvas));
td.append(symbolDiv);
}
tr.append(td);
}
// Indicator column.
else if (col.id === "value")
{
if (indicator.type === "categoric")
var td = $j("<td class='ia-table-cell ia-table-cell-categoric' style='width:"+colWidth+"%'>");
else
var td = $j("<td class='ia-table-cell ia-table-cell-numeric' style='width:"+colWidth+"%'>");
if (formattedData !== undefined) td.html(formattedData);
tr.append(td);
}
// Associate column.
else if (col.id !== "indicatorName" && col.id !== "name")
{
var type = feature[col.id+"_type"];
if (type === "categoric")
var td = $j("<td class='ia-table-cell ia-table-cell-categoric' style='width:"+colWidth+"%'>");
else
var td = $j("<td class='ia-table-cell ia-table-cell-numeric' style='width:"+colWidth+"%'>");
if (formattedData !== undefined) td.html(formattedData);
tr.append(td);
}
}
});
});
});
// Check if any themes have been collapsed.
for (var i = 0; i < this._collapseIds.length; i++)
{
var id = this._collapseIds[i];
this.$table.find("td[id='"+id+"']").removeClass("ia-profile-theme-collapse").addClass("ia-profile-theme-expand");
this.$table.find("tbody[id='"+id+"-children']").hide();
}
this._scrollBox.refresh();
this._size();
};
/**
* Renders special columns.
*
* @method _renderCanvasColumns
* @private
*/
ia.Profile.prototype._renderCanvasColumns = function()
{
// Area Profile.
var me = this;
this.$table.find('tbody > tr > td.ia-profile-cell').each(function()
{
var features = [];
var id = $j(this).data('id');
var barValue = $j(this).data('barValue');
var range = me.bar.maxValue - me.bar.minValue;
var symbolValue = $j(this).data('symbolValue');
var canvas = $j(this).find("canvas:first").get(0);
var canvasWidth = $j(this).width();
var canvasHeight = $j(this).data('canvasHeight');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
var ctx = canvas.getContext("2d");
ctx.clearRect( 0, 0, canvas.width, canvas.height);
// Draw bar.
if (barValue !== undefined)
{
var barWidth = (Math.abs(barValue) / range) * canvasWidth;
var zeroPosition = ((me.bar.minValue / range) * canvasWidth) * -1;
var barX = zeroPosition;
if (barValue < 0) barX = zeroPosition - barWidth;
var barY = (canvasHeight - me.bar.height) / 2;
// Bar color
if (me.colorPalette) // Use legend color
{
var n = me._featureIds.length;
var colorList = me.colorPalette.getColors(n);
var index = me._featureIds.indexOf(id);
ctx.fillStyle = colorList[index];
}
else if (symbolValue !== undefined) // Use symbol color - used by election template.
{
var n = me.symbols.length;
for (var i = 0; i < n; i++)
{
var symbol = me.symbols[i];
if (symbol.value === symbolValue)
{
ctx.fillStyle = symbol.color;
break;
}
}
}
else // Use default color
ctx.fillStyle = me.bar.color;
ctx.beginPath();
ctx.rect(barX, barY, barWidth, me.bar.height);
ctx.fill();
var f = {};
f.label = barValue;
f.rect = new ia.Rectangle(barX, barY, barWidth, me.bar.height);
features[features.length] = f;
}
// Draw targets.
var n = me.targets.length;
for (var i = 0; i < n; i++)
{
var target = me.targets[i];
var targetValue = $j(this).data('targetValue'+i);
if (targetValue !== undefined)
{
var targetX = (((targetValue - me.bar.minValue) / range) * canvasWidth);
ctx.fillStyle = target.color;
ctx.beginPath();
ia.Shape.draw(target.shape, ctx, targetX, canvas.height/2, target.size);
ctx.fill();
var f = {};
f.label = target.label + " : " + targetValue;
f.rect = new ia.Rectangle(targetX-(target.size/2), (canvas.height-target.size)/2, target.size, target.size);
features[features.length] = f;
}
}
me._setToolTip($j(canvas), features);
});
// Performance.
this.$table.find('tbody > tr > td.ia-performance-cell').each(function()
{
var features = [];
var id = $j(this).data('id');
var breaksFlip = $j(this).data('breaksFlip');
var barValue = $j(this).data('barValue');
var minValue = $j(this).data('minValue');
var maxValue = $j(this).data('maxValue');
var range = maxValue - minValue;
var canvas = $j(this).find("canvas:first").get(0);
var canvasWidth = $j(this).width();
var canvasHeight = $j(this).data('canvasHeight');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw breaks.
var n = me.breaks.length;
for (var i = 0; i < n; i++)
{
if (breaksFlip === true) var brk = me.breaks[n-i-1];
else var brk = me.breaks[i];
var breakValue1 = $j(this).data('breakValue'+i);
var j = i+1;
var breakValue2 = $j(this).data('breakValue'+j);
var breakX = (((breakValue1 - minValue) / range) * canvasWidth);
var breakWidth = (((breakValue2 - breakValue1) / range) * canvasWidth);
ctx.fillStyle = brk.color;
ctx.beginPath();
ctx.rect(breakX, 0, breakWidth, canvas.height);
ctx.fill();
var f = {};
f.label = brk.label + " : " + breakValue1 + " - " + breakValue2;
f.rect = new ia.Rectangle(breakX, 0, breakWidth, canvas.height);
features[features.length] = f;
}
// Draw bar.
if (barValue !== undefined)
{
var barWidth = ((Math.abs(barValue) - minValue) / range) * canvasWidth;
var barX = 0;
var barY = (canvasHeight - me.bar.height) / 2;
if (barValue < 0)
{
var zeroPosition = ((minValue / range) * canvasWidth) * -1;
barX = (Math.abs(barValue - minValue) / range) * canvasWidth;
barWidth = zeroPosition - barX;
}
if (me.colorPalette)
{
var n = me._featureIds.length;
var colorList = me.colorPalette.getColors(n);
var index = me._featureIds.indexOf(id);
ctx.fillStyle = colorList[index];
}
else ctx.fillStyle = me.bar.color;
ctx.beginPath();
ctx.rect(barX, barY, barWidth, me.bar.height);
ctx.fill();
var f = {};
f.label = barValue;
f.rect = new ia.Rectangle(barX, barY, barWidth, me.bar.height);
features[features.length] = f;
}
// Draw targets.
var n = me.targets.length;
for (var i = 0; i < n; i++)
{
var target = me.targets[i];
var targetValue = $j(this).data('targetValue'+i);
if (targetValue !== undefined)
{
var targetX = (((targetValue - minValue) / range) * canvasWidth);
ctx.fillStyle = target.color;
ctx.beginPath();
ia.Shape.draw(target.shape, ctx, targetX, canvas.height/2, target.size);
ctx.fill();
var f = {};
f.label = target.label + " : " + targetValue;
f.rect = new ia.Rectangle(targetX-(target.size/2), (canvas.height-target.size)/2, target.size, target.size);
features[features.length] = f;
}
}
me._setToolTip($j(canvas), features);
});
// Health.
this.$table.find('tbody > tr > td.ia-health-cell').each(function()
{
var features = [];
var id = $j(this).data('id');
var breaksFlip = $j(this).data('breaksFlip');
var minValue = $j(this).data('minValue');
var maxValue = $j(this).data('maxValue');
var range = maxValue - minValue;
var symbolValue = $j(this).data('symbolValue');
var areaValue = $j(this).data('areaValue');
var nationalValue = $j(this).data('nationalValue');
var midValue = minValue + (range / 2);
var diff = nationalValue - midValue;
minValue = minValue + diff;
maxValue = maxValue + diff;
range = maxValue - minValue;
var canvas = $j(this).find("canvas:first").get(0);
var canvasWidth = $j(this).width();
var canvasHeight = $j(this).data('canvasHeight');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw breaks.
var n = me.breaks.length;
for (var i = 0; i < n; i++)
{
if (breaksFlip === true) var brk = me.breaks[n-i-1];
else var brk = me.breaks[i];
var breakValue1 = $j(this).data('breakValue'+i);
var j = i+1;
var breakValue2 = $j(this).data('breakValue'+j);
var breakX = (((breakValue1 - minValue) / range) * canvasWidth);
var breakWidth = (((breakValue2 - breakValue1) / range) * canvasWidth);
ctx.fillStyle = brk.color;
ctx.beginPath();
ctx.rect(breakX, 0, breakWidth, canvas.height);
ctx.fill();
var f = {};
f.label = brk.label + " : " + breakValue1 + " - " + breakValue2;
f.rect = new ia.Rectangle(breakX, 0, breakWidth, canvas.height);
features[features.length] = f;
}
// Draw symbol.
if (symbolValue !== undefined)
{
var symbolShape;
var symbolSize;
var symbolColor;
var symbolLabel;
var n = me.symbols.length;
for (var i = 0; i < n; i++)
{
var symbol = me.symbols[i];
if (symbol.value === symbolValue)
{
var symbolX = (((areaValue - minValue) / range) * canvasWidth);
ctx.fillStyle = symbol.color;
ctx.beginPath();
ia.Shape.draw(symbol.shape, ctx, symbolX, canvas.height/2, symbol.size);
ctx.fill();
var f = {};
f.label = symbol.label + " : " + areaValue;
f.rect = new ia.Rectangle(symbolX-(symbol.size/2), (canvas.height-symbol.size)/2, symbol.size, symbol.size);
features[features.length] = f;
break;
}
}
}
// Draw targets.
var n = me.targets.length;
for (var i = 0; i < n; i++)
{
var target = me.targets[i];
var targetValue = $j(this).data('targetValue'+i);
if (targetValue !== undefined)
{
var targetX = (((targetValue - minValue) / range) * canvasWidth);
ctx.fillStyle = target.color;
ctx.beginPath();
ia.Shape.draw(target.shape, ctx, targetX, canvas.height/2, target.size);
ctx.fill();
var f = {};
f.label = target.label + " : " + targetValue;
f.rect = new ia.Rectangle(targetX-(target.size/2), (canvas.height-target.size)/2, target.size, target.size);
features[features.length] = f;
}
}
me._setToolTip($j(canvas), features);
});
// Symbol column.
this.$table.find('tbody > tr > td.ia-symbol-cell').each(function()
{
var features = [];
var symbolValue = $j(this).data('symbolValue');
// Draw symbol.
if (symbolValue !== undefined)
{
var symbolShape;
var symbolSize;
var symbolColor;
var symbolLabel;
var n = me.symbols.length;
for (var i = 0; i < n; i++)
{
var symbol = me.symbols[i];
if (symbol.value === symbolValue)
{
symbolLabel = symbol.label;
symbolShape = symbol.shape;
symbolSize = symbol.size;
symbolColor = symbol.color;
break;
}
}
if (symbolShape !== undefined)
{
var canvas = $j(this).find("canvas:first").get(0);
canvas.width = symbolSize;
canvas.height = symbolSize;
var ctx = canvas.getContext("2d");
ctx.clearRect( 0, 0, canvas.width, canvas.height);
ctx.fillStyle = symbolColor;
var cx = symbolSize / 2;
var cy = symbolSize / 2;
ctx.beginPath();
ia.Shape.draw(symbolShape, ctx, cx, cy, symbolSize);
ctx.fill();
var f = {};
f.label = symbolLabel;
f.rect = new ia.Rectangle(0, 0, canvas.width, canvas.height);
features[features.length] = f;
}
}
me._setToolTip($j(canvas), features);
});
};
/**
* Sets the tooltip.
*
* @method _setToolTip
* @param {HTML Canvas} canvas The canvas to add the tip to.
* @param {Object[]} features The features the tip applies to.
* @private
*/
ia.Profile.prototype._setToolTip = function(canvas, features)
{
var me = this;
canvas.hover
(
function(e)
{
me._tip = $j("<div id='ia-tooltip' class='ia-datatip'>");
$j("body").append(me._tip);
},
function()
{
me._tip.remove();
}
);
var doFade = false;
canvas.mousemove
(
function(e)
{
var mouseX = e.pageX - canvas.offset().left;
var mouseY = e.pageY - canvas.offset().top;
var hit = false;
for (var i = features.length - 1; i >= 0; i--)
{
var f = features[i];
if (f.rect.intersects(mouseX, mouseY))
{
doFade = true;
hit = true;
me._tip.html(f.label);
me._tip.fadeIn("fast");
break;
}
};
if (!hit && doFade === true)
{
doFade = false;
me._tip.fadeOut("fast");
}
var xOffset = me._tip.outerWidth() / 2;
var yOffset = me._tip.outerHeight() + 10;
me._tip.css("top",(e.pageY - yOffset) + "px").css("left",(e.pageX - xOffset) + "px");
}
);
};
/**
* Adds mouse events to the passed jquery object.
* Uses delegation to reduce number of events added to rows and rendering time!
*
* @method _addMouseEvents
* @private
*/
ia.Profile.prototype._addMouseEvents = function()
{
var me = this;
if (ia.IS_TOUCH_DEVICE)
{
// Theme Click.
this.$table.delegate('td.ia-profile-theme-name-cell', 'touchend', function(e)
{
if (!me._scrollBox.isScrolling)
{
me._onThemeClick($j(this));
}
});
if (this._useMouseClick)
{
// Indicator Click.
this.$table.delegate('tr.indicator-row', 'touchend', function(e)
{
if (!me._scrollBox.isScrolling)
{
me._onIndicatorClick($j(this));
}
});
}
}
else
{
// Theme Click.
this.$table.delegate('td.ia-profile-theme-name-cell', ia.CLICK_TYPE, function(e)
{
me._onThemeClick($j(this));
});
if (this._useMouseClick)
{
// Indicator Click.
this.$table.delegate('tr.indicator-row', ia.CLICK_TYPE, function(e)
{
me._onIndicatorClick($j(this));
});
}
}
};
/**
* Called when a theme td is clicked.
*
* @method _onThemeClick
* @param {JQUERY Element} themeCell The theme td.
* @private
*/
ia.Profile.prototype._onThemeClick = function(themeCell)
{
var id = themeCell.attr("id");
if (themeCell.hasClass("ia-profile-theme-expand"))
{
var index = this._collapseIds.indexOf(id);
if (index !== -1) this._collapseIds.splice(index, 1);
themeCell.removeClass("ia-profile-theme-expand").addClass("ia-profile-theme-collapse");
//this.$table.find('tbody > tr.'+id).show();
this.$table.find("tbody[id='"+id+"-children']").show();
}
else
{
this._collapseIds[this._collapseIds.length] = id;
themeCell.removeClass("ia-profile-theme-collapse").addClass("ia-profile-theme-expand");
//this.$table.find('tbody > tr.'+id).hide()
this.$table.find("tbody[id='"+id+"-children']").hide();
}
this._size(); // Need to size here because scrollbar may have disappeared.
};
/**
* Called when a indicator tr is clicked.
*
* @method _onIndicatorClick
* @param {JQUERY Element} indicatorRow The indicator tr.
* @private
*/
ia.Profile.prototype._onIndicatorClick = function(indicatorRow)
{
if (this._tip) this._tip.remove();
if (this.callbackFunction) this.callbackFunction.call(null, indicatorRow.data('linkId'));
};
/**
* Selects a row.
*
* @method select
* @param {String} rowId The id of the row to select.
*/
ia.Profile.prototype.select = function(id)
{
var index = this._featureIds.indexOf(id);
if (index === -1) this._featureIds.push(id);
this._triggerRender();
};
/**
* Unselects a row.
*
* @method unselect
* @param {String} rowId The id of the row to unselect.
*/
ia.Profile.prototype.unselect = function(id)
{
var index = this._featureIds.indexOf(id);
if (index !== -1) this._featureIds.splice(index, 1);
this._triggerRender();
};
/**
* Clears all selections.
*
* @method clearSelection
*/
ia.Profile.prototype.clearSelection = function()
{
this._featureIds = [];
this._triggerRender();
};
/**
* Triggers a render. Prevents over rendering which results in a frozen browser.
*
* @method _triggerRender
* @private
*/
ia.Profile.prototype._triggerRender = function()
{
if (!this._renderTimeout)
{
this._renderTimeout = setTimeout(function()
{
this.render()
}.bind(this), 5);
}
};
/**
* Hightlights a row.
*
* @method highlight
* @param {String} rowId The id of the row to select.
*/
ia.Profile.prototype.highlight = function(rowId) {};
/**
* Clears all highlights.
*
* @method clearHighlight
*/
ia.Profile.prototype.clearHighlight = function() {};