bio-vis-expression-bar
Version:
Simple barchart to show expression levels across experiments
647 lines (566 loc) • 16.2 kB
JavaScript
//var jQuery = require('jquery');
var science = require('science');
var colorbrewer = require('colorbrewer');
require('string.prototype.startswith');
var ExpressionData = function (data, options) {
for (var attrname in data) {
if (attrname == 'values'){
this[attrname] = this._sortGeneOrder(attrname, data[attrname]);
} else {
this[attrname] = data[attrname];
}
}
this.opt = options;
this.sortOrder = [];
};
ExpressionData.prototype.getExpressionValueTypes = function(){
var keys = Object.keys(this.values);
if(keys.length == 0){
return [];
}
var firstVals = this.values[keys[0]];
return Object.keys(firstVals);
};
ExpressionData.prototype.mean = function(data){
var values = Object.keys(data).map(function(val) {
return data[val].value;
});
values = values.sort();
var toRemove = values.length * 0.1;
values.splice(0, toRemove);
values.splice(-1 * toRemove);
return science.stats.mean(values);
};
ExpressionData.prototype.log2 = function(val){
var newVal = val;
if(newVal < 1){
newVal = 0;
}else{
newVal = Math.log2(newVal);
}
return newVal;
};
ExpressionData.prototype.calculateLog2 = function(){
for(g in this.renderedData){
for(v in this.renderedData[g]){
var toTransform = this.renderedData[g][v];
toTransform.stdev = this.log2(toTransform.stdev );
toTransform.value = this.log2(toTransform.value );
for(d in toTransform.data){
toTransform.data[d] = this.log2(toTransform.data[d]);
}
}
}
};
ExpressionData.prototype.setAvailableFactors = function(){
var groups = this.factorOrder;
var fo = this.factorOrder;
var sf = this.selectedFactors;
var optFO = this.opt.renderedOrder;
var optSF = this.opt.selectedFactors;
if( typeof optFO !== 'undefined'){
fo = this.opt.renderedOrder;
}
if(typeof optSF !== 'undefined' ){
sf = this.opt.selectedFactors;
}
var numberOfElements = 0;
if(typeof this.renderedOrder !== 'undefined' ){
numberOfElements = Object.keys(this.renderedOrder).length;
}
if(numberOfElements === 0){
this.renderedOrder = jQuery.extend(true, {}, fo);
}
this.selectedFactors = jQuery.extend(true, {}, sf);
var factorOrder = this.defaultFactorOrder;
this.factors = new Map();
for (var f in factorOrder) {
var g = factorOrder[f];
for(var k in groups[g]){
if(! this.factors.has(g)){
this.factors.set(g, new Set());
}
var currentSet = this.factors.get(g);
currentSet.add(k);
}
}
};
ExpressionData.prototype.prepareColorsForFactors = function(){
//this.factorColors = Map.new();
this.totalColors = 8;
var self = this;
var colors = [
colorbrewer.Pastel2[this.totalColors],
colorbrewer.Accent[this.totalColors],
colorbrewer.Dark2[this.totalColors],
colorbrewer.Set1[this.totalColors],
colorbrewer.Set2[this.totalColors],
colorbrewer.Paired[this.totalColors],
colorbrewer.Pastel1[this.totalColors],
colorbrewer.Set3[this.totalColors]
];
this.factorColors= new Map();
var i = 0;
this.factors.forEach(function(value, key, map){
var color = new Map();
var index = i % self.totalColors ;
var currentColorSet = colors[index];
var j = 0;
value.forEach(function(name){
color[name] = currentColorSet[j++ % self.totalColors ];
});
i ++ ;
self.factorColors[key] = color;
});
return self.factorColors;
};
ExpressionData.prototype.isFiltered = function(group){
var ret = true;
for(var f in group.factors){
if(this.selectedFactors[f]){
ret &= this.selectedFactors[f][group.factors[f]];
}else{
throw new Error('The factor ' + f + ' is not available (' + this.selectedFactors.keys + ')');
}
}
return !ret;
};
ExpressionData.prototype.getSortedKeys = function(factor) {
var i = this.defaultFactorOrder[factor];
var obj = this.renderedOrder[i];
var keys = [];
for(var key in obj) {
keys.push(key);
}
return keys.sort(function(a,b){return obj[a] - obj[b];});
};
/*
The only parameter, sortOrder, is an array of the factors that will be used to sort.
*/
ExpressionData.prototype.sortRenderedGroups = function(){
var i;
if(this.renderedData.length == 0){
return;
}
var sortable = this.renderedData[0].slice();
var sortOrder = this.sortOrder;
var factorOrder= this.renderedOrder;
var sorted = sortable.sort(function(a, b){
for(i in sortOrder){
var o = sortOrder[i];
if(factorOrder[o][a.factors[o]] > factorOrder[o][b.factors[o]]) {
return 1;
}
if (factorOrder[o][a.factors[o]] < factorOrder[o][b.factors[o]]) {
return -1;
}
}
return a.id > b.id ? 1 : -1;
});
for ( i = 0; i < sorted.length; i++) {
sorted[i].renderIndex = i;
}
for(i = 0; i < this.renderedData.length; i++){
for (var j = 0; j < sorted.length; j++) {
var obj = this.renderedData[i][sorted[j].id];
obj.renderIndex = sorted[j].renderIndex;
}
}
};
ExpressionData.prototype.hasExpressionValue = function(property){
for(var gene in this.values){
if(typeof this.values[gene][property] === 'undefined'){
return false;
}else{
return true;
}
}
}
ExpressionData.prototype.getDefaultProperty = function(){
for(var gene in this.values){
var vals = this.values[gene];
for(var v in vals){
return v;
}
}
}
//WARN: This method sets "this.renderedData" to the result of this call.
//This means that the function is not stateles, but the object is the container
//For the data. It could be possible to make it "reentrant"
ExpressionData.prototype.getGroupedData = function(property, groupBy){
var dataArray = [];
for(var gene in this.values){
if(!this.opt.showHomoeologues &&
(
gene !== this.gene &&
gene !== this.compare
)
)
{
continue;
}
var i = 0;
var innerArray;
if(groupBy === 'ungrouped'){
innerArray = [];
var data = this.values[gene][property];
for(var o in data) {
var oldObject = data[o];
var newObject = this._prepareSingleObject(i, oldObject);
newObject.gene = gene;
var filtered = this.isFiltered(newObject);
if (! filtered){
innerArray.push(newObject);
i++;
}
}
dataArray.push(innerArray);
}else if(groupBy === 'groups'){
innerArray = this._fillGroupByExperiment(i++, gene, property);
dataArray.push(innerArray);
}else if(groupBy.constructor === Array){
//This is grouping by factors.
innerArray = this._fillGroupByFactor(i++, gene, property, groupBy);
dataArray.push(innerArray);
}else{
console.log('Not yet implemented');
}
}
this.addMissingFactors(dataArray);
if(this.renderedData && this.renderedData.length > 0){
this.setRenderIndexes(dataArray,this.renderedData);
}
this.renderedData = dataArray;
if(this.isLog()){
this.calculateLog2();
}
this.calculateMinMax();
return dataArray;
};
ExpressionData.prototype.calculateStats = function(newObject){
var v = science.stats.mean(newObject.data);
var stdev = Math.sqrt(science.stats.variance(newObject.data));
newObject.value = v;
newObject.stdev = stdev;
};
ExpressionData.prototype.isLog = function(){
return this.opt.calculateLog;
};
ExpressionData.prototype.calculateMinMax = function(){
var max = -Infinity;
var min = Infinity;
var isLog = this.isLog();
for(var i in this.renderedData){
for(var j in this.renderedData[i]){
var curr =this.renderedData[i][j]
var val = curr.value ;
if(!isLog){
val += curr.stdev;
}
if(val > max) max = val ;
if(val < min) min = val ;
}
}
//if(isLog){
min = 0;
//}
this.max = max;
this.min = min;
//this.min = -1;
//this.max = 1;
}
ExpressionData.prototype._prepareSingleObject = function(index, oldObject){
var newObject = JSON.parse(JSON.stringify(oldObject));
newObject.renderIndex = index;
newObject.id = index;
newObject.name = this.experiments[newObject.experiment].name;
newObject.data = [];
newObject.data.push(oldObject.value);
newObject.value = oldObject.value;
newObject.stdev = 0;
var group = this.experiments[newObject.experiment].group;
newObject.factors = this.groups[group].factors;
return newObject;
};
ExpressionData.prototype._prepareGroupedByExperiment = function(index, group){
var newObject= {};
newObject.renderIndex = index;
newObject.id = index;
newObject.name = this.data.groups[group].description;
newObject.data = [];
newObject.factors = this.data.groups[group].factors;
newObject.value = 0;
newObject.stdev = 0.0;
return newObject;
};
ExpressionData.prototype._prepareGroupedByFactor = function(index, description){
var newObject= {};
newObject.renderIndex = index;
newObject.id = index;
newObject.name = description;
newObject.data = [];
newObject.factors = {};
newObject.value = 0;
newObject.stdev = 0.0;
return newObject;
};
ExpressionData.prototype._fillGroupByExperiment = function(index, gene, property){
var groups ={};
var innerArray = [];
var data = this.values[gene][property];
var g = this.groups;
var e = this.experiments;
var o;
var filtered;
var i = index;
for(o in g){
var newObject = this._prepareGroupedByExperiment(i++,o);
newObject.gene = gene;
groups[o] = newObject;
}
for(o in e){
groups[e[o].group].data.push(data[o].value);
}
i = index;
for(o in groups){
var newObject = groups[o];
newObject.gene = gene;
this.calculateStats(newObject);
if(!this.isFiltered(newObject)){
newObject.renderIndex = i;
newObject.id = i++;
innerArray.push(newObject);
}
}
return innerArray;
};
ExpressionData.prototype._fillGroupByFactor = function(index, gene, property, groupBy){
var groups ={};
var innerArray = [];
var data = this.values[gene][property];
var g = this.groups;
var e = this.experiments;
var names = [];
var o;
var i = index;
for(o in g){
var description = this.getGroupFactorDescription(g[o], groupBy);
var longDescription = this.getGroupFactorLongDescription(g[o], groupBy);
if(names.indexOf(description) === -1){
var newObject = this._prepareGroupedByFactor(i++, description);
newObject.gene = gene;
newObject.longDescription = longDescription;
var factorValues = this.getGroupFactor(g[o], groupBy);
newObject.factors = factorValues;
groups[description] = newObject;
names.push(description);
}
}
i = index;
for(o in e){
if(typeof data[o] === 'undefined' ){
continue; //This is for the cases when the data is set up but not defined
}
var group = g[e[data[o].experiment].group];
if(!this.isFiltered(group)){
var description = this.getGroupFactorDescription(g[e[o].group], groupBy);
groups[description].data.push(data[o].value);
}
}
for(o in groups){
var newObject = groups[o];
if( newObject.data.length === 0){
continue;
}
this.calculateStats(newObject);
if(!this.isFiltered(newObject)){
newObject.renderIndex = i;
newObject.id = i++;
innerArray.push(newObject);
}
}
return innerArray;
};
ExpressionData.prototype.addNames = function(o){
var factors = o.factors;
var factorNames = this.longFactorName;
var numOfFactors = factors.length;
var groupBy = []; //TODO: change this to something like factors.keys
for(var i in factors){
groupBy.push(i);
}
o.name = this.getGroupFactorDescription(o, groupBy);
o.longDescription = this.getGroupFactorLongDescription(o, groupBy);
};
ExpressionData.prototype.getGroupFactorDescription = function(o,groupBy){
var factorArray = [];
var factorNames = this.longFactorName;
var numOfFactors = groupBy.length;
var arrOffset = 0;
for(var i in groupBy) {
var grpby = groupBy[i];
// TODO: This is a patch.
// We should have a list of elements that we don't
// want to display
// if(grpby === 'study'){
// arrOffset ++;
// continue;
// }
var currFact = factorNames[grpby];
var currShort = o.factors[groupBy[i]];
if(typeof currShort === 'undefined' ){
console.error(groupBy[i] + ' is not present in ' + o.factors );
console.error(o.factors);
}
var currLong = currFact[currShort];
factorArray[i - arrOffset ] = currLong;
if(numOfFactors > 4 || currLong.length > 20 ){
factorArray[i - arrOffset ] = currShort;
}
};
return factorArray.join(', ');
};
ExpressionData.prototype.getGroupFactorLongDescription = function(o,groupBy){
var factorArray = [];
var factorNames = this.longFactorName;
//console.log(factorNames);
var numOfFactors = groupBy.length;
for(var i in groupBy) {
var grpby = groupBy[i];
var currFact = factorNames[grpby];
var currShort = o.factors[groupBy[i]];
var currLong = currFact[currShort];
factorArray[i] = currLong;
}
return factorArray.join(', ');
};
ExpressionData.prototype.getGroupFactor = function(o,groupBy){
var factorArray = {};
for (var i in groupBy) {
factorArray[groupBy[i]] = o.factors[groupBy[i]];
}
return factorArray;
};
//To keep the indeces we reiterate and set them
ExpressionData.prototype.setRenderIndexes = function(to, from){
for(var i in to){
var gene=from[i];
for(var j in gene){
to[i][j].renderIndex = from[0][j].renderIndex; //we only use the first gene
}
}
};
ExpressionData.prototype._equals = function(factorA, factorB){
for(a in factorA){
if(factorA[a] != factorB[a]){
return false
}
}
//We test in both sides, to make sure to compare all the
//possible entries in both objects.
for(a in factorB){
if(factorA[a] != factorB[a]){
return false
}
}
return true;
};
ExpressionData.prototype._arrayContains = function(array, object){
for(i in array){
if(this._equals(array[i], object)){
return i;
}
}
return -1;
};
ExpressionData.prototype.addMissingFactors = function(dataArray){
//console.log(dataArray);
var allFactors = [];
for( var i in dataArray){
var gene = dataArray[i];
for(var j in gene){
factors = dataArray[i][j].factors
if(this._arrayContains(allFactors, factors) == -1){
allFactors.push(factors);
}
}
}
//console.log(allFactors);
var fullDataArray = []
for(var i in dataArray){
var gene = dataArray[i];
var localFactors = [];
var tmpDataArray = [];
var localDataArray = [];
for(var j in gene){
localFactors.push(dataArray[i][j].factors);
tmpDataArray.push(dataArray[i][j]);
}
for(var j in allFactors){
var localObject = this._arrayContains(localFactors, allFactors[j])
j = parseInt(j);
if(localObject >= 0){
localDataArray.push(tmpDataArray[localObject]);
//console.log(tmpDataArray[localObject]);
}else{
var obj = this._prepareGroupedByFactor(j, "");
obj.gene = gene[0].gene;
obj.factors = allFactors[j];
localDataArray.push( obj);
}
//localDataArray[j].id = j;
//localDataArray[j].renderIndexs = j;
}
for(var j in localDataArray){
j = parseInt(j);
//console.log(j);
localDataArray[j].id = j;
localDataArray[j].renderIndex = j;
this.addNames(localDataArray[j]);
//console.log(localDataArray[j]);
}
//console.log(localDataArray);
fullDataArray.push(localDataArray);
}
for(var j in fullDataArray){
//console.log(j);
dataArray[j] = fullDataArray[j];
}
//console.log(dataArray);
//console.log(fullDataArray);
};
ExpressionData.prototype.addSortPriority = function(factor, end){
end = typeof end !== 'undefined' ? end : true;
this.removeSortPriority(factor);
if(end === true){
this.sortOrder.push(factor);
}else{
this.sortOrder.unshift(factor);
}
};
ExpressionData.prototype.removeSortPriority = function(factor){
if(typeof this.sortOrder === 'undefined' || this.sortOrder === null){
this.sortOrder = [];
}
var index = this.sortOrder.indexOf(factor);
if (index > -1) {
this.sortOrder.splice(index, 1);
}
};
ExpressionData.prototype._sortGeneOrder = function (key, value){
var geneOrder = {};
var gene;
if(typeof this.tern !== 'undefined' && !$.isEmptyObject(this.tern)){
for (var i = 0; i < Object.keys(this.tooltip_order).length; i++){
gene = this.tern[this.tooltip_order[i]];
if(typeof value[gene] !== 'undefined'){
geneOrder[gene] = value[gene];
}
}
return geneOrder;
} else {
return value;
}
};
//require('biojs-events').mixin(ExpressionData.prototype);
module.exports.ExpressionData = ExpressionData;