swh:1:snp:0c004a03453a29b80f921a24433f7e780b9ceb53
Tip revision: 5d2cf021ab7c15563b9e666e5d9bb18dfa2c9779 authored by Alexey Sergushichev on 21 November 2019, 09:49:38 UTC
version bump
version bump
Tip revision: 5d2cf02
phantasus.js
(function(global){
'use strict';
/**
* @name phantasus
* @namespace
*/
var phantasus = (typeof phantasus !== 'undefined') ? phantasus : {};
if (typeof module !== 'undefined' && module.exports) {
module.exports = phantasus; // Node
} else if (typeof define === 'function' && define.amd) {
define(function () { // AMD module
return phantasus;
});
} else {
global.phantasus = phantasus; // browser global
}
phantasus.Util = function () {
};
phantasus.Util.RIGHT_ARROW = String.fromCharCode(8594);
/**
* Add properties in c2 to c1
*
* @param {Object}
* c1 The object that will inherit from obj2
* @param {Object}
* c2 The object that obj1 inherits from
*/
phantasus.Util.extend = function (c1, c2) {
for (var key in c2.prototype) {
if (!(key in c1.prototype)) {
c1.prototype[key] = c2.prototype[key];
}
}
};
phantasus.Util.isFetchStreamingSupported = function () {
return typeof navigator !== 'undefined' && navigator.userAgent.indexOf('Chrome') !== -1;
};
phantasus.Util.viewPortSize = function () {
return window.getComputedStyle(document.body, ':before').content.replace(
/"/g, '');
};
phantasus.Util.TRACKING_ENABLED = true;
phantasus.Util.TRACKING_CODE_LOADED = false;
phantasus.Util.loadTrackingCode = function () {
if (phantasus.Util.TRACKING_ENABLED && typeof window !== 'undefined' && typeof navigator !== 'undefined' && navigator.onLine) {
if (phantasus.Util.TRACKING_CODE_LOADED) {
return;
} else if (typeof ga === 'undefined') {
phantasus.Util.TRACKING_CODE_LOADED = true;
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments);
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
}
if (typeof ga !== 'undefined') {
ga('create', 'UA-53973555-1', 'auto', 'phantasus');
ga('phantasus.send', 'pageview');
}
phantasus.Util.TRACKING_CODE_LOADED = true;
}
};
phantasus.Util.measureScrollbar = function () {
var $c = $(
'<div style=\'position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;\'></div>')
.appendTo('body');
var dim = {
width: Math.max(0, $c.width() - $c[0].clientWidth),
height: $c.height() - $c[0].clientHeight
};
$c.remove();
return dim;
};
phantasus.Util.trackEvent = function (options) {
if (typeof window !== 'undefined') {
if (!phantasus.Util.TRACKING_CODE_LOADED) {
phantasus.Util.loadTrackingCode();
}
if (phantasus.Util.TRACKING_CODE_LOADED && typeof ga !== 'undefined') {
ga('phantasus.send', {
hitType: 'event',
eventCategory: options.eventCategory,
eventAction: options.eventAction,
eventLabel: options.eventLabel
});
}
}
};
phantasus.Util.isString = function (value) {
return typeof value === 'string' || value instanceof String;
};
/**
*
* @param val The value to determine the data type for.
* @return {String} One of string, number, object, [string], [number], [object]
*/
phantasus.Util.getDataType = function (val) {
var dataType;
var isArray = phantasus.Util.isArray(val);
if (isArray && val.length > 0) {
val = val[0];
}
if (phantasus.Util.isString(val)) {
dataType = 'string';
} else if (_.isNumber(val)) {
dataType = 'number';
} else {
dataType = 'object';
}
if (isArray) {
dataType = '[' + dataType + ']';
}
return dataType;
};
/**
* Checks whether supplied argument is an array
*/
phantasus.Util.isArray = function (array) {
var types = [
Array, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array,
Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array,];
// handle native arrays
for (var i = 0, length = types.length; i < length; i++) {
if (array instanceof types[i]) {
return true;
}
}
return false;
};
phantasus.Util.getWindowSearchObject = function () {
var searchObject = {};
var hashObject = {};
if (window.location.search.length > 0) {
searchObject = phantasus.Util.getQueryParams(window.location.search
.substring(1));
}
if (window.location.hash.length > 0) {
hashObject = phantasus.Util.getQueryParams(window.location.hash
.substring(1));
}
return _.extend(hashObject, searchObject);
};
phantasus.Util.copyString = function (s) {
return (' ' + s).substr(1);
//return (' ' + s).slice(1);
// var copy = [];
// for (var i = 0, end = s.length; i < end; i++) {
// copy.push(s[i]);
// }
// return copy.join('');
};
phantasus.Util.getQueryParams = function (s) {
var params = {};
if (!s) {
return params;
}
var search = decodeURIComponent(s);
var keyValuePairs = search.split('&');
for (var i = 0; i < keyValuePairs.length; i++) {
var pair = keyValuePairs[i].split('=');
if (pair[1] != null && pair[1] !== '') {
var array = params[pair[0]];
if (array === undefined) {
array = [];
params[pair[0]] = array;
}
array.push(pair[1]);
}
}
return params;
};
phantasus.Util.getScriptPath = function (name) {
if (!name) {
name = 'phantasus-latest.min.js';
}
var scripts = document.getElementsByTagName('script');
for (var i = scripts.length - 1; i >= 0; i--) {
var src = scripts[i].src;
var index = src.lastIndexOf('/');
if (index !== -1) {
src = src.substring(index + 1);
}
if (src === name) {
return scripts[i].src;
}
}
// not found
if (name === 'phantasus-latest.min.js') {
return phantasus.Util.getScriptPath('phantasus.js');
}
// return 1st script
return scripts.length > 0 ? scripts[0].src : '';
};
phantasus.Util.forceDelete = function (obj) {
try {
var _garbageCollector = (function () {
var ef = URL.createObjectURL(new Blob([''], {
type: 'text/javascript'
})), w = new Worker(ef);
URL.revokeObjectURL(ef);
return w;
})();
_garbageCollector.postMessage(obj, [obj]);
}
catch (x) {
console.log('Unable to delete');
}
};
phantasus.Util.getFileName = function (fileOrUrl) {
if (phantasus.Util.isFile(fileOrUrl)) {
return fileOrUrl.name;
}
if (fileOrUrl.name !== undefined) {
return fileOrUrl.name;
}
var name = '' + fileOrUrl;
var question = name.indexOf('?');
if (question !== -1) {
var params = name.substring(question + 1);
var keyValuePairs = decodeURIComponent(params).split('&');
// check for parameters in name
for (var i = 0; i < keyValuePairs.length; i++) {
var pair = keyValuePairs[i].split('=');
if (pair[0] === 'file' || pair[0] === 'name') {
name = pair[1];
break;
}
}
} else {
var slash = name.lastIndexOf('/');
if (slash === name.length - 1) {
name = name.substring(0, name.length - 1);
slash = name.lastIndexOf('/');
}
if (slash !== -1) {
name = name.substring(slash + 1); // get stuff after slash
}
}
return name;
};
phantasus.Util.prefixWithZero = function (value) {
return value < 10 ? '0' + value : value;
};
phantasus.Util.getExtension = function (name) {
name = '' + name;
var dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) {
var suffix = name.substring(dotIndex + 1).toLowerCase();
if (suffix === 'txt' || suffix === 'gz' || suffix === 'tsv') { // see if file is in
// the form
// name.gct.txt
var newPath = name.substring(0, dotIndex);
var secondDotIndex = newPath.lastIndexOf('.');
if (secondDotIndex > 0) {// see if file has another suffix
var secondSuffix = newPath.substring(secondDotIndex + 1,
newPath.length).toLowerCase();
if (secondSuffix === 'segtab' || secondSuffix === 'seg'
|| secondSuffix === 'maf' || secondSuffix === 'gct'
|| secondSuffix === 'txt' || secondSuffix === 'gmt') {
return secondSuffix;
}
}
}
return suffix;
}
return '';
};
/**
* Gets the base file name. For example, if name is 'test.txt' the method
* returns the string 'test'. If the name is 'test.txt.gz', the method also
* returns the string 'test'.
*
* @param name
* The file name.
* @return The base file name.
*/
phantasus.Util.getBaseFileName = function (name) {
var dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) {
var suffix = name.substring(dotIndex + 1, name.length);
if (suffix === 'gz' || suffix === 'zip' || suffix === 'bz2') {
return phantasus.Util.getBaseFileName(name.substring(0, dotIndex));
}
return name.substring(0, dotIndex);
}
return name;
};
phantasus.Util.seq = function (length) {
var array = [];
for (var i = 0; i < length; i++) {
array.push(i);
}
return array;
};
phantasus.Util.sequ32 = function (length) {
var array = new Uint32Array(length);
for (var i = 0; i < length; i++) {
array[i] = i;
}
return array;
};
/**
* Converts window hash or search to an object that maps keys to an array of
* values. For example ?foo=bar returns {foo:[bar]}
*/
phantasus.Util.paramsToObject = function (hash) {
var search = hash ? window.location.hash : window.location.search;
if (search.length <= 1) {
return {};
}
search = decodeURIComponent(search);
var keyValuePairs = search.substring(1).split('&');
var result = {};
for (var i = 0, length = keyValuePairs.length; i < length; i++) {
var pair = keyValuePairs[i].split('=');
var values = result[pair[0]];
if (values === undefined) {
values = [];
result[pair[0]] = values;
}
values.push(pair[1]);
}
return result;
};
phantasus.Util.isHeadless = function () {
return typeof $.ui === 'undefined';
};
phantasus.Util.isFile = function (f) {
return typeof File !== 'undefined' && f instanceof File;
};
phantasus.Util.endsWith = function (string, suffix) {
return string.length >= suffix.length
&& string.substr(string.length - suffix.length) === suffix;
};
phantasus.Util.measureSvgText = function (text, classname) {
if (!text || text.length === 0) {
return {
height: 0,
width: 0
};
}
var container = d3.select('body').append('svg');
if (classname) {
container.attr('class', classname);
}
container.append('text').attr({
x: -1000,
y: -1000
}).text(text);
var bbox = container.node().getBBox();
container.remove();
return {
height: bbox.height,
width: bbox.width
};
};
phantasus.Util.IS_MAC = false;
if (typeof navigator !== 'undefined') {
phantasus.Util.IS_MAC = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) ? true
: false;
}
phantasus.Util.COMMAND_KEY = phantasus.Util.IS_MAC ? '⌘' : 'Ctrl+';
phantasus.Util.hammer = function (el, recognizers) {
if (typeof Hammer !== 'undefined') {
var hammer = new Hammer(el, {
recognizers: []
});
if (_.indexOf(recognizers, 'pan') !== -1) {
hammer.add(new Hammer.Pan({
threshold: 1,
direction: Hammer.DIRECTION_ALL
}));
} else if (_.indexOf(recognizers, 'panh') !== -1) {
hammer.add(new Hammer.Pan({
threshold: 1,
direction: Hammer.DIRECTION_HORIZONTAL
}));
} else if (_.indexOf(recognizers, 'panv') !== -1) {
hammer.add(new Hammer.Pan({
threshold: 1,
direction: Hammer.DIRECTION_VERTICAL
}));
}
if (_.indexOf(recognizers, 'tap') !== -1) {
// var singleTap = new Hammer.Tap({
// event : 'singletap',
// interval : 50
// });
// var doubleTap = new Hammer.Tap({
// event : 'doubletap',
// taps : 2
// });
// doubleTap.recognizeWith(singleTap);
// singleTap.requireFailure([ doubleTap ]);
// hammer.add([ doubleTap, singleTap ]);
hammer.add(new Hammer.Tap());
}
if (_.indexOf(recognizers, 'pinch') !== -1) {
hammer.add(new Hammer.Pinch());
}
if (_.indexOf(recognizers, 'longpress') !== -1) {
hammer.add(new Hammer.Press({
event: 'longpress',
time: 1000
}));
}
if (_.indexOf(recognizers, 'press') !== -1) {
hammer.add(new Hammer.Press());
}
if (_.indexOf(recognizers, 'swipe') !== -1) {
hammer.add(new Hammer.Swipe());
}
return hammer;
} else {
return $();
}
};
phantasus.Util.createTextDecoder = function () {
if (typeof TextDecoder !== 'undefined') {
var textDecoder = new TextDecoder();
return function (buf, start, end) {
return textDecoder.decode(buf.subarray(start, end));
};
} else {
return function (buf, start, end) {
// TODO convert in chunks
var s = [];
for (var i = start; i < end; i++) {
s.push(String.fromCharCode(buf[i]));
}
return s.join('');
};
}
};
phantasus.Util.autocompleteArrayMatcher = function (token, cb, array, fields, max) {
var filteredSet = new phantasus.Set();
var regex = new RegExp(phantasus.Util.escapeRegex(token), 'i');
var regexMatch = new RegExp('(' + phantasus.Util.escapeRegex(token) + ')', 'i');
// iterate through the pool of strings and for any string that
// contains the substring `q`, add it to the `matches` array
if (fields) {
var nfields = fields.length;
for (var i = 0, n = array.length; i < n; i++) {
var item = array[i];
for (var j = 0; j < nfields; j++) {
var field = fields[j];
var value = item[field];
if (regex.test(value)) {
filteredSet.add(value);
break;
}
}
if (filteredSet.size() === max) {
break;
}
}
} else {
for (var i = 0, n = array.length; i < n; i++) {
var value = array[i];
if (regex.test(value)) {
filteredSet.add(value);
if (filteredSet.size() === max) {
break;
}
}
}
}
var matches = [];
filteredSet.forEach(function (value) {
var quotedValue = value;
if (quotedValue.indexOf(' ') !== -1) {
quotedValue = '"' + quotedValue + '"';
}
matches.push({
value: quotedValue,
label: '<span>' + value.replace(regexMatch, '<b>$1</b>')
+ '</span>'
});
});
cb(matches);
};
/**
*
* @param text. text
*/
phantasus.Util.setClipboardData = function (text) {
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
var fakeElem = document.createElement('textarea');
var container = document.body;
// Prevent zooming on iOS
fakeElem.style.fontSize = '12pt';
// Reset box model
fakeElem.style.border = '0';
fakeElem.style.padding = '0';
fakeElem.style.margin = '0';
// Move element out of screen horizontally
fakeElem.style.position = 'absolute';
fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px';
// Move element to the same position vertically
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
fakeElem.style.top = yPosition+'px';
fakeElem.setAttribute('readonly', '');
fakeElem.value = text;
container.appendChild(fakeElem);
fakeElem.select();
fakeElem.setSelectionRange(0, fakeElem.value.length);
document.execCommand('copy');
document.body.removeChild(fakeElem);
};
/**
* @param {Number}
* [options.delay=500] - Delay to short autosuggestions.
* @param {jQuery}
* options.$el - Text box to apply autosuggest to.
* @param {Function}
* options.filter - Callback to invoke to filter a suggested term.
* Invoked with array of tokens and response.
* @param {Function}
* options.select - Callback to invoke when a suggested term is
* selected.
* @param {Boolean}
* [options.multi=true] - Whether to allow more than one search term.
* @param {Boolean}
* [options.suggestWhenEmpty=true] - Whether to autosuggest terms
* when text field is empty.
*
*/
phantasus.Util.autosuggest = function (options) {
options = $.extend({}, {
multi: true,
delay: 500,
minLength: 0,
suggestWhenEmpty: true,
}, options);
var searching = false;
function _select(event, ui, isKey) {
if (ui.item.skip) {
return false;
}
if (options.multi) {
var terms = phantasus.Util
.getAutocompleteTokens(
options.$el[0].value,
{
trim: false,
selectionStart: options.$el[0].selectionStart
});
var field = (event.toElement && event.toElement.dataset) ? event.toElement.dataset.autocomplete : null;
var value = field ? ui.item[field] : ui.item.value;
var show = ui.item.show;
// replace the current input
if (terms.length === 0) {
terms.push(value);
} else if (ui.item.clear) {
terms = [value];
} else {
terms[terms.selectionStartIndex === -1
|| terms.selectionStartIndex === undefined ? terms.length - 1
: terms.selectionStartIndex] = value;
}
// add the selected item
options.$el[0].value = terms.join(' ');
if ((show && !isKey) || (isKey && event.which === 13)) { // did
// we
// select
// just a
// field name?
searching = true;
setTimeout(function () {
options.$el.autocomplete('search',
options.$el.val());
}, 20);
setTimeout(function () {
searching = false;
}, 100);
}
if (!isKey && options.select) {
options.select();
}
return false;
}
if (!isKey && options.select) {
options.select();
}
if (!isKey && event.which === 13) {
event.stopImmediatePropagation();
}
}
options.$el
// don't navigate away from the field on tab when selecting an item
.on(
'keydown',
function (event) {
if ((event.keyCode === $.ui.keyCode.TAB)
&& $(this).data('ui-autocomplete').menu.active) {
event.preventDefault();
}
})
.autocomplete(
{
minLength: options.minLength,
delay: options.delay,
source: function (request, response) {
if (request.term.history && options.history) {
return options.history(response);
}
// delegate back to autocomplete, but extract the
// autocomplete term
var terms = phantasus.Util
.getAutocompleteTokens(
request.term,
{
trim: false,
selectionStart: options.$el[0].selectionStart
});
if (terms.selectionStartIndex === undefined
|| terms.selectionStartIndex === -1) {
terms.selectionStartIndex = terms.length - 1;
}
if (options.suggestWhenEmpty || terms.length > 0) {
options.filter(terms, response);
}
},
focus: function (event, ui) {
var original = event.originalEvent;
while (original.originalEvent != null) {
original = original.originalEvent;
}
if (original && /^key/.test(original.type)) {
return _select(original, ui, true);
}
return false;
},
select: function (event, ui) {
return _select(event, ui, false);
}
});
// use html for label instead of default text, class for categories vs. items
var instance = options.$el.autocomplete('instance');
if (instance != null) {
instance._renderItem = function (ul, item) {
if (item.value == null) { // category
return $('<li class="' + (item.class ? (' ' + item.class) : '') + ' search-category">')
.append($('<div>').html(item.label))
.appendTo(ul);
}
return $('<li class="' + (item.class ? (' ' + item.class) : '') + ' search-item">')
.append($('<div>').html(item.label))
.appendTo(ul);
};
instance._normalize = function (items) {
return items;
};
instance._resizeMenu = function () {
var ul = this.menu.element;
ul.outerWidth(instance.element.outerWidth());
};
}
var menu = options.$el.autocomplete('widget');
menu.menu('option', 'items', '> :not(.search-category)');
if (menu) {
menu.addClass('search-menu');
}
if (options.suggestWhenEmpty) {
options.$el.on('focus', function () {
options.$el.autocomplete('search', options.$el.val());
});
}
options.$el.on('keyup', function (e) {
if (e.which === 13 && !searching) {
options.$el.autocomplete('close');
} else if (e.which === 38 && options.history) { // up arrow
options.$el.autocomplete('search', {history: true});
} else if (options.suggestWhenEmpty && options.$el.val() === '') {
options.$el.autocomplete('search', '');
}
});
};
phantasus.Util.getAutocompleteTokens = function (text, options) {
options = $.extend({}, {
trim: true
}, options);
if (options.trim) {
text = $.trim(text);
}
if (text === '') {
return [];
}
var inQuote = false;
var inParen = false;
var tokens = [];
var currentToken = [];
for (var i = 0, n = text.length; i < n; i++) {
var c = text[i];
if (c === '"') {
inQuote = !inQuote;
currentToken.push(c);
} else if (c === '(' || c === ')') {
inParen = c === '(';
currentToken.push(c);
} else {
if ((c === ' ' || c === '\t') && !inQuote && !inParen) {
tokens.push({
s: currentToken.join(''),
inSelectionStart: currentToken.inSelectionStart
});
currentToken = []; // start new token
} else { // add to current token
currentToken.push(c);
}
}
if (i === options.selectionStart - 1) {
currentToken.inSelectionStart = true;
}
}
tokens.push({
s: currentToken.join(''),
inSelectionStart: currentToken.inSelectionStart
});
// add trailing token
if (!options.trim && !inQuote && text[text.length - 1] === ' ') {
tokens.push({
s: ' ',
inSelectionStart: false
});
}
// remove empty tokens
// keep spaces at end of input "field:value" for next autocomplete
var filteredTokens = [];
var selectionStartIndex = -1;
for (var i = 0, ntokens = tokens.length; i < ntokens; i++) {
var token = tokens[i];
var s = token.s;
if (options.trim || i < (ntokens - 1)) {
s = $.trim(s);
}
if (s !== '') {
if (token.inSelectionStart) {
selectionStartIndex = filteredTokens.length;
}
filteredTokens.push(s);
}
}
filteredTokens.selectionStartIndex = selectionStartIndex;
return filteredTokens;
};
phantasus.Util.showDialog = function ($el, title, options) {
var $dialog = $('<div></div>');
$el.appendTo($dialog);
$dialog.appendTo($(document.body));
if (!options) {
options = {};
}
$dialog.dialog({
width: 670,
height: 590,
title: title,
// resizeStop : function(event, ui) {
// var w = parseInt($dialog.width());
// var h = parseInt($dialog.height());
// //var d = Math.min(w, h);
// svg.attr("width", w - 50);
// svg.attr("height", h - 50);
// chart.update();
// },
close: function (event, ui) {
$dialog.remove();
if (options.close) {
options.close();
}
}
});
};
/**
* @param sheet
* An xlsx sheet
* @param delim
* If a delim is specified each row, will contain a string separated
* by delim. Otherwise each row will contain an array.
*/
phantasus.Util.sheetToArray = function (sheet, delim) {
var r = XLSX.utils.decode_range(sheet['!ref']);
var rows = [];
var colors = [];
var header = [];
for (var C = r.s.c; C <= r.e.c; ++C) {
var val = sheet[XLSX.utils.encode_cell({
c: C,
r: r.s.r
})];
var txt = String(XLSX.utils.format_cell(val));
header.push(txt);
}
for (var R = r.s.r; R <= r.e.r; ++R) {
var row = [];
var isRowEmpty = true;
for (var C = r.s.c; C <= r.e.c; ++C) {
var val = sheet[XLSX.utils.encode_cell({
c: C,
r: R
})];
if (!val) {
row.push('');
continue;
}
isRowEmpty = false;
var txt = String(XLSX.utils.format_cell(val));
if (val.s != null && val.s.fgColor != null) {
var color = '#' + val.s.fgColor.rgb;
colors.push({
header: header[row.length],
color: color,
value: txt
});
}
row.push(txt);
}
if (!isRowEmpty) {
rows.push(delim ? row.join(delim) : row);
}
}
rows.colors = colors;
return rows;
};
phantasus.Util.linesToObjects = function (lines) {
var header = lines[0];
var array = [];
var nfields = header.length;
for (var i = 1, length = lines.length; i < length; i++) {
var line = lines[i];
var obj = {};
for (var f = 0; f < nfields; f++) {
var value = line[f];
var field = header[f];
obj[field] = value;
}
array.push(obj);
}
return array;
};
/**
*
* @param options.data Binary data string
* @param options.prompt Prompt for sheet name
* @param callback {Function} Callback
*/
phantasus.Util.xlsxTo2dArray = function (options, callback) {
var workbook = XLSX.read(options.data, {
type: 'binary',
cellFormula: false,
cellHTML: false,
cellStyles: true
});
var sheetNames = workbook.SheetNames;
if (options.prompt && sheetNames.length > 1) {
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'sheet',
type: 'bootstrap-select',
options: sheetNames,
required: true,
style: 'max-width:100px;'
});
phantasus.FormBuilder.showInModal({
title: 'Choose Sheet',
html: formBuilder.$form,
focus: document.activeElement,
onClose: function () {
var worksheet = workbook.Sheets[formBuilder.getValue('sheet')];
var lines = phantasus.Util.sheetToArray(worksheet);
callback(null, lines);
}
});
} else {
var worksheet = workbook.Sheets[sheetNames[0]];
var lines = phantasus.Util.sheetToArray(worksheet);
callback(null, lines);
}
};
/**
*
* @param options.data Binary data string
* @param options.prompt Prompt for sheet name
* @param callback {Function} Callback
*/
phantasus.Util.xlsxTo1dArray = function (options, callback) {
var workbook = XLSX.read(options.data, {
type: 'binary',
cellFormula: false,
cellHTML: false,
cellStyles: true
});
var sheetNames = workbook.SheetNames;
if (options.prompt && sheetNames.length > 1) {
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'sheet',
type: 'bootstrap-select',
options: sheetNames,
required: true,
style: 'max-width:100px;'
});
phantasus.FormBuilder.showOkCancel({
title: 'Choose Sheet',
cancel: false,
focus: document.activeElement,
content: formBuilder.$form,
okCallback: function () {
var worksheet = workbook.Sheets[formBuilder.getValue('sheet')];
callback(null, phantasus.Util.sheetToArray(worksheet, '\t'));
}
});
} else {
var worksheet = workbook.Sheets[sheetNames[0]];
callback(null, phantasus.Util.sheetToArray(worksheet, '\t'));
}
};
/**
* Returns a promise that resolves to a string
*/
phantasus.Util.getText = function (fileOrUrl) {
var deferred = $.Deferred();
if (phantasus.Util.isString(fileOrUrl)) {
fetch(fileOrUrl).then(function (response) {
if (response.ok) {
return response.text();
} else {
deferred.reject(response.status + ' ' + response.statusText);
}
}).then(function (text) {
// var type = xhr.getResponseHeader('Content-Type');
deferred.resolve(text);
}).catch(function (err) {
deferred.reject(err);
});
} else if (phantasus.Util.isFile(fileOrUrl)) {
var reader = new FileReader();
reader.onload = function (event) {
deferred.resolve(event.target.result);
};
reader.readAsText(fileOrUrl);
} else {
// what is fileOrUrl?
deferred.resolve(fileOrUrl);
}
return deferred.promise();
};
phantasus.Util.createOptions = function (values, none) {
var html = [];
if (none) {
html.push('<option value="">(None)</option>');
}
_.each(values, function (val) {
html.push('<option value="');
html.push(val);
html.push('">');
html.push(val);
html.push('</option>');
});
return html.join('');
};
/**
* Computes the rank using the given index array. The index array can be
* obtained from the phantasus.Util.indexSort method. Does not handle ties.
*
* @param index
* @return The ranks.
*/
phantasus.Util.rankIndexArray = function (index) {
var rank = [];
var n = index.length;
for (var j = 0; j < n; j++) {
rank[index[j]] = j + 1;
}
return rank;
};
phantasus.Util.indexSort = function (array, ascending) {
var pairs = [];
for(var i = 0, length = array.length; i < length; i++) {
pairs.push({
value: array[i],
index: i
});
}
return phantasus.Util.indexSortPairs(pairs, ascending);
};
phantasus.Util.indexSortPairs = function (array, ascending) {
if (ascending) {
array.sort(function (a, b) {
return (a.value < b.value ? -1 : (a.value === b.value ? (a.index < b.index ? -1 : 1) : 1));
});
} else {
array.sort(function (a, b) {
return (a.value < b.value ? 1 : (a.value === b.value ? (a.index < b.index ? 1 : -1) : -1));
});
}
var indices = [];
array.forEach(function (item) {
indices.push(item.index);
});
return indices;
};
phantasus.Util.arrayEquals = function (array1, array2, comparator) {
if (array1 == array2) {
return true;
}
if (array1 == null || array2 == null) {
return false;
}
if (!comparator) {
comparator = function (a, b) {
return a === b;
};
}
var length = array1.length;
if (array2.length !== length) {
return false;
}
for (var i = 0; i < length; i++) {
if (!comparator(array1[i], array2[i])) {
return false;
}
}
return true;
};
phantasus.Util._intFormat = typeof d3 !== 'undefined' ? d3.format(',i')
: function (d) {
return '' + Math.round(d);
};
phantasus.Util.intFormat = function (n) {
return phantasus.Util._intFormat(n);
};
phantasus.Util._nf = typeof d3 !== 'undefined' ? d3.format('.5g') : function (d) {
return '' + d;
};
phantasus.Util.getNumberFormatPatternFractionDigits = function (pattern) {
return parseInt(pattern.substring(1, pattern.length)) || 0;
};
phantasus.Util.nf = function (n) {
// var str = (n < 1 && n > -1 && n.toPrecision !== undefined) ? n
// .toPrecision(4) : phantasus.Util._nf(n);
// return phantasus.Util.removeTrailingZerosInFraction(str);
return phantasus.Util._nf(n);
};
phantasus.Util.createNumberFormat = function (pattern) {
var f = d3.format(pattern);
f.toJSON = function () {
return {pattern: pattern};
};
return f;
};
phantasus.Util.wrapNumber = function (value, object) {
var n = new Number(value);
n.toObject = function () {
return object;
};
return n;
};
phantasus.Util.toString = function (value) {
if (value == null) {
return '';
} else if (_.isNumber(value)) {
return phantasus.Util.nf(value);
} else if (phantasus.Util.isArray(value)) {
return phantasus.Util.arrayToString(value, ', ');
}
return '' + value;
};
phantasus.Util.arrayToString = function (value, sep) {
var s = [];
for (var i = 0, length = value.length; i < length; i++) {
var val_i = value[i];
if (_.isNumber(val_i)) {
s.push(phantasus.Util.nf(val_i));
} else {
s.push('' + val_i);
}
}
return s.join(sep);
};
phantasus.Util.removeTrailingZerosInFraction = function (str) {
var index = str.lastIndexOf('.');
if (str.lastIndexOf('e') !== -1) {
return str;
}
if (index !== -1) {
var len = str.length;
var zeros = len;
for (var i = len - 1; i > index; i--, zeros--) {
if (str[i] != '0') {
break;
}
}
if (zeros === (index + 1)) {
return str.substring(0, index);
}
if (zeros < len) {
return str.substring(0, index) + str.substring(index, zeros);
}
}
return str;
};
phantasus.Util.s = function (n) {
return n === 1 ? '' : 's';
};
phantasus.Util.create2dArray = function (rows, columns) {
var array2d = [];
for (var i = 0; i < rows; i++) {
var array = [];
for (var j = 0; j < columns; j++) {
array[j] = NaN;
}
array2d.push(array);
}
return array2d;
};
phantasus.Util.escapeRegex = function (value) {
return value.replace(/[*]/g, '.*')
.replace(/[-[\]{}()+?,\\^$|#\s]/g, '\\$&');
};
phantasus.Util.createSearchPredicates = function (options) {
options = $.extend({}, {
validateFieldNames: true,
caseSensitive: true
}, options);
var tokens = options.tokens;
if (tokens == null) {
return [];
}
var availableFields = options.fields;
if (!options.caseSensitive && availableFields != null) {
for (var i = 0; i < availableFields.length; i++) {
availableFields[i] = availableFields[i].toLowerCase();
}
}
var validateFieldNames = options.validateFieldNames;
var fieldSearchEnabled = !validateFieldNames
|| (availableFields != null && availableFields.length > 0);
var fieldRegExp = /\\:/g;
var predicates = [];
var defaultIsExactMatch = options.defaultMatchMode === 'exact';
tokens
.forEach(function (token) {
var isNot = false;
if (token[0] === '-') { // not predicate
token = token.substring(1);
isNot = true;
}
var field = null;
var semi = token.indexOf(':');
if (semi > 0) { // field search?
if (!fieldSearchEnabled
|| token.charCodeAt(semi - 1) === 92) { // \:
token = token.replace(fieldRegExp, ':');
} else { // only a field search if field matches
// one of available fields
var possibleToken = $.trim(token.substring(semi + 1));
// check for "field":"val" and "field:val"
var possibleField = $.trim(token.substring(0, semi)); // split
// on :
if (possibleField.length > 0
&& possibleField[0] === '"'
&& possibleField[possibleField.length - 1] === '"') {
possibleField = possibleField.substring(1,
possibleField.length - 1);
} else if (possibleField.length > 0
&& possibleField[0] === '"'
&& possibleToken[possibleToken.length - 1] === '"'
&& possibleToken[0] !== '"') {
possibleField = possibleField.substring(1,
possibleField.length);
possibleToken = '"' + possibleToken;
}
if (!validateFieldNames
|| availableFields.indexOf(options.caseSensitive ? possibleField : possibleField.toLowerCase()) !== -1) {
token = possibleToken;
field = possibleField;
}
}
}
var predicate;
var rangeIndex = -1;
var rangeToken = null;
var rangeIndicators = ['..', '>=', '>', '<=', '<', '='];
for (var i = 0; i < rangeIndicators.length; i++) {
rangeIndex = token.indexOf(rangeIndicators[i]);
if (rangeIndex !== -1) {
rangeToken = rangeIndicators[i];
break;
}
}
if (rangeIndex !== -1) { // range query
if (rangeToken === '..') {
var start = parseFloat(token.substring(0, rangeIndex));
var end = parseFloat(token.substring(rangeIndex + 2));
if (!isNaN(start) && !isNaN(end)) {
predicate = new phantasus.Util.NumberRangePredicate(
field, start, end);
}
} else if (rangeToken === '>') {
var val = parseFloat(token.substring(rangeIndex + 1));
if (!isNaN(val)) {
predicate = new phantasus.Util.GreaterThanPredicate(
field, val);
}
} else if (rangeToken === '>=') {
var val = parseFloat(token.substring(rangeIndex + 2));
if (!isNaN(val)) {
predicate = new phantasus.Util.GreaterThanOrEqualPredicate(
field, val);
}
} else if (rangeToken === '<') {
var val = parseFloat(token.substring(rangeIndex + 1));
if (!isNaN(val)) {
predicate = new phantasus.Util.LessThanPredicate(
field, val);
}
} else if (rangeToken === '<=') {
var val = parseFloat(token.substring(rangeIndex + 2));
if (!isNaN(val)) {
predicate = new phantasus.Util.LessThanOrEqualPredicate(
field, val);
}
} else if (rangeToken === '=') {
var val = parseFloat(token.substring(rangeIndex + 1));
predicate = new phantasus.Util.EqualsPredicate(
field, val);
} else {
console.log('Unknown range token:' + rangeToken);
}
} else if (token[0] === '"' && token[token.length - 1] === '"') { // exact
token = token.substring(1, token.length - 1);
predicate = new phantasus.Util.ExactTermPredicate(field,
token);
} else if (token[0] === '(' && token[token.length - 1] === ')') { // exact terms
token = token.substring(1, token.length - 1);
var values = phantasus.Util.getAutocompleteTokens(token);
if (values.length > 0) {
predicate = new phantasus.Util.ExactTermsPredicate(field,
values.map(function (val) {
if (val[0] === '"' && val[val.length - 1] === '"') {
val = val.substring(1, val.length - 1);
}
return val.toLowerCase();
}));
}
} else if (token.indexOf('*') !== -1) { // contains
predicate = new phantasus.Util.RegexPredicate(field, token);
} else {
predicate = defaultIsExactMatch ? new phantasus.Util.ExactTermPredicate(
field, token)
: new phantasus.Util.RegexPredicate(field, token);
}
if (predicate != null) {
predicates.push(isNot ? new phantasus.Util.NotPredicate(
predicate) : predicate);
}
});
return predicates;
}
;
phantasus.Util.createRegExpStringToMatchText = function (text) {
var tokens = phantasus.Util.getAutocompleteTokens(text);
if (tokens.length === 0) {
return null;
}
var regex = [];
_.each(tokens, function (token) {
if (token[0] === '"' && token[token.length - 1] === '"') {
token = token.substring(1, token.length - 1);
regex.push('^' + phantasus.Util.escapeRegex(token) + '$'); // exact
// match
} else {
regex.push(phantasus.Util.escapeRegex(token));
}
});
return '(' + regex.join('|') + ')';
};
phantasus.Util.createRegExpToMatchText = function (text) {
var s = phantasus.Util.createRegExpStringToMatchText(text);
return s == null ? null : new RegExp(s, 'i');
};
phantasus.Util.reorderArray = function (array, index) {
var newArray = [];
for (var i = 0; i < index.length; i++) {
newArray.push(array[index[i]]);
}
return newArray;
};
phantasus.Util.getSearchString = function () {
var s = window.location.search;
return s.length > 1 ? s.substring(1) : '';
};
/**
* Takes an array of strings and splits each string by \t
*
* @return An array of arrays
*/
phantasus.Util.splitLines = function (lines) {
var tab = /\t/;
var tokens = [];
for (var i = 0, nlines = lines.length; i < nlines; i++) {
var line = lines[i];
if (line === '') {
continue;
}
tokens.push(line.split(tab));
}
return tokens;
};
/**
* @param file
* a File or url
* @return A deferred object that resolves to an array of strings
*/
phantasus.Util.readLines = function (fileOrUrl, interactive) {
var isFile = phantasus.Util.isFile(fileOrUrl);
var isString = phantasus.Util.isString(fileOrUrl);
var name = phantasus.Util.getFileName(fileOrUrl);
var ext = phantasus.Util.getExtension(name);
var deferred = $.Deferred();
if (isString) { // URL
if (ext === 'xlsx') {
var fetchOptions = {};
if (fileOrUrl.headers) {
fetchOptions.headers = new Headers();
for (var header in fileOrUrl.headers) {
fetchOptions.headers.append(header, fileOrUrl.headers[header]);
}
}
fetch(fileOrUrl, fetchOptions).then(function (response) {
if (response.ok) {
return response.arrayBuffer();
} else {
deferred.reject(response);
}
}).then(function (arrayBuffer) {
if (arrayBuffer) {
var data = new Uint8Array(arrayBuffer);
var arr = [];
for (var i = 0; i != data.length; ++i) {
arr[i] = String.fromCharCode(data[i]);
}
var bstr = arr.join('');
phantasus.Util.xlsxTo1dArray({
data: bstr,
prompt: interactive
}, function (err, lines) {
deferred.resolve(lines);
});
} else {
deferred.reject();
}
});
} else {
fetch(fileOrUrl, fetchOptions).then(function (response) {
if (response.ok) {
return response.text();
} else {
deferred.reject();
}
}).then(function (text) {
deferred.resolve(phantasus.Util.splitOnNewLine(text));
}).catch(function (err) {
deferred.reject(err);
});
}
} else if (isFile) {
var reader = new FileReader();
reader.onerror = function () {
console.log('Unable to read file');
deferred.reject('Unable to read file');
};
reader.onload = function (event) {
if (ext === 'xlsx' || ext === 'xls') {
var data = new Uint8Array(event.target.result);
var arr = [];
for (var i = 0; i != data.length; ++i) {
arr[i] = String.fromCharCode(data[i]);
}
var bstr = arr.join('');
phantasus.Util
.xlsxTo1dArray({
data: bstr,
prompt: interactive
}, function (err, lines) {
deferred.resolve(lines);
});
} else {
deferred.resolve(phantasus.Util.splitOnNewLine(event.target.result));
}
};
if (ext === 'xlsx' || ext === 'xls') {
reader.readAsArrayBuffer(fileOrUrl);
} else {
reader.readAsText(fileOrUrl);
}
} else { // it's already lines?
deferred.resolve(fileOrUrl);
}
return deferred;
};
phantasus.Util.createValueToIndices = function (array, field) {
var map = new phantasus.Map();
_.each(array, function (item) {
var key = item[field];
var values = map.get(key);
if (values === undefined) {
values = [];
map.set(key, values);
}
values.push(item);
});
return map;
};
phantasus.Util.createPhantasusHeader = function () {
var html = [];
html.push('<div style="margin-bottom:10px;"><svg width="32px" height="32px"><g><rect x="0" y="0" width="32" height="14" style="fill:#ca0020;stroke:none"/><rect x="0" y="18" width="32" height="14" style="fill:#0571b0;stroke:none"/></g></svg> <div data-name="brand" style="display:inline-block; vertical-align: top;font-size:24px;font-family:sans-serif;">');
html.push('<span>P</span>');
html.push('<span>h</span>');
html.push('<span>a</span>');
html.push('<span>n</span>');
html.push('<span>t</span>');
html.push('<span>a</span>');
html.push('<span>s</span>');
html.push('<span>u</span>');
html.push('<span>s</span>');
html.push('</span>');
html.push('<strong style="font-size: 12px">v' + PHANTASUS_VERSION + '</strong>');
html.push('</div>');
var $div = $(html.join(''));
var colorScale = d3.scale.linear().domain([0, 4, 7]).range(['#ca0020', '#999999', '#0571b0']).clamp(true);
var brands = $div.find('span');
var index = 0;
var step = function () {
brands[index].style.color = colorScale(index);
index++;
if (index < brands.length) {
setTimeout(step, 200);
}
};
setTimeout(step, 500);
return $div;
};
phantasus.Util.createLoadingEl = function () {
return $(
'<div style="overflow:hidden;text-align:center;"><i class="fa fa-spinner fa-spin fa-3x"></i><span style="padding-left:4px;vertical-align:middle;font-weight:bold;">Loading...</span></div>');
};
/**
* Splits a string by the new line character, trimming whitespace
*/
phantasus.Util.splitOnNewLine = function (text, commentChar) {
var commentCharCode = commentChar !== undefined ? commentChar.charCodeAt(0)
: undefined;
var lines = text.split(/\n/);
if (lines.length === 1) {
var tmp = text.split(/\r/); // old school mac?
if (tmp.length > 1) {
lines = tmp;
}
}
var rows = [];
var rtrim = /\s+$/;
for (var i = 0, nlines = lines.length; i < nlines; i++) {
var line = lines[i].replace(rtrim, '');
if (line !== '') {
if (commentCharCode !== undefined) {
if (line.charCodeAt(0) !== commentCharCode) {
rows.push(line);
}
} else {
rows.push(line);
}
}
}
return rows;
};
phantasus.Util.ContainsPredicate = function (field, text) {
this.field = field;
text = text.toLowerCase();
this.text = text;
};
phantasus.Util.ContainsPredicate.prototype = {
accept: function (value) {
if (value == null) {
return false;
}
value = ('' + value).toLowerCase();
return value.indexOf(this.text) !== -1;
},
getField: function () {
return this.field;
},
getText: function () {
return this.text;
},
isNumber: function () {
return false;
},
toString: function () {
return 'ContainsPredicate ' + this.field + ':' + this.text;
}
};
phantasus.Util.ExactTermsPredicate = function (field, values) {
this.field = field;
this.values = new phantasus.Set();
for (var i = 0, nvalues = values.length; i < nvalues; i++) {
this.values.add(values[i]);
}
};
phantasus.Util.ExactTermsPredicate.prototype = {
accept: function (value) {
if (value == null) {
return false;
}
value = ('' + value).toLowerCase();
return this.values.has(value);
},
getField: function () {
return this.field;
},
getValues: function () {
return this.values;
},
isNumber: function () {
return false;
},
toString: function () {
return 'ExactTermsPredicate ' + this.field + ':' + this.text;
}
};
phantasus.Util.ExactTermPredicate = function (field, term) {
this.field = field;
term = term.toLowerCase();
this.text = term;
};
phantasus.Util.ExactTermPredicate.prototype = {
accept: function (value) {
if (value == null) {
return false;
}
value = ('' + value).toLowerCase();
return value === this.text;
},
getField: function () {
return this.field;
},
getText: function () {
return this.text;
},
isNumber: function () {
return false;
},
toString: function () {
return 'ExactTermPredicate ' + this.field + ':' + this.text;
}
};
phantasus.Util.RegexPredicate = function (field, text) {
this.field = field;
this.text = text;
this.regex = new RegExp(phantasus.Util.escapeRegex(text), 'i');
};
phantasus.Util.RegexPredicate.prototype = {
accept: function (value) {
return this.regex.test('' + value);
},
getField: function () {
return this.field;
},
getText: function () {
return this.text;
},
isNumber: function () {
return false;
},
toString: function () {
return 'RegexPredicate ' + this.field + ':' + this.regex;
}
};
phantasus.Util.NumberRangePredicate = function (field, min, max) {
this.field = field;
this.min = min;
this.max = max;
};
phantasus.Util.NumberRangePredicate.prototype = {
accept: function (value) {
return value >= this.min && value <= this.max;
},
getField: function () {
return this.field;
},
isNumber: function () {
return true;
},
toString: function () {
return 'NumberRangePredicate ' + this.field + ':' + this.min + '...'
+ this.max;
}
};
phantasus.Util.GreaterThanPredicate = function (field, val) {
this.field = field;
this.val = val;
};
phantasus.Util.GreaterThanPredicate.prototype = {
accept: function (value) {
return value > this.val;
},
getField: function () {
return this.field;
},
isNumber: function () {
return true;
}
};
phantasus.Util.GreaterThanOrEqualPredicate = function (field, val) {
this.field = field;
this.val = val;
};
phantasus.Util.GreaterThanOrEqualPredicate.prototype = {
accept: function (value) {
return value >= this.val;
},
getField: function () {
return this.field;
},
isNumber: function () {
return true;
}
};
phantasus.Util.LessThanPredicate = function (field, val) {
this.field = field;
this.val = val;
};
phantasus.Util.LessThanPredicate.prototype = {
accept: function (value) {
return value < this.val;
},
getField: function () {
return this.field;
},
isNumber: function () {
return true;
}
};
phantasus.Util.LessThanOrEqualPredicate = function (field, val) {
this.field = field;
this.val = val;
};
phantasus.Util.LessThanOrEqualPredicate.prototype = {
accept: function (value) {
return value <= this.val;
},
getField: function () {
return this.field;
},
isNumber: function () {
return true;
}
};
phantasus.Util.EqualsPredicate = function (field, val) {
this.field = field;
this.val = val;
};
phantasus.Util.EqualsPredicate.prototype = {
accept: function (value) {
return value === this.val;
},
getField: function () {
return this.field;
},
isNumber: function () {
return true;
}
};
phantasus.Util.NotPredicate = function (p) {
this.p = p;
};
phantasus.Util.NotPredicate.prototype = {
accept: function (value) {
return !this.p.accept(value);
},
getField: function () {
return this.p.getField();
},
isNumber: function () {
return this.p.isNumber();
},
toString: function () {
return 'NotPredicate ' + this.p;
}
};
phantasus.Util.getFieldNames = function (rexp) {
if (_.size(rexp.attrValue) === 0) {
return [];
}
var strValues = rexp.attrValue[0].stringValue;
var res = [];
strValues.forEach(function (v) {
res.push(v.strval);
});
return res;
};
phantasus.Util.getRexpData = function (rexp, rclass) {
//console.log(rexp, rclass);
var names = phantasus.Util.getFieldNames(rexp);
//console.log('fieldNames', names);
var data = {};
for (var i = 0; i < names.length; i++) {
var rexpV = rexp.rexpValue[i];
data[names[i]] = {};
if (rexpV.rclass == rclass.LIST) {
data[names[i]] = phantasus.Util.getRexpData(rexpV, rclass);
}
if (rexpV.attrName.length > 0 && rexpV.attrName[0] == 'dim') {
data[names[i]].dim = rexpV.attrValue[0].intValue;
}
if (rexpV.rclass == rclass.INTEGER) {
if (rexpV.attrName.length > 0 && rexpV.attrName[0] == 'levels') {
data[names[i]].values = [];
rexpV.attrValue[0].stringValue.forEach(function (v) {
data[names[i]].values.push(v.strval);
})
}
else {
data[names[i]].values = rexpV.intValue;
}
}
else if (rexpV.rclass == rclass.REAL) {
data[names[i]].values = rexpV.realValue;
}
else if (rexpV.rclass == rclass.STRING) {
data[names[i]].values = [];
rexpV.stringValue.forEach(function (v) {
data[names[i]].values.push(v.strval);
});
}
}
return data;
};
phantasus.Util.getFilePath = function (session, str) {
var splitted = str.split("/");
var fileName = splitted[splitted.length - 1];
return session.getLoc() + "files/" + fileName;
};
phantasus.Util.getConsNumbers = function (n) {
var ar = [];
for (var i = 0; i < n; i++) {
ar.push(i);
}
return ar;
};
phantasus.Util.equalArrays = function (a, b) {
if (a.length != b.length || a == null || b == null) {
return false;
}
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
};
phantasus.Util.getMessages = function(session) {
var url = session.getLoc() + "messages";
$.ajax({
url : url,
success : function(result) {
console.log(result);
}
});
};
phantasus.Util.setLibrary = function (libraryName) {
if (!window.libraryPrefix) window.libraryPrefix = '/phantasus/';
ocpu.seturl(window.libraryPrefix + 'ocpu/library/' + libraryName + '/R');
};
phantasus.Util.getTrueIndices = function (dataset) {
//console.log('TrueIndices', dataset, dataset.dataset, dataset.dataset === undefined);
var rowIndices = dataset.rowIndices ? dataset.rowIndices : [];
var rows = phantasus.Util.getConsNumbers(rowIndices.length);
var columnIndices = dataset.columnIndices ? dataset.columnIndices : [];
var columns = phantasus.Util.getConsNumbers(columnIndices.length);
var iter = 0;
var savedDataset = dataset;
//console.log("rows processing");
while (dataset.dataset && dataset.esSource !== 'original') {
var transposed = dataset instanceof phantasus.TransposedDatasetView;
var currentIndices = transposed ? dataset.columnIndices : dataset.rowIndices;
if (currentIndices == undefined) {
dataset = dataset.dataset;
continue;
}
rowIndices = currentIndices;
//console.log(iter, "rows:", rows.length, rows);
var newRows = Array.apply(null, Array(rows.length)).map(Number.prototype.valueOf, 0);
for (var i = 0; i < rows.length; i++) {
newRows[i] = currentIndices[rows[i]];
}
rows = newRows;
dataset = dataset.dataset;
iter++;
}
iter = 0;
//console.log("columns processing");
dataset = savedDataset;
while (dataset.dataset && dataset.esSource !== 'original') {
transposed = dataset instanceof phantasus.TransposedDatasetView;
currentIndices = transposed ? dataset.rowIndices : dataset.columnIndices;
if (currentIndices == undefined) {
dataset = dataset.dataset;
continue;
}
columnIndices = dataset.columnIndices;
var newCols = Array.apply(null, Array(columns.length)).map(Number.prototype.valueOf, 0);
for (i = 0; i < columns.length; i++) {
newCols[i] = currentIndices[columns[i]];
}
columns = newCols;
dataset = dataset.dataset;
iter++;
}
//console.log("res", rows, columns);
var conseqRows = phantasus.Util.getConsNumbers(dataset.rows);
var conseqCols = phantasus.Util.getConsNumbers(dataset.columns);
//console.log(conseqCols);
var ans = {};
//console.log(phantasus.Util.equalArrays(rows, conseqRows));
if (phantasus.Util.equalArrays(rows, conseqRows) || rows.length == 0 && phantasus.Util.equalArrays(conseqRows, rowIndices)) {
ans.rows = [];
}
else {
ans.rows = rows.length > 0 ? rows : rowIndices;
}
//console.log(phantasus.Util.equalArrays(columns, conseqCols));
if (phantasus.Util.equalArrays(columns, conseqCols) || columns.length == 0 && phantasus.Util.equalArrays(conseqCols, columnIndices)) {
ans.columns = [];
}
else {
ans.columns = columns.length > 0 ? columns : columnIndices;
}
//console.log(ans);
return ans;
};
phantasus.Util.safeTrim = function (string) {
if (string && string.trim) {
return string.trim();
} else {
return string;
}
};
phantasus.Util.getURLParameter = function (name) {
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
};
phantasus.Util.saveAsSVG = function (svgEl, name) {
svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
var svgData = svgEl.outerHTML.split('<br>').join('\n');
var preface = '<?xml version="1.0" standalone="no"?>\r\n';
var svgBlob = new Blob([preface, svgData], {type:"image/svg+xml;charset=utf-8"});
var svgUrl = URL.createObjectURL(svgBlob);
phantasus.Util.promptBLOBdownload(svgUrl, name);
};
phantasus.Util.promptBLOBdownload = function (url, name) {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = name;
a.click();
setTimeout(function () {
document.body.removeChild(a);
}, 0)
};
phantasus.Util.chunk = function(array, count) {
if (count == null || count < 1) return [];
var result = [];
var i = 0, length = array.length;
while (i < length) {
result.push(array.slice(i, i += count));
}
return result;
};
phantasus.Util.customToolWaiter = function (promise, toolName, heatMap) {
var $dialogContent = $('<div><span>' + toolName + '...</span></div>');
var $dialog = phantasus.FormBuilder.showInDraggableDiv({
$content: $dialogContent,
appendTo: heatMap.getContentEl(),
width: 'auto'
});
promise.always(function () {
$dialog.remove();
});
};
phantasus.Util.browserCheck = function () {
var ua = navigator.userAgent;
var isFirefox = typeof InstallTrigger !== 'undefined';
var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.csi);
var test = [isFirefox, isSafari, isChrome];
if (test.every(function (val) {return !val;})) {
phantasus.FormBuilder.showInModal({
title: 'Unsupported browser.',
html: 'Please note that Phantasus works best with Chrome, Firefox, Safari browsers'
});
}
};
phantasus.BlobFromPath = function () {
};
phantasus.BlobFromPath.getFileBlob = function (url, cb) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "blob";
xhr.addEventListener('load', function () {
cb(xhr.response);
});
xhr.send();
};
// If we add lastModifiedDate to blob it will stay blob, not File. But this operation is prohibited
// To make it File we need name and mimo
// phantasus.BlobFromPath.blobToFile = function (blob) {
// blob.lastModifiedDate = new Date();
// return blob;
// };
phantasus.BlobFromPath.getFileObject = function (filePathOrUrl, cb) {
phantasus.BlobFromPath.getFileBlob(filePathOrUrl, function (blob) {
//cb(phantasus.BlobFromPath.blobToFile(blob));
cb(blob);
});
};
// code taken from KineticJS
phantasus.Events = function () {
};
phantasus.Events.prototype = {
/**
* Pass in a string of events delimmited by a space to bind multiple events
* at once such as 'mousedown mouseup mousemove'. Include a namespace to
* bind an event by name such as 'click.foobar'.
*
* @param {String}
* evtStr e.g. 'click', 'mousedown touchstart', 'mousedown.foo
* touchstart.foo'
* @param {Function}
* handler The handler function is passed an event object
*/
on: function (evtStr, handler) {
if (!handler) {
throw Error('Handler not specified');
}
if (!this.eventListeners) {
this.eventListeners = {};
}
var events = evtStr.split(' '), len = events.length, n, event, parts, baseEvent, name;
/*
* loop through types and attach event listeners to each one. eg. 'click
* mouseover.namespace mouseout' will create three event bindings
*/
for (n = 0; n < len; n++) {
event = events[n];
parts = event.split('.');
baseEvent = parts[0];
name = parts[1] || '';
// create events array if it doesn't exist
if (!this.eventListeners[baseEvent]) {
this.eventListeners[baseEvent] = [];
}
this.eventListeners[baseEvent].push({
name: name,
handler: handler
});
}
return this;
},
getListeners: function () {
if (!this.eventListeners) {
this.eventListeners = {};
}
return this.eventListeners;
},
setListeners: function (eventListeners) {
this.eventListeners = eventListeners;
},
/**
* Fire an event.
*
* @param eventType
* @param evt
*/
trigger: function (eventType, evt) {
if (!this.eventListeners) {
this.eventListeners = {};
}
if (!evt) {
evt = {};
}
evt.type = eventType;
if (!evt.source) {
evt.source = this;
}
var events = this.eventListeners[eventType];
if (events) {
var len = events.length;
for (var i = 0; i < len; i++) {
events[i].handler.apply(this, [evt]);
}
}
return this;
},
/**
* Remove event bindings. Pass in a string of event types delimmited by a
* space to remove multiple event bindings at once such as 'mousedown
* mouseup mousemove'. include a namespace to remove an event binding by
* name such as 'click.foobar'. If you only give a name like '.foobar', all
* events in that namespace will be removed.
*
* @param {String}
* evtStr e.g. 'click', 'mousedown.foo touchstart', '.foobar'
*/
off: function (evtStr, handler) {
if (!this.eventListeners) {
this.eventListeners = {};
}
var events = (evtStr || '').split(' '), len = events.length, n, t, event, parts, baseEvent, name;
if (!evtStr) {
// remove all events
for (t in this.eventListeners) {
this._off(t, null, handler);
}
}
for (n = 0; n < len; n++) {
event = events[n];
parts = event.split('.');
baseEvent = parts[0];
name = parts[1];
if (baseEvent) {
if (this.eventListeners[baseEvent]) {
this._off(baseEvent, name, handler);
}
} else {
for (t in this.eventListeners) {
this._off(t, name, handler);
}
}
}
return this;
},
_off: function (type, name, handler) {
var evtListeners = this.eventListeners[type], i, evtName;
for (i = 0; i < evtListeners.length; i++) {
evtName = evtListeners[i].name;
// check if an event name is not specified, or if one is specified,
// it matches the current event name
if ((!name || evtName === name)
&& (handler == null || handler == evtListeners[i].handler)) {
evtListeners.splice(i, 1);
if (evtListeners.length === 0) {
delete this.eventListeners[type];
break;
}
i--;
}
}
}
};
phantasus.Identifier = function (array) {
this.array = array;
};
phantasus.Identifier.prototype = {
toString: function () {
return this.array.join(',');
},
equals: function (otherId) {
var other = otherId.getArray();
var length = this.array.length;
if (other.length !== length) {
return false;
}
for (var i = 0; i < length; i++) {
if (this.array[i] !== other[i]) {
return false;
}
}
return true;
},
getArray: function () {
return this.array;
}
};
phantasus.Map = function () {
this.map = {}; // object string -> key, value
// the key field is stored to get the original key object back
this.n = 0;
};
phantasus.Map.prototype = {
toJSON: function () {
var json = {};
this.forEach(function (value, key) {
json[key] = value;
});
return json;
},
toString: function () {
var s = [];
this.forEach(function (value, key) {
if (s.length > 0) {
s.push(', ');
}
s.push(key);
s.push('=');
s.push(value);
});
return s.join('');
},
keys: function () {
var keys = [];
for (var key in this.map) {
var pair = this.map[key];
keys.push(pair.key);
}
return keys;
},
size: function () {
return this.n;
},
equals: function (m) {
if (m.size() !== this.size()) {
return false;
}
var ret = true;
try {
this.forEach(function (value, key) {
if (value !== m.get(key)) {
ret = false;
throw 'break'; // break out of loop
}
});
}
catch (e) {
// catch break out of loop
}
return ret;
},
setAll: function (map) {
var _this = this;
map.forEach(function (value, key) {
_this.set(key, value);
});
},
set: function (key, value) {
var skey = '\0' + key;
var previous = this.map[skey];
if (previous === undefined) { // only increment size when this is a
// new key
this.n++;
}
this.map[skey] = {
key: key,
value: value
};
},
forEach: function (callback) {
for (var key in this.map) {
var pair = this.map[key];
callback(pair.value, pair.key);
}
},
entries: function () {
var array = [];
this.forEach(function (value, key) {
array.push({
value: value,
key: key
});
});
return array;
},
values: function () {
var values = [];
for (var key in this.map) {
var pair = this.map[key];
values.push(pair.value);
}
return values;
},
get: function (key) {
var skey = '\0' + key;
var pair = this.map[skey];
return pair !== undefined ? pair.value : undefined;
},
clear: function () {
this.map = {};
this.n = 0;
},
remove: function (key) {
var skey = '\0' + key;
var pair = this.map[skey];
if (pair !== undefined) {
delete this.map[skey];
this.n--;
return pair.value;
}
},
has: function (key) {
var skey = '\0' + key;
return this.map[skey] !== undefined;
}
};
phantasus.Map.fromJSON = function (json) {
var map = new phantasus.Map();
for (var key in json) {
map.set(key, json[key]);
}
return map;
};
phantasus.ParseDatasetFromProtoBin = function () {
};
phantasus.ParseDatasetFromProtoBin.parse = function (session, callback, options) {
var response = JSON.parse(session.txt)[0];
var filePath = options.pathFunction ?
options.pathFunction(response) :
phantasus.Util.getFilePath(session, response);
var r = new FileReader();
r.onload = function (e) {
var contents = e.target.result;
var ProtoBuf = dcodeIO.ProtoBuf;
ProtoBuf.protoFromFile("./message.proto", function (error, success) {
if (error) {
throw new Error(error);
}
var builder = success,
rexp = builder.build("rexp"),
REXP = rexp.REXP,
rclass = REXP.RClass;
var res = REXP.decode(contents);
var jsondata = phantasus.Util.getRexpData(res, rclass);
var datasets = [];
for (var k = 0; k < Object.keys(jsondata).length; k++) {
var dataset = phantasus.ParseDatasetFromProtoBin.getDataset(new Promise(function (resolve) {resolve(session)}),
Object.keys(jsondata)[k],
jsondata[Object.keys(jsondata)[k]],
options);
datasets.push(dataset);
}
callback(null, datasets);
});
};
phantasus.BlobFromPath.getFileObject(filePath, function (f) {
r.readAsArrayBuffer(f);
});
};
phantasus.ParseDatasetFromProtoBin.getDataset = function (session, seriesName, jsondata, options) {
var flatData = jsondata.data.values;
var nrowData = jsondata.data.dim[0];
var ncolData = jsondata.data.dim[1];
var flatPdata = jsondata.pdata.values;
var annotation = jsondata.fdata.values;
//var id = jsondata.rownames.values;
var metaNames = jsondata.colMetaNames.values;
var rowMetaNames = jsondata.rowMetaNames.values;
var experimentData = jsondata.experimentData;
// console.log(seriesName, jsondata);
var matrix = [];
for (var i = 0; i < nrowData; i++) {
var curArray = new Float32Array(ncolData);
for (var j = 0; j < ncolData; j++) {
curArray[j] = flatData[i + j * nrowData];
}
matrix.push(curArray);
}
var dataset = new phantasus.Dataset({
name: seriesName,
rows: nrowData,
columns: ncolData,
array: matrix,
dataType: 'Float32',
esSession: session,
isGEO: options.isGEO,
preloaded: options.preloaded,
experimentData: experimentData
});
// console.log(seriesName, dataset);
if (metaNames) {
for (i = 0; i < metaNames.length; i++) {
var curVec = dataset.getColumnMetadata().add(metaNames[i]);
for (j = 0; j < ncolData; j++) {
curVec.setValue(j, phantasus.Util.safeTrim(flatPdata[j + i * ncolData]));
}
}
}
// console.log(seriesName, "meta?");
//var rowIds = dataset.getRowMetadata().add('id');
// console.log(rowMetaNames);
for (i = 0; i < rowMetaNames.length; i++) {
curVec = dataset.getRowMetadata().add(rowMetaNames[i]);
for (j = 0; j < nrowData; j++) {
curVec.setValue(j, phantasus.Util.safeTrim(annotation[j + i * nrowData]));
//rowIds.setValue(j, id[j])
}
}
phantasus.MetadataUtil.maybeConvertStrings(dataset.getRowMetadata(), 1);
phantasus.MetadataUtil.maybeConvertStrings(dataset.getColumnMetadata(),
1);
return dataset;
};
phantasus.Set = function () {
this._map = new phantasus.Map();
};
phantasus.Set.prototype = {
toJSON: function () {
var json = [];
this.forEach(function (value) {
json.push(value);
});
return json;
},
toString: function () {
var s = [];
this.forEach(function (key) {
if (s.length > 0) {
s.push(', ');
}
s.push(key);
});
return s.join('');
},
size: function () {
return this._map.size();
},
equals: function (m) {
return this._map.equals(m);
},
forEach: function (callback) {
this._map.forEach(function (value, key) {
callback(key);
});
},
add: function (value) {
this._map.set(value, true);
},
values: function () {
var values = [];
this._map.forEach(function (value, key) {
values.push(key);
});
return values;
},
clear: function () {
this._map.clear();
},
remove: function (key) {
this._map.remove(key);
},
has: function (key) {
return this._map.has(key);
}
};
phantasus.Set.fromJSON = function (json) {
var set = new phantasus.Set();
for (var i = 0, length = json.length; i < length; i++) {
set.add(json[i]);
}
return set;
};
phantasus.ArrayBufferReader = function (buffer) {
this.buffer = buffer;
this.bufferLength = buffer.length;
this.index = 0;
this.decoder = phantasus.Util.createTextDecoder();
};
phantasus.ArrayBufferReader.prototype = {
readLine: function () {
var index = this.index;
var bufferLength = this.bufferLength;
if (index >= bufferLength) {
return null;
}
var buffer = this.buffer;
var start = index;
var end = start;
// dos: \r\n, old mac:\r
for (; index < bufferLength; index++) {
var c = buffer[index];
if (c === 10 || c === 13) { // \n or \r
end = index;
if ((index !== bufferLength - 1) && buffer[index + 1] === 10) { // skip
// ahead
index++;
}
index++;
break;
}
}
this.index = index;
if (start === end && index === bufferLength) { // eof
return this.decoder(this.buffer, start, bufferLength);
}
return this.decoder(this.buffer, start, end);
}
};
phantasus.ArrayBufferReader.getArrayBuffer = function (fileOrUrl, callback) {
var isString = typeof fileOrUrl === 'string' || fileOrUrl instanceof String;
if (isString) { // URL
var fetchOptions = {};
if (fileOrUrl.headers) {
fetchOptions.headers = new Headers();
for (var header in fileOrUrl.headers) {
fetchOptions.headers.append(header, fileOrUrl.headers[header]);
}
}
fetch(fileOrUrl, fetchOptions).then(function (response) {
if (response.ok) {
return response.arrayBuffer();
} else {
callback(new Error(fileOrUrl + ' status: ' + response.status));
}
}).then(function (buf) {
callback(null, buf);
}).catch(function (error) {
console.log('Fetch error', error);
callback(error);
});
} else {
var reader = new FileReader();
reader.onload = function (event) {
callback(null, event.target.result);
};
reader.onerror = function (event) {
callback(event);
};
reader.readAsArrayBuffer(fileOrUrl);
}
};
phantasus.Array2dReaderInteractive = function () {
};
phantasus.Array2dReaderInteractive.prototype = {
read: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util.getFileName(fileOrUrl));
var html = [];
html.push('<div>');
html.push('<label>Click the table cell containing the first data row and column.</label>');
html.push('<div class="checkbox"> <label> <input name="transpose" type="checkbox">' +
' Tranpose </label>' +
' </div>');
html.push('<div' +
' style="display:inline-block;width:10px;height:10px;background-color:#b3cde3;"></div><span> Data Matrix</span>');
html.push('<br /><div' +
' style="display:inline-block;width:10px;height:10px;background-color:#fbb4ae;"></div><span> Column' +
' Annotations</span>');
html.push('<br /><div' +
' style="display:inline-block;' +
' width:10px;height:10px;background-color:#ccebc5;"></div><span> Row' +
' Annotations</span>');
html.push('<div class="slick-bordered-table" style="width:550px;height:300px;"></div>');
html.push('</div>');
var $el = $(html.join(''));
phantasus.Util.readLines(fileOrUrl, true).done(function (lines) {
// show in table
var tab = /\t/;
for (var i = 0, nrows = lines.length; i < nrows; i++) {
lines[i] = lines[i].split(tab);
}
var grid;
var columns = [];
for (var j = 0, ncols = lines[0].length; j < ncols; j++) {
columns.push({
id: j,
field: j
});
}
var dataRowStart = 1;
var dataColumnStart = 1;
var _lines = lines;
var grid = new Slick.Grid($el.find('.slick-bordered-table')[0], lines, columns, {
enableCellNavigation: true,
headerRowHeight: 0,
showHeaderRow: false,
multiColumnSort: false,
multiSelect: false,
topPanelHeight: 0,
enableColumnReorder: false,
enableTextSelectionOnCells: true,
forceFitColumns: false,
defaultFormatter: function (row, cell, value, columnDef, dataContext) {
var color = 'white';
if (cell >= dataColumnStart && row >= dataRowStart) {
color = '#b3cde3'; // data
} else if (row <= (dataRowStart - 1) && cell >= dataColumnStart) {
color = '#fbb4ae'; // column
} else if (cell < dataColumnStart && row >= dataRowStart) {
color = '#ccebc5'; // row
}
var html = ['<div style="width:100%;height:100%;background-color:' + color + '">'];
if (_.isNumber(value)) {
html.push(phantasus.Util.nf(value));
} else if (phantasus.Util.isArray(value)) {
var s = [];
for (var i = 0, length = value.length; i < length; i++) {
if (i > 0) {
s.push(', ');
}
var val = value[i];
s.push(value[i]);
}
html.push(s.join(''));
} else {
html.push(value);
}
html.push('</div>');
return html.join('');
}
});
var transposedLines;
var transposedColumns;
$el.find('[name=transpose]').on('click', function (e) {
if ($(this).prop('checked')) {
if (transposedLines == null) {
transposedLines = [];
for (var j = 0, ncols = lines[0].length; j < ncols; j++) {
var row = [];
transposedLines.push(row);
for (var i = 0, nrows = lines.length; i < nrows; i++) {
row.push(lines[i][j]);
}
}
transposedColumns = [];
for (var j = 0, ncols = transposedLines[0].length; j < ncols; j++) {
transposedColumns.push({
id: j,
field: j
});
}
}
lines = transposedLines;
grid.setData(transposedLines);
grid.setColumns(transposedColumns);
grid.resizeCanvas();
grid.invalidate();
} else {
grid.setData(_lines);
grid.setColumns(columns);
grid.resizeCanvas();
grid.invalidate();
lines = _lines;
}
});
grid.onClick.subscribe(function (e, args) {
dataRowStart = args.row;
dataColumnStart = args.cell;
grid.invalidate();
});
$el.find('.slick-header').remove();
var footer = [];
footer
.push('<button name="ok" type="button" class="btn btn-default">OK</button>');
footer
.push('<button name="cancel" type="button" data-dismiss="modal" class="btn btn-default">Cancel</button>');
var $footer = $(footer.join(''));
phantasus.FormBuilder.showOkCancel({
title: 'Open',
content: $el,
close: false,
focus: document.activeElement,
cancelCallback: function () {
callback(null);
},
okCallback: function () {
_this._read(name, lines, dataColumnStart, dataRowStart, callback);
}
});
grid.resizeCanvas();
}).fail(function (err) {
callback(err);
});
},
_read: function (datasetName, lines, dataColumnStart, dataRowStart, cb) {
var columnCount = lines[0].length;
var columns = columnCount - dataColumnStart;
var rows = lines.length - dataRowStart;
var dataset = new phantasus.Dataset({
name: datasetName,
rows: rows,
columns: columns,
dataType: 'Float32'
});
// column metadata names are in 1st
// column
if (dataColumnStart > 0) {
for (var i = 0; i < dataRowStart; i++) {
var name = lines[i][0];
if (name == null || name === '' || name === 'na') {
name = 'id';
}
var unique = 1;
while (dataset.getColumnMetadata().getByName(name) != null) {
name = name + '-' + unique;
unique++;
}
var v = dataset.getColumnMetadata().add(name);
var nonEmpty = false;
for (var j = 0; j < columns; j++) {
var s = lines[i][j + dataColumnStart];
if (s != null && s !== '') {
nonEmpty = true;
v.setValue(j, s);
}
}
if (!nonEmpty) {
dataset.getColumnMetadata().remove(phantasus.MetadataUtil.indexOf(dataset.getColumnMetadata(), v.getName()));
}
}
}
if (dataRowStart > 0) {
// row metadata names are in first row
for (var j = 0; j < dataColumnStart; j++) {
var name = lines[0][j];
if (name == null || name === '') {
name = 'id';
}
var unique = 1;
while (dataset.getRowMetadata().get(name) != null) {
name = name + '-' + unique;
unique++;
}
dataset.getRowMetadata().add(name);
}
}
for (var i = 0; i < rows; i++) {
for (var j = 0, k = 0; k < dataset.getRowMetadata().getMetadataCount(); j++, k++) {
var metaDataValue = lines[i + dataRowStart][j];
dataset.getRowMetadata().get(j).setValue(i, metaDataValue);
}
}
for (var i = 0; i < rows; i++) {
for (var j = 0; j < columns; j++) {
var s = lines[i + dataRowStart][j + dataColumnStart];
dataset.setValue(i, j, parseFloat(s));
}
}
phantasus.MetadataUtil.maybeConvertStrings(dataset.getRowMetadata(), 1);
phantasus.MetadataUtil.maybeConvertStrings(dataset.getColumnMetadata(),
1);
cb(null, dataset);
}
};
phantasus.BufferedReader = function (reader, callback, doneCallback) {
var textDecoder = phantasus.Util.createTextDecoder();
var skipLF = false;
var text = '';
reader.read().then(function processResult(result) {
// result contains a value which is an array of Uint8Array
text += (result.done ? '' : textDecoder(result.value, 0, result.value.length));
var start = 0;
// TODO no need to search previous chunk of text
for (var i = 0, length = text.length; i < length; i++) {
var c = text[i];
if (skipLF && c === '\n') {
start++;
skipLF = false;
} else if (c === '\n' || c === '\r') {
skipLF = c === '\r'; // \r\n windows line ending
var s = phantasus.Util.copyString(text.substring(start, i));
callback(s);
start = i + 1;
} else {
skipLF = false;
}
}
text = start < text.length ? text.substring(start) : '';
if (!result.done) {
return reader.read().then(processResult);
} else {
if (text !== '' && text !== '\r') {
callback(text);
}
doneCallback();
}
});
};
phantasus.BufferedReader.parse = function (url, options) {
var delim = options.delimiter;
var regex = new RegExp(delim);
var handleTokens = options.handleTokens;
var complete = options.complete;
var fetchOptions = {};
if (url.headers) {
fetchOptions.headers = new Headers();
for (var header in url.headers) {
fetchOptions.headers.append(header, url.headers[header]);
}
}
fetch(url, fetchOptions).then(function (response) {
if (response.ok) {
var reader = response.body.getReader();
new phantasus.BufferedReader(reader, function (line) {
handleTokens(line.split(regex));
}, function () {
complete();
});
} else {
options.error('Network error');
}
}).catch(function (error) {
options.error(error);
});
};
/**
* Class for reading cls files. <p/> <p/> The CLS files are simple files created
* to load class information into GeneCluster. These files use spaces to
* separate the fields.
* </P>
* <UL>
* <LI>The first line of a CLS file contains numbers indicating the number of
* samples and number of classes. The number of samples should correspond to the
* number of samples in the associated RES or GCT data file.</LI>
* <p/>
* <UL>
* <LI>Line format: (number of samples) (space) (number of classes) (space) 1</LI>
* <LI>For example: 58 2 1</LI>
* </UL>
* <p/>
* <LI>The second line in a CLS file contains names for the class numbers. The
* line should begin with a pound sign (#) followed by a space.</LI>
* <p/>
* <UL>
* <LI>Line format: # (space) (class 0 name) (space) (class 1 name)</LI>
* <p/>
* <LI>For example: # cured fatal/ref.</LI>
* </UL>
* <p/>
* <LI>The third line contains numeric class labels for each of the samples.
* The number of class labels should be the same as the number of samples
* specified in the first line.</LI>
* <UL>
* <LI>Line format: (sample 1 class) (space) (sample 2 class) (space) ...
* (sample N class)</LI>
* <LI>For example: 0 0 0 ... 1
* </UL>
* <p/>
* </UL>
*/
phantasus.ClsReader = function () {
};
phantasus.ClsReader.prototype = {
/**
* Parses the cls file.
*
* @param lines
* The lines to read.
* @throw Error If there is a problem with the data
*/
read: function (lines) {
var regex = /[ ,]+/;
// header= <num_data> <num_classes> 1
var header = lines[0].split(regex);
if (header.length < 3) {
throw new Error('Header line needs three numbers');
}
var headerNumbers = [];
try {
for (var i = 0; i < 3; i++) {
headerNumbers[i] = parseInt($.trim(header[i]));
}
}
catch (e) {
throw new Error('Header line element ' + i + ' is not a number');
}
if (headerNumbers[0] <= 0) {
throw new Error(
'Header line missing first number, number of data points');
}
if (headerNumbers[1] <= 0) {
throw new Error(
'Header line missing second number, number of classes');
}
var numClasses = headerNumbers[1];
var numItems = headerNumbers[0];
var classDefinitionLine = lines[1];
classDefinitionLine = classDefinitionLine.substring(classDefinitionLine
.indexOf('#') + 1);
var classNames = $.trim(classDefinitionLine).split(regex);
if (classNames.length < numClasses) {
throw new Error('First line specifies ' + numClasses
+ ' classes, but found ' + classNames.length + '.');
}
var dataLine = lines[2];
var assignments = dataLine.split(regex);
// convert the assignments to names
for (var i = 0; i < assignments.length; i++) {
var assignment = $.trim(assignments[i]);
var index = parseInt(assignment);
var tmp = classNames[index];
if (tmp !== undefined) {
assignments[i] = tmp;
}
}
return assignments;
}
};
phantasus.ClsWriter = function () {
};
phantasus.ClsWriter.prototype = {
write: function (vector) {
var pw = [];
var size = vector.size();
pw.push(size);
pw.push(' ');
var set = phantasus.VectorUtil.getSet(vector);
pw.push(set.size());
pw.push(' ');
pw.push('1\n');
pw.push('#');
var valueToIndex = new phantasus.Map();
var index = 0;
set.forEach(function (name) {
pw.push(' ');
pw.push(name);
valueToIndex.set(name, index++);
});
pw.push('\n');
for (var i = 0; i < size; i++) {
if (i > 0) {
pw.push(' ');
}
pw.push(valueToIndex.get(vector.getValue(i)));
}
pw.push('\n');
return pw.join('');
}
};
phantasus.GctReader = function () {
};
phantasus.GctReader.prototype = {
getFormatName: function () {
return 'gct';
},
read: function (fileOrUrl, callback) {
var _this = this;
if (phantasus.Util.isFile(fileOrUrl)) {
this._readChunking(fileOrUrl, callback, false);
} else {
if (phantasus.Util.isFetchStreamingSupported()) {
this._readChunking(fileOrUrl, callback, true);
} else {
this._readNoChunking(fileOrUrl, callback);
}
// XXX only do byte range requests from S3
// if (fileOrUrl.indexOf('s3.amazonaws.com') !== -1) {
// $.ajax({
// url: fileOrUrl,
// method: 'HEAD'
// }).done(function (data, textStatus, jqXHR) {
// if ('gzip' === jqXHR.getResponseHeader('Content-Encoding')) {
// _this._readNoChunking(fileOrUrl, callback);
// } else {
// _this._readChunking(fileOrUrl, callback, false);
// }
// }).fail(function () {
// _this._readNoChunking(fileOrUrl, callback);
// });
// } else {
// _this._readNoChunking(fileOrUrl, callback);
// }
}
},
_readChunking: function (fileOrUrl, callback, useFetch) {
var _this = this;
// Papa.LocalChunkSize = 10485760 * 10; // 100 MB
//Papa.RemoteChunkSize = 10485760 / 2; // 10485760 = 10MB
var lineNumber = 0;
var version;
var numRowAnnotations = 1; // in addition to row id
var numColumnAnnotations = 0; // in addition to column id
var nrows = -1;
var ncols = -1;
var version = 2;
var rowMetadataNames = [];
var columnMetadataNames = [];
var rowMetadata = [[]];
var columnMetadata = [[]];
var dataColumnStart;
var matrix = [];
var dataMatrixLineNumberStart;
var columnIdFieldName = 'id';
var rowIdFieldName = 'id';
var columnNamesArray;
var handleTokens = function (tokens) {
if (lineNumber === 0) {
var text = tokens[0].trim();
if ('#1.2' === text) {
version = 2;
} else if ('#1.3' === text) {
version = 3;
} else {
// console.log('Unknown version: assuming version 2');
}
} else if (lineNumber === 1) {
var dimensions = tokens;
if (version === 3) {
if (dimensions.length >= 4) {
nrows = parseInt(dimensions[0]);
ncols = parseInt(dimensions[1]);
numRowAnnotations = parseInt(dimensions[2]);
numColumnAnnotations = parseInt(dimensions[3]);
} else { // no dimensions specified
numRowAnnotations = parseInt(dimensions[0]);
numColumnAnnotations = parseInt(dimensions[1]);
}
} else {
nrows = parseInt(dimensions[0]);
ncols = parseInt(dimensions[1]);
if (nrows <= 0 || ncols <= 0) {
callback(
'Number of rows and columns must be greater than 0.');
}
}
dataColumnStart = numRowAnnotations + 1;
} else if (lineNumber === 2) {
columnNamesArray = tokens;
for (var i = 0; i < columnNamesArray.length; i++) {
columnNamesArray[i] = phantasus.Util.copyString(columnNamesArray[i]);
}
if (ncols === -1) {
ncols = columnNamesArray.length - numRowAnnotations - 1;
}
if (version == 2) {
var expectedColumns = ncols + 2;
if (columnNamesArray.length !== expectedColumns) {
// check for trailing tabs
if (columnNamesArray.length > expectedColumns) {
var skip = columnNamesArray.length - 1;
for (var i = columnNamesArray.length - 1; i >= 0; i--, skip--) {
if (columnNamesArray[i] !== '') {
break;
}
}
if (skip !== columnNamesArray.length - 1) {
columnNamesArray = columnNamesArray.slice(0, skip + 1);
}
}
if (columnNamesArray.length !== expectedColumns) {
return callback('Expected ' + (expectedColumns - 2)
+ ' column names, but read '
+ (columnNamesArray.length - 2) + ' column names.');
}
}
}
var name = columnNamesArray[0];
var slashIndex = name.lastIndexOf('/');
if (slashIndex != -1 && slashIndex < (name.length - 1)) {
rowIdFieldName = name.substring(0, slashIndex);
columnIdFieldName = name.substring(slashIndex + 1);
}
rowMetadataNames.push(rowIdFieldName);
columnMetadataNames.push(columnIdFieldName);
for (var j = 0; j < ncols; j++) {
var index = j + numRowAnnotations + 1;
var columnName = index < columnNamesArray.length ? columnNamesArray[index]
: null;
columnMetadata[0].push(phantasus.Util.copyString(columnName));
}
for (var j = 0; j < numRowAnnotations; j++) {
var rowMetadataName = '' === columnNamesArray[1] ? 'description'
: columnNamesArray[j + 1];
rowMetadataNames.push(
rowMetadataName);
rowMetadata.push([]);
}
dataMatrixLineNumberStart = 3 + numColumnAnnotations;
} else { // lines >=3
if (lineNumber < dataMatrixLineNumberStart) {
var metadataName = phantasus.Util.copyString(tokens[0]);
var v = [];
columnMetadata.push(v);
columnMetadataNames.push(metadataName);
for (var j = 0; j < ncols; j++) {
v.push(phantasus.Util.copyString(tokens[j + dataColumnStart]));
}
} else { // data lines
if (tokens[0] !== '') {
var array = new Float32Array(ncols);
matrix.push(array);
// we iterate to numRowAnnotations + 1 to include id row
// metadata field
for (var rowAnnotationIndex = 0; rowAnnotationIndex <= numRowAnnotations; rowAnnotationIndex++) {
var rowMetadataValue = tokens[rowAnnotationIndex];
rowMetadata[rowAnnotationIndex].push(
phantasus.Util.copyString(rowMetadataValue));
}
for (var columnIndex = 0; columnIndex < ncols; columnIndex++) {
var token = tokens[columnIndex + dataColumnStart];
array[columnIndex] = parseFloat(token);
}
}
}
}
lineNumber++;
};
(useFetch ? phantasus.BufferedReader : Papa).parse(fileOrUrl, {
delimiter: '\t', // auto-detect
newline: '', // auto-detect
header: false,
dynamicTyping: false,
preview: 0,
encoding: '',
worker: false,
comments: false,
handleTokens: handleTokens,
step: function (result) {
handleTokens(result.data[0]);
},
complete: function () {
var dataset = new phantasus.Dataset({
name: phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl)),
rows: matrix.length,
columns: ncols,
array: matrix,
dataType: 'Float32'
});
for (var i = 0; i < rowMetadataNames.length; i++) {
dataset.getRowMetadata().add(rowMetadataNames[i]).array = rowMetadata[i];
}
for (var i = 0; i < columnMetadataNames.length; i++) {
dataset.getColumnMetadata().add(columnMetadataNames[i]).array = columnMetadata[i];
}
phantasus.MetadataUtil.maybeConvertStrings(dataset.getRowMetadata(), 1);
phantasus.MetadataUtil.maybeConvertStrings(dataset.getColumnMetadata(),
1);
callback(null, dataset);
},
error: function (err) {
callback(err);
},
download: !phantasus.Util.isFile(fileOrUrl),
skipEmptyLines: false,
chunk: undefined,
fastMode: true,
beforeFirstChunk: undefined,
withCredentials: undefined
});
},
_read: function (datasetName, reader) {
var tab = /\t/;
var versionLine = phantasus.Util.copyString(reader.readLine().trim());
if (versionLine === '') {
throw new Error('Missing version line');
}
var version = 2;
if ('#1.2' === versionLine) {
version = 2;
} else if ('#1.3' === versionLine) {
version = 3;
} else {
// console.log('Unknown version: assuming version 2');
}
var dimensionsLine = phantasus.Util.copyString(reader.readLine());
if (dimensionsLine == null) {
throw new Error('No dimensions specified');
}
// <numRows> <tab> <numCols>
var dimensions = dimensionsLine.split(/[ \t]/);
var numRowAnnotations = 1; // in addition to row id
var numColumnAnnotations = 0; // in addition to column id
var nrows = -1;
var ncols = -1;
if (version === 3) {
if (dimensions.length >= 4) {
nrows = parseInt(dimensions[0]);
ncols = parseInt(dimensions[1]);
numRowAnnotations = parseInt(dimensions[2]);
numColumnAnnotations = parseInt(dimensions[3]);
} else { // no dimensions specified
numRowAnnotations = parseInt(dimensions[0]);
numColumnAnnotations = parseInt(dimensions[1]);
}
} else {
nrows = parseInt(dimensions[0]);
ncols = parseInt(dimensions[1]);
if (nrows <= 0 || ncols <= 0) {
throw new Error(
'Number of rows and columns must be greater than 0.');
}
}
var columnNamesLine = phantasus.Util.copyString(reader.readLine());
if (columnNamesLine == null) {
throw new Error('No column annotations');
}
var columnNamesArray = columnNamesLine.split(tab);
if (ncols === -1) {
ncols = columnNamesArray.length - numRowAnnotations - 1;
}
if (version == 2) {
var expectedColumns = ncols + 2;
if (columnNamesArray.length !== expectedColumns) {
throw new Error('Expected ' + (expectedColumns - 2)
+ ' column names, but read '
+ (columnNamesArray.length - 2) + ' column names.');
}
}
var name = columnNamesArray[0];
var slashIndex = name.lastIndexOf('/');
var columnIdFieldName = 'id';
var rowIdFieldName = 'id';
if (slashIndex != -1 && slashIndex < (name.length - 1)) {
rowIdFieldName = name.substring(0, slashIndex);
columnIdFieldName = name.substring(slashIndex + 1);
}
if (nrows === -1) {
var matrix = [];
var rowMetadataNames = [rowIdFieldName];
var columnMetadataNames = [columnIdFieldName];
var rowMetadata = [[]];
var columnMetadata = [[]];
for (var j = 0; j < ncols; j++) {
var index = j + numRowAnnotations + 1;
var columnName = index < columnNamesArray.length ? columnNamesArray[index]
: null;
columnMetadata[0].push(phantasus.Util.copyString(columnName));
}
for (var j = 0; j < numRowAnnotations; j++) {
var rowMetadataName = '' === columnNamesArray[1] ? 'description'
: columnNamesArray[j + 1];
rowMetadataNames.push(
rowMetadataName);
rowMetadata.push([]);
}
var dataColumnStart = numRowAnnotations + 1;
var ntokens = ncols + numRowAnnotations + 1;
var linen = 3;
if (numColumnAnnotations > 0) {
for (var columnAnnotationIndex = 0; columnAnnotationIndex < numColumnAnnotations; columnAnnotationIndex++) {
var tokens = reader.readLine().split(tab);
var metadataName = tokens[0];
var v = [];
columnMetadata.push(v);
columnMetadataNames.push(metadataName);
for (var j = 0; j < ncols; j++) {
v.push(phantasus.Util.copyString(tokens[j + dataColumnStart]));
}
}
}
var nonEmptyDescriptionFound = false;
var numRowAnnotationsPlusOne = numRowAnnotations + 1;
var s;
while ((s = reader.readLine()) !== null) {
if (s !== '') {
var array = new Float32Array(ncols);
matrix.push(array);
var tokens = s.split(tab);
// we iterate to numRowAnnotations + 1 to include id row
// metadata field
for (var rowAnnotationIndex = 0; rowAnnotationIndex < numRowAnnotationsPlusOne; rowAnnotationIndex++) {
var rowMetadataValue = tokens[rowAnnotationIndex];
rowMetadata[rowAnnotationIndex].push(
phantasus.Util.copyString(rowMetadataValue));
}
for (var columnIndex = 0; columnIndex < ncols; columnIndex++) {
var token = tokens[columnIndex + dataColumnStart];
array[columnIndex] = parseFloat(token);
}
}
}
var dataset = new phantasus.Dataset({
name: datasetName,
rows: matrix.length,
columns: ncols,
array: matrix,
dataType: 'Float32'
});
for (var i = 0; i < rowMetadataNames.length; i++) {
dataset.getRowMetadata().add(rowMetadataNames[i]).array = rowMetadata[i];
}
for (var i = 0; i < columnMetadataNames.length; i++) {
dataset.getColumnMetadata().add(columnMetadataNames[i]).array = columnMetadata[i];
}
phantasus.MetadataUtil.maybeConvertStrings(dataset.getRowMetadata(), 1);
phantasus.MetadataUtil.maybeConvertStrings(dataset.getColumnMetadata(),
1);
return dataset;
} else {
var dataset = new phantasus.Dataset({
dataType: 'Float32',
name: datasetName,
rows: nrows,
columns: ncols
});
var columnIds = dataset.getColumnMetadata().add(columnIdFieldName);
if (version == 3) {
for (var j = 0; j < ncols; j++) {
var index = j + numRowAnnotations + 1;
var columnName = index < columnNamesArray.length ? columnNamesArray[index]
: null;
columnIds.setValue(j, phantasus.Util.copyString(columnName));
}
} else {
for (var j = 0; j < ncols; j++) {
var columnName = columnNamesArray[j + numRowAnnotations + 1];
columnIds.setValue(j, phantasus.Util.copyString(columnName));
}
}
var rowAnnotationVectors = [
dataset.getRowMetadata().add(
rowIdFieldName)];
if (version === 3) {
for (var j = 0; j < numRowAnnotations; j++) {
var rowMetadataName = '' === columnNamesArray[1] ? 'description'
: columnNamesArray[j + 1];
rowAnnotationVectors.push(dataset.getRowMetadata().add(
rowMetadataName));
}
} else {
rowAnnotationVectors.push(dataset.getRowMetadata().add(
columnNamesArray[1]));
}
var dataColumnStart = numRowAnnotations + 1;
var ntokens = ncols + numRowAnnotations + 1;
var linen = 3;
if (numColumnAnnotations > 0) {
for (var columnAnnotationIndex = 0; columnAnnotationIndex < numColumnAnnotations; columnAnnotationIndex++) {
var tokens = reader.readLine().split(tab);
var metadataName = tokens[0];
var v = dataset.getColumnMetadata().add(metadataName);
for (var j = 0; j < ncols; j++) {
v.setValue(j, phantasus.Util.copyString(tokens[j + dataColumnStart]));
}
}
}
var nonEmptyDescriptionFound = false;
var numRowAnnotationsPlusOne = numRowAnnotations + 1;
for (var rowIndex = 0, nrows = dataset.getRowCount(); rowIndex < nrows; rowIndex++) {
var s = reader.readLine();
if (s === null) {
throw new Error('Missing data rows.');
}
var tokens = s.split(tab);
if (version === 2) {
rowAnnotationVectors[0].setValue(rowIndex, phantasus.Util.copyString(tokens[0]));
var desc = tokens[1];
if (!nonEmptyDescriptionFound) {
nonEmptyDescriptionFound = desc !== '';
}
rowAnnotationVectors[1].setValue(rowIndex, phantasus.Util.copyString(desc));
} else {
// we iterate to numRowAnnotations + 1 to include id row
// metadata field
for (var rowAnnotationIndex = 0; rowAnnotationIndex < numRowAnnotationsPlusOne; rowAnnotationIndex++) {
var rowMetadataValue = tokens[rowAnnotationIndex];
rowAnnotationVectors[rowAnnotationIndex].setValue(rowIndex,
phantasus.Util.copyString(rowMetadataValue));
}
}
for (var columnIndex = 0; columnIndex < ncols; columnIndex++) {
var token = tokens[columnIndex + dataColumnStart];
// if (token[0] === '{') {
// var value = JSON.parse(token);
// dataset.setValue(rowIndex, columnIndex, phantasus.Util
// .wrapNumber(value.__v, value));
// } else {
// dataset.setValue(rowIndex, columnIndex, parseFloat(token));
// }
dataset.setValue(rowIndex, columnIndex, parseFloat(token));
}
}
if (version === 2 && !nonEmptyDescriptionFound) {
dataset.getRowMetadata().remove(1);
}
if (rowIndex !== nrows) {
throw new Error('Missing data rows');
}
phantasus.MetadataUtil.maybeConvertStrings(dataset.getRowMetadata(), 1);
phantasus.MetadataUtil.maybeConvertStrings(dataset.getColumnMetadata(),
1);
return dataset;
}
},
_readNoChunking: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl));
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (err, arrayBuffer) {
if (err) {
callback(err);
} else {
callback(null, _this._read(name,
new phantasus.ArrayBufferReader(new Uint8Array(
arrayBuffer))));
}
});
// $.ajax({
// url: fileOrUrl,
// dataType: 'text'
// }).done(function (text) {
// callback(null, _this.read(name, new phantasus.StringReader(text)));
// }).fail(function (err) {
// callback(err);
// });
}
};
phantasus.GctWriter = function () {
this.nf = phantasus.Util.createNumberFormat('.5g');
};
phantasus.GctWriter.idFirst = function (model) {
var fields = ['id', 'Id', 'pr_id'];
var idIndex = -1;
for (var i = 0; i < fields.length; i++) {
idIndex = phantasus.MetadataUtil.indexOf(model, fields[i]);
if (idIndex !== -1) {
break;
}
}
if (idIndex !== -1) {
var order = [];
order[0] = idIndex;
for (var i = 0, j = 1, count = model.getMetadataCount(); i < count; i++) {
if (i !== idIndex) {
order[j++] = i;
}
}
return new phantasus.MetadataModelColumnView(model, order);
}
return model;
};
phantasus.GctWriter.prototype = {
setNumberFormat: function (nf) {
this.nf = nf;
},
getExtension: function () {
return 'gct';
},
write: function (dataset, pw) {
if (pw == null) {
pw = [];
}
var rowMetadata = phantasus.GctWriter.idFirst(dataset.getRowMetadata());
var columnMetadata = phantasus.GctWriter.idFirst(dataset
.getColumnMetadata());
this.writeHeader(rowMetadata, columnMetadata, pw);
this.writeData(dataset, rowMetadata, pw);
//console.log(this, dataset, pw);
return pw.join('');
},
writeData: function (dataset, rowMetadata, pw) {
//console.log("writeData")
var ncols = dataset.getColumnCount();
var rowMetadataCount = rowMetadata.getMetadataCount();
var nf = this.nf;
for (var i = 0, rows = dataset.getRowCount(); i < rows; i++) {
for (var rowMetadataIndex = 0; rowMetadataIndex < rowMetadataCount; rowMetadataIndex++) {
if (rowMetadataIndex > 0) {
pw.push('\t');
}
var vector = rowMetadata.get(rowMetadataIndex);
var value = vector.getValue(i);
if (value !== null) {
var toString = phantasus.VectorTrack.vectorToString(vector);
pw.push(toString(value));
}
}
for (var j = 0; j < ncols; j++) {
pw.push('\t');
var value = dataset.getValue(i, j);
pw.push(nf(value));
}
pw.push('\n');
}
},
writeHeader: function (rowMetadata, columnMetadata, pw) {
var rows = rowMetadata.getItemCount();
var ncols = columnMetadata.getItemCount();
pw.push('#1.3\n');
var rowMetadataCount = rowMetadata.getMetadataCount();
pw.push(rows + '\t' + ncols + '\t' + (rowMetadataCount - 1) + '\t'
+ (columnMetadata.getMetadataCount() - 1));
pw.push('\n');
for (var i = 0; i < rowMetadataCount; i++) {
if (i > 0) {
pw.push('\t');
}
var name = rowMetadata.get(i).getName();
if (i === 0 && name !== columnMetadata.get(0).getName()) {
name = name + '/' + columnMetadata.get(0).getName();
}
pw.push(name);
}
var toString = phantasus.VectorTrack.vectorToString(columnMetadata.get(0));
for (var j = 0; j < ncols; j++) {
pw.push('\t');
pw.push(toString(columnMetadata.get(0).getValue(j)));
}
pw.push('\n');
for (var columnMetadataIndex = 1, metadataSize = columnMetadata
.getMetadataCount(); columnMetadataIndex < metadataSize; columnMetadataIndex++) {
pw.push(columnMetadata.get(columnMetadataIndex).getName());
for (var i = 1; i < rowMetadataCount; i++) {
pw.push('\t');
pw.push('na');
}
for (var j = 0; j < ncols; j++) {
pw.push('\t');
var vector = columnMetadata.get(columnMetadataIndex);
var value = vector.getValue(j);
if (value != null) {
toString = phantasus.VectorTrack.vectorToString(columnMetadata.get(0));
pw.push(toString(value));
}
}
pw.push('\n');
}
}
};
phantasus.GctWriter12 = function () {
this.options = {
rowDescription: 'Description',
rowId: 'id',
columnId: 'id'
};
this.nf = phantasus.Util.createNumberFormat('.5g');
};
phantasus.GctWriter12.prototype = {
setNumberFormat: function (nf) {
this.nf = nf;
},
getExtension: function () {
return 'gct';
},
write: function (dataset, pw) {
if (pw == null) {
pw = [];
}
var rows = dataset.getRowCount();
var columns = dataset.getColumnCount();
var version = '#1.2';
pw.push(version);
pw.push('\n');
pw.push(rows + '\t' + columns);
pw.push('\n');
var rowMetadata = phantasus.GctWriter.idFirst(dataset.getRowMetadata());
var columnMetadata = phantasus.GctWriter.idFirst(dataset
.getColumnMetadata());
pw.push('Name');
pw.push('\t');
pw.push('Description');
var columnIds = columnMetadata.getByName(this.options.columnId);
if (!columnIds) {
columnIds = columnMetadata.get(0);
}
var columnIdToString = phantasus.VectorTrack.vectorToString(columnIds);
for (var j = 0; j < columns; j++) {
pw.push('\t');
pw.push(columnIdToString(columnIds.getValue(j)));
}
var rowIds = rowMetadata.get(this.options.rowId);
if (!rowIds) {
rowIds = rowMetadata.get(0);
}
var rowDescriptions = rowMetadata
.getByName(this.options.rowDescription);
if (rowDescriptions == null && rowMetadata.getMetadataCount() > 1) {
rowDescriptions = rowMetadata.get(1);
}
var rowIdToString = phantasus.VectorTrack.vectorToString(rowIds);
var rowDescriptionToString = rowDescriptions != null ? phantasus.VectorTrack.vectorToString(rowDescriptions) : null;
var nf = this.nf;
for (var i = 0; i < rows; i++) {
pw.push('\n');
pw.push(rowIdToString(rowIds.getValue(i)));
pw.push('\t');
var rowDescription = rowDescriptions != null ? rowDescriptions
.getValue(i) : null;
if (rowDescription != null) {
pw.push(rowDescriptionToString(rowDescription));
}
for (var j = 0; j < columns; j++) {
pw.push('\t');
pw.push(nf(dataset.getValue(i, j)));
}
}
pw.push('\n');
return pw.join('');
}
};
phantasus.GeoReader = function () {
};
phantasus.GeoReader.prototype = {
read: function (name, callback) {
// console.log("read", name);
var afterLoaded = function (err, dataset) {
if (!err) {
var datasetTitle = "GEO dataset";
var experimentData = dataset[0].getExperimentData();
if (experimentData) datasetTitle = experimentData.title.values.toString() || datasetTitle;
var geoAccesion = name.split('-')[0];
phantasus.datasetHistory.store({
name: geoAccesion,
description: datasetTitle,
openParameters: {
file: geoAccesion,
options: {
interactive: true,
isGEO: true
}
}
});
}
callback(err, dataset);
};
var req = ocpu.call('loadGEO/print', { name: name }, function (session) {
// session.getMessages(function (success) {
// console.log('loadGEO messages', '::', success);
// });
phantasus.ParseDatasetFromProtoBin.parse(session, afterLoaded, { isGEO : true, pathFunction: phantasus.GeoReader.prototype.getPath });
});
req.fail(function () {
callback(new Error(_.first(req.responseText.split('\n'))));
});
},
getPath: function (fragment) {
return window.libraryPrefix.slice(0, -1) + fragment;
}
};
phantasus.GisticReader = function () {
};
phantasus.GisticReader.prototype = {
read: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl));
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (err,
arrayBuffer) {
if (err) {
callback(err);
} else {
try {
callback(null, _this._read(name,
new phantasus.ArrayBufferReader(new Uint8Array(
arrayBuffer))));
}
catch (x) {
if (x.stack) {
// console.log(x.stack);
}
callback(x);
}
}
});
},
_read: function (datasetName, reader) {
var tab = /\t/;
var header = reader.readLine().trim().split(tab);
// Unique Name, Descriptor, Wide Peak Limits, Peak Limits, Region
// Limits, q values, Residual q values after removing segments shared
// with higher peaks, Broad or Focal, Amplitude Threshold
var ncols = header.length - 9;
var matrix = [];
var s;
var rowIds = [];
var qValues = [];
while ((s = reader.readLine()) !== null) {
s = s.trim();
if (s !== '') {
var tokens = s.split(tab);
if (tokens[8] === 'Actual Copy Change Given') {
var array = new Float32Array(ncols);
matrix.push(array);
rowIds.push(String($.trim(tokens[1])));
qValues.push(parseFloat(tokens[5]));
for (var j = 9; j <= ncols; j++) {
var token = tokens[j];
array[j - 9] = parseFloat(token);
}
}
}
}
var dataset = new phantasus.Dataset({
name: datasetName,
rows: matrix.length,
columns: ncols,
array: matrix,
dataType: 'Float32'
});
var columnIds = dataset.getColumnMetadata().add('id');
for (var j = 0; j < ncols; j++) {
columnIds.setValue(j, String(header[j + 9]));
}
dataset.getRowMetadata().add('id').array = rowIds;
dataset.getRowMetadata().add('q_value').array = qValues;
return dataset;
}
};
phantasus.GmtDatasetReader = function () {
};
phantasus.GmtDatasetReader.prototype = {
getFormatName: function () {
return 'gmt';
},
read: function (fileOrUrl, callback) {
var name = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl));
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (err, arrayBuffer) {
if (err) {
callback(err);
} else {
try {
callback(null, phantasus.DatasetUtil.geneSetsToDataset(name,
new phantasus.GmtReader()
.read(new phantasus.ArrayBufferReader(
new Uint8Array(arrayBuffer)))));
}
catch (x) {
callback(x);
}
}
});
}
};
phantasus.GmtReader = function () {
};
phantasus.GmtReader.prototype = {
read: function (reader) {
var sets = [];
var tab = /\t/;
var s;
while ((s = reader.readLine()) != null) {
if (s === '' || s[0] === '#') {
continue;
}
var tokens = s.split(tab);
var name = tokens[0].trim();
var description = tokens.length > 1 ? tokens[1].trim() : '';
if ('BLANK' === description) {
description = '';
}
var ids = [];
for (var i = 2; i < tokens.length; i++) {
var geneName = tokens[i].trim();
if (geneName !== '') {
ids.push(geneName);
}
}
var set = {
name: name,
description: description,
ids: ids
};
set.toString = function () {
return this.name;
};
sets.push(set);
}
return sets;
}
};
phantasus.JsonDatasetReader = function () {
};
phantasus.JsonDatasetReader.prototype = {
read: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util.getFileName(fileOrUrl));
var isString = typeof fileOrUrl === 'string' || fileOrUrl instanceof String;
if (isString) {
fetch(fileOrUrl).then(function (response) {
if (response.ok) {
return response.text();
} else {
callback(response.status + ' ' + response.statusText);
}
}).then(function (text) {
callback(null, phantasus.Dataset.fromJSON(JSON.parse(text.trim())));
}).catch(function (err) {
callback(err);
});
} else {
var reader = new FileReader();
reader.onload = function (event) {
callback(null, phantasus.Dataset.fromJSON(JSON.parse(event.target.result)));
};
reader.onerror = function (event) {
callback(event);
};
reader.readAsText(fileOrUrl);
}
}
};
phantasus.MafFileReader = function () {
this.geneFilter = null;
};
/**
*
* @param options.dataset
* @param options.fields
*/
phantasus.MafFileReader.summarizeMutations = function (options) {
var dataset = options.dataset;
var fields = options.fields;
var count = fields.length;
var vector = dataset.getRowMetadata().add('mutation_summary');
vector.getProperties().set(
phantasus.VectorKeys.FIELDS, fields);
vector.getProperties().set(phantasus.VectorKeys.DATA_TYPE, '[number]');
// computing dynamically screws things up b/c summary is computed for other data types (e.g. CN)
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
var bins = new Int32Array(count); // 1-count
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var value = dataset.getValue(i, j);
if (value > 0) {
bins[value - 1]++;
}
}
vector.setValue(i, bins);
}
};
phantasus.MafFileReader.getField = function (fieldNames, headerToIndex) {
var name;
var index;
for (var i = 0; i < fieldNames.length; i++) {
name = fieldNames[i];
var lc = name.toLowerCase();
index = headerToIndex[lc];
if (index !== undefined) {
break;
}
}
if (index !== undefined) {
return {
name: name,
index: index
};
}
};
phantasus.MafFileReader.VARIANT_MAP = new phantasus.Map();
// silent
phantasus.MafFileReader.VARIANT_MAP.set('Silent', 1);
// in-frame indel
phantasus.MafFileReader.VARIANT_MAP.set('In_Frame_Del', 2);
phantasus.MafFileReader.VARIANT_MAP.set('In_Frame_Ins', 2);
phantasus.MafFileReader.VARIANT_MAP.set('Inframe_Del', 2);
phantasus.MafFileReader.VARIANT_MAP.set('Inframe_Ins', 2);
// other
phantasus.MafFileReader.VARIANT_MAP.set('Translation_Start_Site', 3);
phantasus.MafFileReader.VARIANT_MAP.set('Nonstop_Mutation', 3);
phantasus.MafFileReader.VARIANT_MAP.set('3\'UTR', 3);
phantasus.MafFileReader.VARIANT_MAP.set('3\'Flank', 3);
phantasus.MafFileReader.VARIANT_MAP.set('5\'UTR', 3);
phantasus.MafFileReader.VARIANT_MAP.set('5\'Flank', 3);
phantasus.MafFileReader.VARIANT_MAP.set('IGR', 3);
phantasus.MafFileReader.VARIANT_MAP.set('Intron', 3);
phantasus.MafFileReader.VARIANT_MAP.set('RNA', 3);
phantasus.MafFileReader.VARIANT_MAP.set('Targeted_Region', 3);
phantasus.MafFileReader.VARIANT_MAP.set('Unknown', 3);
phantasus.MafFileReader.VARIANT_MAP.set('1DEL', 3); // single copy loss from oncopanel
phantasus.MafFileReader.VARIANT_MAP.set('HA', 3); // high amplification from oncopanel
// mis-sense
phantasus.MafFileReader.VARIANT_MAP.set('Missense_Mutation', 4);
phantasus.MafFileReader.VARIANT_MAP.set('Missense', 4);
// splice site
phantasus.MafFileReader.VARIANT_MAP.set('Splice_Site', 5);
phantasus.MafFileReader.VARIANT_MAP.set('Splice_Acceptor', 5);
phantasus.MafFileReader.VARIANT_MAP.set('Splice_Region', 5);
// frame shift indel
phantasus.MafFileReader.VARIANT_MAP.set('Frame_Shift_Del', 6);
phantasus.MafFileReader.VARIANT_MAP.set('Frame_Shift_Ins', 6);
phantasus.MafFileReader.VARIANT_MAP.set('Frameshift', 6);
// non-sense
phantasus.MafFileReader.VARIANT_MAP.set('Nonsense_Mutation', 7);
phantasus.MafFileReader.VARIANT_MAP.set('Nonsense', 7);
phantasus.MafFileReader.VARIANT_MAP.set('2DEL', 7); // homozygous deletion from oncopanel
phantasus.MafFileReader.FIELD_NAMES = [
'Synonymous', 'In Frame Indel', 'Other Non-Synonymous',
'Missense', 'Splice Site', 'Frame Shift', 'Nonsense'];
phantasus.MafFileReader.prototype = {
setGeneFilter: function (geneFilter) {
this.geneFilter = geneFilter;
},
getFormatName: function () {
return 'maf';
},
_getGeneLevelDataset: function (datasetName, reader) {
var _this = this;
var tab = /\t/;
var header = reader.readLine().split(tab);
var headerToIndex = {};
for (var i = 0, length = header.length; i < length; i++) {
headerToIndex[header[i].toLowerCase()] = i;
}
// TODO six classes of base substitution—C>A, C>G, C>T, T>A, T>C, T>G
// (all substitutions are referred to by the pyrimidine of the mutated
// Watson–Crick base pair)
// var fields = ['Hugo_Symbol', 'Chromosome', 'Start_position',
// 'Reference_Allele', 'Tumor_Seq_Allele2',
// 'Variant_Classification', 'Protein_Change', 'Protein_Change', 'ccf_hat',
// 'tumor_f', 'i_tumor_f', 'Tumor_Sample_Barcode', 'tumor_name',
// 'Tumor_Sample_UUID', 'encoding'];
//
var sampleField = phantasus.MafFileReader.getField([
'Tumor_Sample_Barcode', 'tumor_name', 'Tumor_Sample_UUID'],
headerToIndex);
var encodingField = phantasus.MafFileReader.getField([
'encoding'],
headerToIndex); // gives a numeric value for string
if (sampleField == null) {
throw new Error('Sample id column not found.');
}
var encodingColumnIndex = encodingField == null ? -1 : encodingField.index;
var sampleColumnName = sampleField.name;
var sampleIdColumnIndex = sampleField.index;
var tumorFractionField = phantasus.MafFileReader.getField([
'ccf_hat',
'tumor_f', 'i_tumor_f'], headerToIndex);
var ccfColumnName;
var ccfColumnIndex;
if (tumorFractionField !== undefined) {
ccfColumnName = tumorFractionField.name;
ccfColumnIndex = tumorFractionField.index;
}
var chromosomeColumn = headerToIndex['Chromosome'.toLowerCase()];
var startPositionColumn = headerToIndex['Start_position'
.toLowerCase()];
var refAlleleColumn = headerToIndex['Reference_Allele'.toLowerCase()];
var tumorAllelColumn = headerToIndex['Tumor_Seq_Allele2'
.toLowerCase()];
var proteinChangeColumn = headerToIndex['Protein_Change'.toLowerCase()];
if (proteinChangeColumn == null) {
proteinChangeColumn = headerToIndex['Protein'.toLowerCase()];
}
var geneSymbolColumn = headerToIndex['Hugo_Symbol'.toLowerCase()];
if (geneSymbolColumn == null) {
geneSymbolColumn = headerToIndex['gene'];
}
if (geneSymbolColumn == null) {
throw new Error('Gene symbol column not found.');
}
var variantColumnIndex = headerToIndex['Variant_Classification'
.toLowerCase()];
if (variantColumnIndex == null) {
variantColumnIndex = headerToIndex['variant'
.toLowerCase()];
}
if (variantColumnIndex == null) {
throw new Error('Variant_Classification not found');
}
// keep fields that are in file only
var geneSymbolToIndex = new phantasus.Map();
var sampleIdToIndex = new phantasus.Map();
var variantMatrix = [];
var ccfMatrix = [];
var s;
var customNumberToValueMap = new phantasus.Map();
var hasMutationInfo = chromosomeColumn !== undefined && startPositionColumn !== undefined && refAlleleColumn !== undefined && tumorAllelColumn !== undefined;
while ((s = reader.readLine()) !== null) {
var tokens = s.split(tab);
var sample = String(tokens[sampleIdColumnIndex]);
var columnIndex = sampleIdToIndex.get(sample);
if (columnIndex === undefined) {
columnIndex = sampleIdToIndex.size();
sampleIdToIndex.set(sample, columnIndex);
}
var gene = String(tokens[geneSymbolColumn]);
if (gene === 'Unknown') {
continue;
}
if (this.geneFilter == null
|| this.geneFilter.has(tokens[geneSymbolColumn])) {
var rowIndex = geneSymbolToIndex.get(gene);
if (rowIndex === undefined) {
rowIndex = geneSymbolToIndex.size();
geneSymbolToIndex.set(gene, rowIndex);
}
var value = String(tokens[variantColumnIndex]);
var variantCode;
if (encodingColumnIndex === -1) {
variantCode = phantasus.MafFileReader.VARIANT_MAP.get(value);
if (variantCode === undefined) {
variantCode = 3;
}
} else {
variantCode = parseInt(tokens[encodingColumnIndex]);
customNumberToValueMap.set(variantCode, value);
}
var variantObject = {};
var Protein_Change = tokens[proteinChangeColumn];
if (Protein_Change) {
variantObject.Protein = String(Protein_Change);
}
variantObject.__v = variantCode;
variantObject.Variant = value;
if (hasMutationInfo) {
variantObject.Mutation = String(tokens[chromosomeColumn]) + ':'
+ String(tokens[startPositionColumn]) + ' '
+ String(tokens[refAlleleColumn]) + ' > '
+ String(tokens[tumorAllelColumn]);
}
var wrappedVariant = phantasus.Util.wrapNumber(variantCode,
variantObject);
var variantRow = variantMatrix[rowIndex];
if (variantRow === undefined) {
variantRow = [];
variantMatrix[rowIndex] = variantRow;
}
var ccf = -1;
var priorCcf = -1;
if (ccfColumnIndex !== undefined) {
var ccfRow = ccfMatrix[rowIndex];
if (ccfRow === undefined) {
ccfRow = [];
ccfMatrix[rowIndex] = ccfRow;
}
ccf = parseFloat(tokens[ccfColumnIndex]);
priorCcf = ccfRow[columnIndex] || -1;
}
var priorValue = variantRow[columnIndex] || -1;
if (variantCode > priorValue) { // take most severe mutation
variantRow[columnIndex] = wrappedVariant;
if (ccfColumnIndex !== undefined) {
ccfRow[columnIndex] = ccf;
}
} else if (variantCode === priorValue && ccf > priorCcf) {
variantRow[columnIndex] = wrappedVariant;
ccfRow[columnIndex] = ccf;
}
}
}
var dataset = new phantasus.Dataset({
name: datasetName,
array: variantMatrix,
dataType: 'Number',
rows: geneSymbolToIndex.size(),
columns: sampleIdToIndex.size()
});
var columnIds = dataset.getColumnMetadata().add('id');
sampleIdToIndex.forEach(function (index, id) {
columnIds.setValue(index, id);
});
var rowIds = dataset.getRowMetadata().add('id');
geneSymbolToIndex.forEach(function (index, id) {
rowIds.setValue(index, id);
});
for (var i = 0, nrows = dataset.getRowCount(), ncols = dataset
.getColumnCount(); i < nrows; i++) {
for (var j = 0; j < ncols; j++) {
if (variantMatrix[i][j] === undefined) {
variantMatrix[i][j] = 0;
}
}
}
if (ccfColumnIndex !== undefined) {
dataset.addSeries({
dataType: 'Float32',
name: 'allelic_fraction',
array: ccfMatrix
});
}
if (this.geneFilter) {
var orderVector = dataset.getRowMetadata().add('order');
for (var i = 0, size = orderVector.size(); i < size; i++) {
var gene = rowIds.getValue(i);
var order = this.geneFilter.get(gene);
orderVector.setValue(i, order);
}
var project = new phantasus.Project(dataset);
project.setRowSortKeys([
new phantasus.SortKey('order',
phantasus.SortKey.SortOrder.ASCENDING)], true); // sort
// collapsed
// dataset
var tmp = project.getSortedFilteredDataset();
project = new phantasus.Project(tmp);
var columnIndices = phantasus.Util.seq(tmp.getColumnCount());
columnIndices
.sort(function (a, b) {
for (var i = 0, nrows = tmp.getRowCount(); i < nrows; i++) {
for (var seriesIndex = 0, nseries = tmp
.getSeriesCount(); seriesIndex < nseries; seriesIndex++) {
var f1 = tmp.getValue(i, a, seriesIndex);
if (isNaN(f1)) {
f1 = Number.NEGATIVE_INFINITY;
}
f1 = f1.valueOf();
var f2 = tmp.getValue(i, b, seriesIndex);
if (isNaN(f2)) {
f2 = Number.NEGATIVE_INFINITY;
}
f2 = f2.valueOf();
var returnVal = (f1 === f2 ? 0 : (f1 < f2 ? 1
: -1));
if (returnVal !== 0) {
return returnVal;
}
}
}
return 0;
});
dataset = new phantasus.SlicedDatasetView(dataset, null,
columnIndices);
}
var fieldNames = phantasus.MafFileReader.FIELD_NAMES;
if (customNumberToValueMap.size() > 0) {
var pairs = [];
customNumberToValueMap.forEach(function (value, key) {
pairs.push({
key: key,
value: value
});
});
pairs.sort(function (a, b) {
return (a.key === b.key ? 0 : (a.key < b.key ? -1 : 1));
});
fieldNames = pairs.map(function (p) {
return p.value;
});
}
var numUniqueValues = fieldNames.length;
phantasus.MafFileReader.summarizeMutations({
dataset: dataset,
fields: fieldNames
});
phantasus.MafFileReader
.summarizeMutations({
dataset: new phantasus.TransposedDatasetView(dataset),
fields: fieldNames
});
var mutationSummarySelectionVector = dataset.getColumnMetadata().add('mutation_summary_selection');
mutationSummarySelectionVector.getProperties().set(
phantasus.VectorKeys.FIELDS,
fieldNames);
mutationSummarySelectionVector.getProperties().set(phantasus.VectorKeys.DATA_TYPE, '[number]');
mutationSummarySelectionVector.getProperties().set(phantasus.VectorKeys.RECOMPUTE_FUNCTION_SELECTION, true);
var datasetName = dataset.getName();
mutationSummarySelectionVector.getProperties().set(phantasus.VectorKeys.FUNCTION, {
binSize: 1,
domain: [1, 8],
cumulative: false
});
// mutationSummarySelectionVector.getProperties().set(phantasus.VectorKeys.FUNCTION, function (view, selectedDataset, columnIndex) {
// var sourceVector = selectedDataset.getRowMetadata().getByName('Source');
// var bins = new Int32Array(numUniqueValues); // 1-7
// for (var i = 0, nrows = selectedDataset.getRowCount(); i < nrows; i++) {
// var source = sourceVector.getValue(i);
// if (source == null || source === datasetName) {
// var value = selectedDataset.getValue(i, columnIndex);
// if (value > 0) {
// bins[value - 1]++;
// }
// }
// }
// return bins;
// });
return dataset;
},
read: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl));
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (err, arrayBuffer) {
if (err) {
callback(err);
} else {
try {
callback(null, _this._getGeneLevelDataset(name,
new phantasus.ArrayBufferReader(new Uint8Array(
arrayBuffer))));
}
catch (err) {
callback(err);
}
}
});
}
};
phantasus.PreloadedReader = function () {
};
phantasus.PreloadedReader.prototype = {
read: function(name, callback) {
console.log("preloaded read", name);
name = { name: name.name || name };
var afterLoaded = function (err, dataset) {
if (!err) {
var datasetTitle = "preloaded dataset";
var experimentData = dataset[0].getExperimentData();
if (experimentData) datasetTitle = experimentData.title.values.toString() || datasetTitle;
phantasus.datasetHistory.store({
name: name.name,
description: datasetTitle,
openParameters: {
file: name.name,
options: {
interactive: true,
preloaded: true
}
}
});
}
callback(err, dataset);
};
var req = ocpu.call('loadPreloaded/print', name, function(session) {
phantasus.ParseDatasetFromProtoBin.parse(session, afterLoaded, { preloaded : true, pathFunction: phantasus.PreloadedReader.prototype.getPath });
});
req.fail(function () {
callback(req.responseText);
})
},
getPath: function (fragment) {
return window.libraryPrefix.slice(0, -1) + fragment;
}
};
phantasus.SavedSessionReader = function () {
};
phantasus.SavedSessionReader.prototype = {
read: function(name, callback) {
console.log("saved session read", name);
name = typeof name === "string" ? { sessionName : name } : name;
var sessionWithLoadedMeta;
var afterLoaded = function (err, dataset) {
if (!err) {
var datasetTitle = "permanent linked dataset";
var experimentData = dataset[0].getExperimentData();
var seriesName = dataset[0].seriesNames[0];
if (experimentData) datasetTitle = experimentData.title.values.toString() || seriesName || datasetTitle;
else datasetTitle = seriesName || datasetTitle;
phantasus.datasetHistory.store({
name: name.sessionName,
description: datasetTitle,
openParameters: {
file: name.sessionName,
options: {
interactive: true,
session: true
}
}
});
dataset[0].setESSession(new Promise(function (rs) { rs(sessionWithLoadedMeta); }));
}
callback(err, dataset);
};
var req = ocpu.call('loadSession/print', name, function(session) {
sessionWithLoadedMeta = session;
sessionWithLoadedMeta.loc = sessionWithLoadedMeta.loc.split(sessionWithLoadedMeta.key).join(name.sessionName);
sessionWithLoadedMeta.key = name.sessionName;
sessionWithLoadedMeta.getLoc = function () {
return sessionWithLoadedMeta.loc;
};
sessionWithLoadedMeta.getKey = function () {
return sessionWithLoadedMeta.key;
};
phantasus.ParseDatasetFromProtoBin.parse(session, afterLoaded, {
preloaded : true
});
});
req.fail(function () {
callback(req.responseText);
})
}
};
phantasus.SegTabReader = function () {
this.regions = null;
};
phantasus.SegTabReader.binByRegion = function (dataset, regions) {
var chromosomeVector = dataset.getRowMetadata().getByName('Chromosome');
var startVector = dataset.getRowMetadata().getByName('Start_bp');
var endVector = dataset.getRowMetadata().getByName('End_bp');
var collapsedDataset = new phantasus.Dataset({
name: dataset.getName(),
rows: regions.length,
columns: dataset.getColumnCount(),
dataType: 'Float32'
});
phantasus.DatasetUtil.fill(collapsedDataset, NaN);
var regionIdVector = collapsedDataset.getRowMetadata().add('id');
var newChromosomeVector = collapsedDataset.getRowMetadata().add(
'chromosome');
var newStartVector = collapsedDataset.getRowMetadata().add('start');
var newEndVector = collapsedDataset.getRowMetadata().add('end');
var nsegmentsVector = collapsedDataset.getRowMetadata().add('nsegments');
var nseries = dataset.getSeriesCount();
for (var series = 1; series < nseries; series++) {
collapsedDataset.addSeries({
name: dataset.getName(series),
dataType: 'Float32'
});
}
var summarizeFunction = phantasus.Mean;
collapsedDataset.setColumnMetadata(dataset.getColumnMetadata());
for (var regionIndex = 0; regionIndex < regions.length; regionIndex++) {
var region = regions[regionIndex];
var rowIndices = [];
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
var chromosome = chromosomeVector.getValue(i);
var start = startVector.getValue(i);
var end = endVector.getValue(i);
if (region.chromosome == chromosome && start >= region.start
&& end <= region.end) {
rowIndices.push(i);
}
}
if (rowIndices.length > 0) {
var slice = phantasus.DatasetUtil.slicedView(dataset, rowIndices,
null);
var columnView = new phantasus.DatasetColumnView(slice);
for (var j = 0; j < dataset.getColumnCount(); j++) {
columnView.setIndex(j);
for (var series = 0; series < nseries; series++) {
columnView.setSeriesIndex(series);
collapsedDataset.setValue(regionIndex, j,
summarizeFunction(columnView), series);
}
}
}
nsegmentsVector.setValue(regionIndex, rowIndices.length);
regionIdVector.setValue(regionIndex, region.id);
newChromosomeVector.setValue(regionIndex, region.chromosome);
newStartVector.setValue(regionIndex, region.start);
newEndVector.setValue(regionIndex, region.end);
}
return collapsedDataset;
};
phantasus.SegTabReader.prototype = {
getFormatName: function () {
return 'seg';
},
setRegions: function (regions) {
this.regions = regions;
},
_read: function (datasetName, reader) {
var tab = /\t/;
var header = reader.readLine().split(tab);
var fieldNameToIndex = {};
for (var i = 0, length = header.length; i < length; i++) {
var name = header[i].toLowerCase();
fieldNameToIndex[name] = i;
}
var sampleField = phantasus.MafFileReader.getField(['pair_id',
'Tumor_Sample_Barcode', 'tumor_name', 'Tumor_Sample_UUID',
'Sample'], fieldNameToIndex);
var sampleColumnName = sampleField.name;
var sampleIdColumnIndex = sampleField.index;
var tumorFractionField = phantasus.MafFileReader.getField(['ccf_hat',
'tumor_f', 'i_tumor_f'], fieldNameToIndex);
var ccfColumnName;
var ccfColumnIndex;
if (tumorFractionField !== undefined) {
ccfColumnName = tumorFractionField.name;
ccfColumnIndex = tumorFractionField.index;
}
var chromosomeColumn = fieldNameToIndex.Chromosome;
var startPositionColumn = phantasus.MafFileReader.getField(['Start_bp',
'Start'], fieldNameToIndex).index;
var endPositionColumn = phantasus.MafFileReader.getField(['End_bp',
'End'], fieldNameToIndex, {
remove: false,
lc: true
}).index;
var valueField = phantasus.MafFileReader.getField(['tau',
'Segment_Mean']).index;
var s;
var matrix = [];
var ccfMatrix = [];
var sampleIdToIndex = new phantasus.Map();
var chromosomeStartEndToIndex = new phantasus.Map();
while ((s = reader.readLine()) !== null) {
if (s === '') {
continue;
}
var tokens = s.split(tab);
var sample = String(tokens[sampleIdColumnIndex]);
var columnIndex = sampleIdToIndex.get(sample);
if (columnIndex === undefined) {
columnIndex = sampleIdToIndex.size();
sampleIdToIndex.set(sample, columnIndex);
}
var rowId = new phantasus.Identifier([
String(tokens[chromosomeColumn]),
String(tokens[startPositionColumn]),
String(tokens[endPositionColumn])]);
var rowIndex = chromosomeStartEndToIndex.get(rowId);
if (rowIndex === undefined) {
rowIndex = chromosomeStartEndToIndex.size();
chromosomeStartEndToIndex.set(rowId, rowIndex);
}
var value = parseFloat(String(tokens[valueField]));
value = isNaN(value) ? value : (phantasus.Log2(value) - 1);
var matrixRow = matrix[rowIndex];
if (matrixRow === undefined) {
matrixRow = [];
matrix[rowIndex] = matrixRow;
if (ccfColumnIndex !== undefined) {
ccfMatrix[rowIndex] = [];
}
}
matrixRow[columnIndex] = value;
if (ccfColumnIndex !== undefined) {
ccfMatrix[rowIndex][columnIndex] = parseFloat(tokens[ccfColumnIndex]);
}
}
var dataset = new phantasus.Dataset({
name: datasetName,
array: matrix,
dataType: 'number',
rows: chromosomeStartEndToIndex.size(),
columns: sampleIdToIndex.size()
});
var columnIds = dataset.getColumnMetadata().add('id');
sampleIdToIndex.forEach(function (index, id) {
columnIds.setValue(index, id);
});
var chromosomeVector = dataset.getRowMetadata().add('Chromosome');
var startVector = dataset.getRowMetadata().add('Start_bp');
var endVector = dataset.getRowMetadata().add('End_bp');
chromosomeStartEndToIndex.forEach(function (index, id) {
chromosomeVector.setValue(index, id.getArray()[0]);
startVector.setValue(index, id.getArray()[1]);
endVector.setValue(index, id.getArray()[2]);
});
if (ccfColumnIndex !== undefined) {
dataset.addSeries({
dataType: 'number',
name: 'ccf',
array: ccfMatrix
});
}
if (this.regions != null && this.regions.length > 0) {
dataset = phantasus.SegTabReader.binByRegion(dataset, this.regions);
}
return dataset;
},
read: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl));
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (err,
arrayBuffer) {
if (err) {
callback(err);
} else {
// try {
callback(null, _this._read(name, new phantasus.ArrayBufferReader(
new Uint8Array(arrayBuffer))));
// } catch (err) {
// callback(err);
// }
}
});
}
};
phantasus.TcgaUtil = function () {
};
phantasus.TcgaUtil.DISEASE_STUDIES = {
'ACC': 'Adrenocortical carcinoma',
'BLCA': 'Bladder Urothelial Carcinoma',
'BRCA': 'Breast invasive carcinoma',
'CESC': 'Cervical squamous cell carcinoma and endocervical adenocarcinoma',
'CHOL': 'Cholangiocarcinoma',
// 'CNTL': 'Controls',
'COAD': 'Colon adenocarcinoma',
'COADREAD': 'Colonrectal adenocarcinoma',
'DLBC': 'Lymphoid Neoplasm Diffuse Large B-cell Lymphoma',
'ESCA': 'Esophageal carcinoma ',
// 'FPPP': 'FFPE Pilot Phase II',
'GBM': 'Glioblastoma multiforme',
'GBMLGG': 'Glioma',
'HNSC': 'Head and Neck squamous cell carcinoma',
'KICH': 'Kidney Chromophobe',
'KIPAN': 'Pan-Kidney Cohort',
'KIRC': 'Kidney renal clear cell carcinoma',
'KIRP': 'Kidney renal papillary cell carcinoma',
'LAML': 'Acute Myeloid Leukemia',
'LCML': 'Chronic Myelogenous Leukemia',
'LGG': 'Brain Lower Grade Glioma',
'LIHC': 'Liver hepatocellular carcinoma',
'LUAD': 'Lung adenocarcinoma',
'LUSC': 'Lung squamous cell carcinoma',
'MESO': 'Mesothelioma',
// 'MISC': 'Miscellaneous',
'OV': 'Ovarian serous cystadenocarcinoma',
'PAAD': 'Pancreatic adenocarcinoma',
'PCPG': 'Pheochromocytoma and Paraganglioma',
'PRAD': 'Prostate adenocarcinoma',
'READ': 'Rectum adenocarcinoma',
'SARC': 'Sarcoma',
'SKCM': 'Skin Cutaneous Melanoma',
'STAD': 'Stomach adenocarcinoma',
'STES': 'Stomach and Esophageal Carcinoma',
'TGCT': 'Testicular Germ Cell Tumors',
'THCA': 'Thyroid carcinoma',
'THYM': 'Thymoma',
'UCEC': 'Uterine Corpus Endometrial Carcinoma',
'UCS': 'Uterine Carcinosarcoma',
'UVM': 'Uveal Melanoma'
};
phantasus.TcgaUtil.SAMPLE_TYPES = {
'01': 'Primary solid Tumor',
'02': 'Recurrent Solid Tumor',
'03': 'Primary Blood Derived Cancer - Peripheral Blood',
'04': 'Recurrent Blood Derived Cancer - Bone Marrow',
'05': 'Additional - New Primary',
'06': 'Metastatic',
'07': 'Additional Metastatic',
'08': 'Human Tumor Original Cells',
'09': 'Primary Blood Derived Cancer - Bone Marrow',
'10': 'Blood Derived Normal',
'11': 'Solid Tissue Normal',
'12': 'Buccal Cell Normal',
'13': 'EBV Immortalized Normal',
'14': 'Bone Marrow Normal',
'20': 'Control Analyte',
'40': 'Recurrent Blood Derived Cancer - Peripheral Blood',
'50': 'Cell Lines',
'60': 'Primary Xenograft Tissue',
'61': 'Cell Line Derived Xenograft Tissue'
};
phantasus.TcgaUtil.barcode = function (s) {
// e.g. TCGA-AC-A23H-01A-11D-A159-09
// see https://wiki.nci.nih.gov/display/TCGA/TCGA+barcode
// TCGA, Tissue source site, Study participant, Sample type
var tokens = s.split('-');
var id = tokens[2];
var sampleType;
if (tokens.length > 3) {
sampleType = tokens[3];
if (sampleType.length > 2) {
sampleType = sampleType.substring(0, 2);
}
sampleType = phantasus.TcgaUtil.SAMPLE_TYPES[sampleType];
} else {
sampleType = phantasus.TcgaUtil.SAMPLE_TYPES['01'];
}
return {
id: id.toLowerCase(),
sampleType: sampleType
};
};
phantasus.TcgaUtil.setIdAndSampleType = function (dataset) {
var idVector = dataset.getColumnMetadata().get(0);
var participantId = dataset.getColumnMetadata().add('participant_id');
var sampleType = dataset.getColumnMetadata().add('sample_type');
for (var i = 0, size = idVector.size(); i < size; i++) {
var barcode = phantasus.TcgaUtil.barcode(idVector.getValue(i));
idVector.setValue(i, barcode.id + '-' + barcode.sampleType);
sampleType.setValue(i, barcode.sampleType);
participantId.setValue(i, barcode.id);
}
};
phantasus.TcgaUtil.getDataset = function (options) {
var promises = [];
var datasets = [];
var returnDeferred = $.Deferred();
if (options.mrna) {
// id + type
var mrna = $.Deferred();
promises.push(mrna);
new phantasus.TxtReader().read(options.mrna, function (err, dataset) {
if (err) {
console.log('Error reading file:' + err);
} else {
datasets.push(dataset);
phantasus.TcgaUtil.setIdAndSampleType(dataset);
}
console.log("mrna promise", datasets);
mrna.resolve();
});
}
var sigGenesLines;
if (options.mutation) {
var mutation = $.Deferred();
promises.push(mutation);
new phantasus.MafFileReader().read(options.mutation, function (err, dataset) {
if (err) {
// console.log('Error reading file:' + err);
} else {
datasets.push(dataset);
phantasus.TcgaUtil.setIdAndSampleType(dataset);
}
mutation.resolve();
});
var sigGenesAnnotation = phantasus.Util.readLines(options.sigGenes);
sigGenesAnnotation.done(function (lines) {
sigGenesLines = lines;
});
promises.push(sigGenesAnnotation);
}
if (options.gistic) {
var gistic = $.Deferred();
promises.push(gistic);
new phantasus.GisticReader().read(options.gistic,
function (err, dataset) {
if (err) {
// console.log('Error reading file:' + err);
} else {
datasets.push(dataset);
phantasus.TcgaUtil.setIdAndSampleType(dataset);
}
gistic.resolve();
});
}
if (options.gisticGene) {
var gisticGene = $.Deferred();
promises.push(gisticGene);
new phantasus.TxtReader({
dataColumnStart: 3
}).read(options.gisticGene, function (err, dataset) {
if (err) {
// console.log('Error reading file:' + err);
} else {
datasets.push(dataset);
phantasus.TcgaUtil.setIdAndSampleType(dataset);
}
gisticGene.resolve();
});
}
if (options.seg) {
var seg = $.Deferred();
promises.push(seg);
new phantasus.SegTabReader().read(options.seg, function (err, dataset) {
if (err) {
// console.log('Error reading file:' + err);
} else {
datasets.push(dataset);
phantasus.TcgaUtil.setIdAndSampleType(dataset);
}
seg.resolve();
});
}
if (options.rppa) {
// id + type
var rppa = $.Deferred();
promises.push(rppa);
new phantasus.TxtReader({dataColumnStart: 2}).read(options.rppa, function (err, dataset) {
if (err) {
// console.log('Error reading file:' + err);
} else {
datasets.push(dataset);
phantasus.TcgaUtil.setIdAndSampleType(dataset);
}
rppa.resolve();
});
}
if (options.methylation) {
// id + type
var methylation = $.Deferred();
promises.push(methylation);
new phantasus.TxtReader({}).read(options.methylation, function (
err,
dataset) {
if (err) {
// console.log('Error reading file:' + err);
} else {
datasets.push(dataset);
phantasus.TcgaUtil.setIdAndSampleType(dataset);
}
methylation.resolve();
});
}
var mrnaClustPromise = phantasus.Util.readLines(options.mrnaClust);
promises.push(mrnaClustPromise);
var sampleIdToClusterId;
mrnaClustPromise.done(function (lines) {
// SampleName cluster silhouetteValue
// SampleName cluster silhouetteValue
// TCGA-OR-A5J1-01 1 0.00648776228925048
sampleIdToClusterId = new phantasus.Map();
var lineNumber = 0;
while (lines[lineNumber].indexOf('SampleName') !== -1) {
lineNumber++;
}
var tab = /\t/;
for (; lineNumber < lines.length; lineNumber++) {
var tokens = lines[lineNumber].split(tab);
var barcode = phantasus.TcgaUtil.barcode(tokens[0]);
sampleIdToClusterId.set(barcode.id + '-' + barcode.sampleType, tokens[1]);
}
});
var annotationCallbacks = [];
var annotationDef = null;
if (options.columnAnnotations) {
// match datasetField: 'participant_id' to fileField: 'patient_id', // e.g. tcga-5l-aat0
annotationDef = phantasus.DatasetUtil.annotate({
annotations: options.columnAnnotations,
isColumns: true
});
promises.push(annotationDef);
annotationDef.done(function (array) {
annotationCallbacks = array;
});
}
$.when.apply($, promises).then(
function () {
var datasetToReturn = null;
if (datasets.length === 1) {
var sourceName = datasets[0].getName();
var sourceVector = datasets[0].getRowMetadata().add(
'Source');
for (var i = 0; i < sourceVector.size(); i++) {
sourceVector.setValue(i, sourceName);
}
datasetToReturn = datasets[0];
} else {
var maxIndex = 0;
var maxColumns = datasets[0].getColumnCount();
// use dataset with most columns as the reference or
// mutation data
for (var i = 1; i < datasets.length; i++) {
if (datasets[i].getColumnCount() > maxColumns) {
maxColumns = datasets[i].getColumnCount();
maxIndex = i;
}
if (datasets[i].getName() === 'mutations_merged.maf') {
maxColumns = Number.MAX_VALUE;
maxIndex = i;
}
}
var datasetIndices = [];
datasetIndices.push(maxIndex);
for (var i = 0; i < datasets.length; i++) {
if (i !== maxIndex) {
datasetIndices.push(i);
}
}
var joined = new phantasus.JoinedDataset(
datasets[datasetIndices[0]],
datasets[datasetIndices[1]], 'id', 'id');
for (var i = 2; i < datasetIndices.length; i++) {
joined = new phantasus.JoinedDataset(joined,
datasets[datasetIndices[i]], 'id', 'id');
}
datasetToReturn = joined;
}
var clusterIdVector = datasetToReturn.getColumnMetadata().add(
'mRNAseq_cluster');
var idVector = datasetToReturn.getColumnMetadata().getByName(
'id');
for (var j = 0, size = idVector.size(); j < size; j++) {
clusterIdVector.setValue(j, sampleIdToClusterId
.get(idVector.getValue(j)));
}
// view in space of mutation sample ids only
if (options.mutation) {
var sourceToIndices = phantasus.VectorUtil
.createValueToIndicesMap(datasetToReturn
.getRowMetadata().getByName('Source'));
var mutationDataset = new phantasus.SlicedDatasetView(
datasetToReturn, sourceToIndices
.get('mutations_merged.maf'));
new phantasus.AnnotateDatasetTool()
.annotate(sigGenesLines, mutationDataset, false,
null, 'id', 'gene', ['q']);
var qVector = mutationDataset.getRowMetadata().getByName(
'q');
var qValueVector = mutationDataset.getRowMetadata()
.getByName('q_value');
if (qValueVector == null) {
qValueVector = mutationDataset.getRowMetadata().add(
'q_value');
}
for (var i = 0, size = qValueVector.size(); i < size; i++) {
qValueVector.setValue(i, qVector.getValue(i));
}
mutationDataset.getRowMetadata().remove(
phantasus.MetadataUtil.indexOf(mutationDataset
.getRowMetadata(), 'q'));
}
if (annotationDef) {
annotationCallbacks.forEach(function (f) {
f(datasetToReturn);
});
}
// console.log("phantasus.TcgaUtil.setIdAndSampleType ::", datasetToReturn);
// phantasus.DatasetUtil.toESSessionPromise(datasetToReturn);
returnDeferred.resolve(datasetToReturn);
});
return returnDeferred;
};
/**
*
* @param options.dataRowStart
* @param options.dataColumnStart
* @constructor
*/
phantasus.TxtReader = function (options) {
if (options == null) {
options = {};
}
this.options = options;
};
phantasus.TxtReader.prototype = {
read: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl));
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (err,
arrayBuffer) {
if (err) {
callback(err);
} else {
try {
callback(null, _this._read(name,
new phantasus.ArrayBufferReader(new Uint8Array(
arrayBuffer))));
}
catch (x) {
callback(x);
}
}
});
},
_read: function (datasetName, reader) {
var dataColumnStart = this.options.dataColumnStart;
var dataRowStart = this.options.dataRowStart;
if (dataRowStart == null) {
dataRowStart = 1;
}
var tab = /\t/;
var header = reader.readLine().trim().split(tab);
if (dataRowStart > 1) {
for (var i = 1; i < dataRowStart; i++) {
reader.readLine(); // skip
}
}
var testLine = null;
if (dataColumnStart == null) { // try to figure out where data starts by finding 1st
// numeric column
testLine = reader.readLine().trim();
var tokens = testLine.split(tab);
for (var i = 1; i < tokens.length; i++) {
var token = tokens[i];
if (token === '' || token === 'NA' || token === 'NaN' || $.isNumeric(token)) {
dataColumnStart = i;
break;
}
}
if (dataColumnStart == null) {
dataColumnStart = 1;
}
}
var ncols = header.length - dataColumnStart;
var matrix = [];
var s;
var arrayOfRowArrays = [];
for (var i = 0; i < dataColumnStart; i++) {
arrayOfRowArrays.push([]);
}
if (testLine != null) {
var array = new Float32Array(ncols);
matrix.push(array);
var tokens = testLine.split(tab);
for (var j = 0; j < dataColumnStart; j++) {
// row metadata
arrayOfRowArrays[j].push(phantasus.Util.copyString(tokens[j]));
}
for (var j = dataColumnStart, k = 0; k < ncols; j++, k++) {
var token = tokens[j];
array[j - dataColumnStart] = parseFloat(token);
}
}
while ((s = reader.readLine()) !== null) {
s = s.trim();
if (s !== '') {
var array = new Float32Array(ncols);
matrix.push(array);
var tokens = s.split(tab);
for (var j = 0; j < dataColumnStart; j++) {
// row metadata
arrayOfRowArrays[j].push(phantasus.Util.copyString(tokens[j]));
}
for (var j = dataColumnStart, k = 0; k < ncols; j++, k++) {
var token = tokens[j];
array[j - dataColumnStart] = parseFloat(token);
}
}
}
var dataset = new phantasus.Dataset({
name: datasetName,
rows: matrix.length,
columns: ncols,
array: matrix,
dataType: 'Float32'
});
var columnIds = dataset.getColumnMetadata().add('id');
for (var i = 0, j = dataColumnStart; i < ncols; i++, j++) {
columnIds.setValue(i, phantasus.Util.copyString(header[j]));
}
var rowIdVector = dataset.getRowMetadata().add('id');
rowIdVector.array = arrayOfRowArrays[0];
// add additional row metadata
for (var i = 1; i < dataColumnStart; i++) {
var v = dataset.getRowMetadata().add(header[i]);
v.array = arrayOfRowArrays[i];
}
return dataset;
}
};
phantasus.XlsxDatasetReader = function () {
};
phantasus.XlsxDatasetReader.prototype = {
read: function (fileOrUrl, callback) {
var _this = this;
var name = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(fileOrUrl));
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (err,
arrayBuffer) {
if (err) {
callback(err);
} else {
try {
var data = new Uint8Array(arrayBuffer);
var arr = [];
for (var i = 0; i != data.length; ++i) {
arr[i] = String.fromCharCode(data[i]);
}
var bstr = arr.join('');
_this._read(name, bstr, callback);
}
catch (x) {
callback(x);
}
}
});
},
_read: function (datasetName, bstr, callback) {
phantasus.Util.xlsxTo2dArray({data: bstr}, function (err, lines) {
var nrows = lines.length - 1;
var header = lines[0];
var ncols = header.length - 1;
var dataset = new phantasus.Dataset({
name: datasetName,
rows: nrows,
columns: ncols
});
var columnIds = dataset.getColumnMetadata().add('id');
for (var j = 1; j <= ncols; j++) {
columnIds.setValue(j - 1, header[j]);
}
var rowIds = dataset.getRowMetadata().add('id');
for (var i = 1; i < lines.length; i++) {
var tokens = lines[i];
rowIds.setValue(i - 1, tokens[0]);
for (var j = 1; j <= ncols; j++) {
var token = tokens[j];
var value = parseFloat(token);
dataset.setValue(i - 1, j - 1, value);
}
}
callback(null, dataset);
});
}
};
phantasus.VectorAdapter = function (v) {
if (v == null) {
throw 'vector is null';
}
this.v = v;
};
phantasus.VectorAdapter.prototype = {
setValue: function (i, value) {
this.v.setValue(i, value);
},
getValue: function (i) {
return this.v.getValue(i);
},
getProperties: function () {
return this.v.getProperties();
},
size: function () {
return this.v.size();
},
getName: function () {
return this.v.getName();
},
setName: function (name) {
this.v.setName(name);
},
isFactorized: function () {
return this.v.isFactorized();
},
getFactorLevels: function () {
return this.v.getFactorLevels();
},
factorize: function (levels) {
return this.v.factorize(levels);
},
defactorize: function () {
return this.v.defactorize();
}
};
/**
*
* Creates a new dataset with the specified dimensions. Subclasses must implement getValue and
* setValue.
* @param rows {number} The number of rows
* @param columns {number} The number of columns
* @implements {phantasus.DatasetInterface}
* @constructor
*/
phantasus.AbstractDataset = function (rows, columns) {
this.seriesNames = [];
this.seriesArrays = [];
this.seriesDataTypes = [];
this.rows = rows;
this.columns = columns;
this.rowMetadataModel = new phantasus.MetadataModel(rows);
this.columnMetadataModel = new phantasus.MetadataModel(columns);
};
phantasus.AbstractDataset.prototype = {
/**
* @ignore
* @param metadata
*/
setRowMetadata: function (metadata) {
this.rowMetadataModel = metadata;
},
/**
* @ignore
* @param metadata
*/
setColumnMetadata: function (metadata) {
this.columnMetadataModel = metadata;
},
/**
* Returns the name for the given series. Series can be used to store
* standard error of data points for example.
*
* @param seriesIndex
* the series
* @return the series name
*/
getName: function (seriesIndex) {
return this.seriesNames[seriesIndex || 0];
},
/**
* Sets the name for the given series. Series can be used to store standard
* error of data points for example.
*
* @param seriesIndex
* the series *
* @param name
* the series name
*/
setName: function (seriesIndex, name) {
this.seriesNames[seriesIndex || 0] = name;
},
/**
* Gets the row metadata for this dataset.
*
* @return the row metadata
*/
getRowMetadata: function () {
return this.rowMetadataModel;
},
/**
* Gets the column metadata for this dataset.
*
* @return The column metadata
*/
getColumnMetadata: function () {
return this.columnMetadataModel;
},
/**
* Returns the number of rows in the dataset.
*
* @return the number of rows
*/
getRowCount: function () {
return this.rows;
},
/**
* Returns the number of columns in the dataset.
*
* @return the number of columns
*/
getColumnCount: function () {
return this.columns;
},
/**
* Returns the value at the given row and column for the given series.
* Series can be used to store standard error of data points for example.
*
* @param rowIndex
* the row index
* @param columnIndex
* the column index
* @param seriesIndex
* the series index
* @return the value
*/
getValue: function (rowIndex, columnIndex, seriesIndex) {
// not implemented
},
/**
* Sets the value at the given row and column for the given series.
*
* @param rowIndex
* the row index
*
* @param columnIndex
* the column index
* @param value
* the value
* @param seriesIndex
* the series index
*
*/
setValue: function (rowIndex, columnIndex, value, seriesIndex) {
// not implemented
},
/**
* Adds the specified series.
*
* @param options
* @param options.name
* the series name
* @param options.dataType
* the series data type (e.g. object, Float32, Int8)
* @return the series index
*/
addSeries: function (options) {
// not implemented
},
/**
* Removes the specified series.
*
* @param seriesIndex The series index.
*/
removeSeries: function (seriesIndex) {
this.seriesArrays.splice(seriesIndex, 1);
this.seriesNames.splice(seriesIndex, 1);
this.seriesDataTypes.splice(seriesIndex, 1);
},
/**
* Returns the number of matrix series. Series can be used to store standard
* error of data points for example.
*
* @return the number of series
*/
getSeriesCount: function () {
return this.seriesArrays.length;
},
/**
* Returns the data type at the specified series index.
*
* @param seriesIndex
* the series index
* @return the series data type (e.g. Number, Float32, Int8)
*/
getDataType: function (seriesIndex) {
return this.seriesDataTypes[seriesIndex || 0];
},
toString: function () {
return this.getName();
}
};
/**
*
* Creates a new vector with the given name and size. Subclasses must implement getValue
*
* @param {string} name
* the vector name
* @param size {number}
* the number of elements in this vector
* @implements {phantasus.VectorInterface}
* @constructor
*/
phantasus.AbstractVector = function (name, size) {
this.name = name;
this.n = size;
this.properties = new phantasus.Map();
};
phantasus.AbstractVector.prototype = {
getValue: function (index) {
throw new Error('Not implemented');
},
getProperties: function () {
return this.properties;
},
size: function () {
return this.n;
},
getName: function () {
return this.name;
}
};
phantasus.SignalToNoise = function (list1, list2) {
var m1 = phantasus.Mean(list1);
var m2 = phantasus.Mean(list2);
var s1 = Math.sqrt(phantasus.Variance(list1, m1));
var s2 = Math.sqrt(phantasus.Variance(list2, m2));
return (m1 - m2) / (s1 + s2);
};
phantasus.SignalToNoise.toString = function () {
return 'Signal to noise';
};
phantasus.createSignalToNoiseAdjust = function (percent) {
percent = percent || 0.2;
var f = function (list1, list2) {
var m1 = phantasus.Mean(list1);
var m2 = phantasus.Mean(list2);
var s1 = Math.sqrt(phantasus.Variance(list1, m1));
var s2 = Math.sqrt(phantasus.Variance(list2, m2));
s1 = phantasus.SignalToNoise.thresholdStandardDeviation(m1, s1, percent);
s2 = phantasus.SignalToNoise.thresholdStandardDeviation(m2, s2, percent);
// ensure variance is at least 20% of mean
return (m1 - m2) / (s1 + s2);
};
f.toString = function () {
return 'Signal to noise (adjust standard deviation)';
};
return f;
};
phantasus.SignalToNoise.thresholdStandardDeviation = function (mean,
standardDeviation, percent) {
var returnValue = standardDeviation;
var absMean = Math.abs(mean);
var minStdev = percent * absMean;
if (minStdev > standardDeviation) {
returnValue = minStdev;
}
if (returnValue < percent) {
returnValue = percent;
}
return returnValue;
};
phantasus.createContingencyTable = function (listOne, listTwo, groupingValue) {
if (groupingValue == null || isNaN(groupingValue)) {
groupingValue = 1;
}
var aHit = 0;
var aMiss = 0;
for (var j = 0, size = listOne.size(); j < size; j++) {
var val = listOne.getValue(j);
if (!isNaN(val)) {
if (val >= groupingValue) {
aHit++;
} else {
aMiss++;
}
}
}
var bHit = 0;
var bMiss = 0;
for (var j = 0, size = listTwo.size(); j < size; j++) {
var val = listTwo.getValue(j);
if (!isNaN(val)) {
if (val >= groupingValue) {
bHit++;
} else {
bMiss++;
}
}
}
// listOne=drawn, listTwo=not drawn
// green=1, red=0
var N = aHit + aMiss + bHit + bMiss;
var K = aHit + bHit;
var n = aHit + aMiss;
var k = aHit;
var a = k;
var b = K - k;
var c = n - k;
var d = N + k - n - K;
return [a, b, c, d];
};
phantasus.FisherExact = function (listOne, listTwo) {
var abcd = phantasus.createContingencyTable(listOne, listTwo, 1);
return phantasus.FisherExact.fisherTest(abcd[0], abcd[1], abcd[2], abcd[3]);
};
phantasus.createFisherExact = function (groupingValue) {
var f = function (listOne, listTwo) {
var abcd = phantasus.createContingencyTable(listOne, listTwo,
groupingValue);
return phantasus.FisherExact.fisherTest(abcd[0], abcd[1], abcd[2],
abcd[3]);
};
return f;
};
/**
* Computes the hypergeometric probability.
*/
phantasus.FisherExact.phyper = function (a, b, c, d) {
return Math
.exp((phantasus.FisherExact.logFactorial(a + b)
+ phantasus.FisherExact.logFactorial(c + d)
+ phantasus.FisherExact.logFactorial(a + c) + phantasus.FisherExact
.logFactorial(b + d))
- (phantasus.FisherExact.logFactorial(a)
+ phantasus.FisherExact.logFactorial(b)
+ phantasus.FisherExact.logFactorial(c)
+ phantasus.FisherExact.logFactorial(d) + phantasus.FisherExact
.logFactorial(a + b + c + d)));
};
phantasus.FisherExact.logFactorials = [0.00000000000000000,
0.00000000000000000, 0.69314718055994531, 1.79175946922805500,
3.17805383034794562, 4.78749174278204599, 6.57925121201010100,
8.52516136106541430, 10.60460290274525023, 12.80182748008146961,
15.10441257307551530, 17.50230784587388584, 19.98721449566188615,
22.55216385312342289, 25.19122118273868150, 27.89927138384089157,
30.67186010608067280, 33.50507345013688888, 36.39544520803305358,
39.33988418719949404, 42.33561646075348503, 45.38013889847690803,
48.47118135183522388, 51.60667556776437357, 54.78472939811231919,
58.00360522298051994, 61.26170176100200198, 64.55753862700633106,
67.88974313718153498, 71.25703896716800901];
phantasus.FisherExact.logFactorial = function (k) {
if (k >= 30) { // stirlings approximation
var C0 = 9.18938533204672742e-01;
var C1 = 8.33333333333333333e-02;
var C3 = -2.77777777777777778e-03;
var C5 = 7.93650793650793651e-04;
var C7 = -5.95238095238095238e-04;
var r = 1.0 / k;
var rr = r * r;
return (k + 0.5) * Math.log(k) - k + C0 + r
* (C1 + rr * (C3 + rr * (C5 + rr * C7)));
// log k! = (k + 1/2)log(k) - k + (1/2)log(2Pi) + stirlingCorrection(k)
}
return phantasus.FisherExact.logFactorials[k];
};
phantasus.FisherExact.fisherTest = function (a, b, c, d) {
// match R 2-sided fisher.test
var p = phantasus.FisherExact.phyper(a, b, c, d);
var sum = p;
for (var _a = 0, n = a + b + c + d; _a <= n; _a++) {
var _b = a + b - _a;
var _c = a + c - _a;
var _d = b + d - _b;
if (_a !== a && _b >= 0 && _c >= 0 && _d >= 0) {
var _p = phantasus.FisherExact.phyper(_a, _b, _c, _d);
if (_p <= p) {
sum += _p;
}
}
}
return Math.min(1, sum);
// var lt = jStat.hypgeom.cdf(a, a + b + c + d, a + b, a + c);
// var gt = jStat.hypgeom.cdf(b, a + b + c + d, a + b, b + d);
// return Math.min(1, 2 * Math.min(lt, gt));
};
phantasus.FisherExact.toString = function () {
return 'Fisher Exact Test';
};
phantasus.FoldChange = function (list1, list2) {
var m1 = phantasus.Mean(list1);
var m2 = phantasus.Mean(list2);
return (m1 / m2);
};
phantasus.FoldChange.toString = function () {
return 'Fold Change';
};
phantasus.MeanDifference = function (list1, list2) {
var m1 = phantasus.Mean(list1);
var m2 = phantasus.Mean(list2);
return m1 - m2;
};
phantasus.MeanDifference.toString = function () {
return 'Mean Difference';
};
phantasus.TTest = function (list1, list2) {
var m1 = phantasus.Mean(list1);
var m2 = phantasus.Mean(list2);
var s1 = Math.sqrt(phantasus.Variance(list1, m1));
var s2 = Math.sqrt(phantasus.Variance(list2, m2));
var n1 = phantasus.CountNonNaN(list1);
var n2 = phantasus.CountNonNaN(list2);
return ((m1 - m2) / Math.sqrt((s1 * s1 / n1) + (s2 * s2 / n2)));
};
phantasus.TTest.toString = function () {
return 'T-Test';
};
/**
* Computes approximate degrees of freedom for 2-sample t-test.
*
* @param v1 first sample variance
* @param v2 second sample variance
* @param n1 first sample n
* @param n2 second sample n
* @return approximate degrees of freedom
*/
phantasus.DegreesOfFreedom = function (v1, v2, n1, n2) {
return (((v1 / n1) + (v2 / n2)) * ((v1 / n1) + (v2 / n2))) / ((v1 * v1) / (n1 * n1 * (n1 - 1.0)) + (v2 * v2) / (n2 * n2 * (n2 - 1.0)));
};
phantasus.Spearman = function (list1, list2) {
var flist1 = [];
var flist2 = [];
for (var i = 0, n = list1.size(); i < n; i++) {
var v1 = list1.getValue(i);
var v2 = list2.getValue(i);
if (isNaN(v1) || isNaN(v2)) {
continue;
}
flist1.push(v1);
flist2.push(v2);
}
var rank1 = phantasus.Ranking(flist1);
var rank2 = phantasus.Ranking(flist2);
return phantasus.Pearson(new phantasus.Vector('', rank1.length)
.setArray(rank1), new phantasus.Vector('', rank2.length)
.setArray(rank2));
};
phantasus.Spearman.toString = function () {
return 'Spearman rank correlation';
};
phantasus.WeightedMean = function (weights, values) {
var numerator = 0;
var denom = 0;
for (var i = 0, size = values.size(); i < size; i++) {
var value = values.getValue(i);
if (!isNaN(value)) {
var weight = Math.abs(weights.getValue(i));
if (!isNaN(weight)) {
numerator += (weight * value);
denom += weight;
}
}
}
return denom === 0 ? NaN : numerator / denom;
};
phantasus.WeightedMean.toString = function () {
return 'Weighted average';
};
phantasus.createOneMinusMatrixValues = function (dataset) {
var f = function (listOne, listTwo) {
return 1 - dataset.getValue(listOne.getIndex(), listTwo.getIndex());
};
f.toString = function () {
return 'One minus matrix values (for a precomputed similarity matrix)';
};
return f;
};
phantasus.Pearson = function (listOne, listTwo) {
var sumx = 0;
var sumxx = 0;
var sumy = 0;
var sumyy = 0;
var sumxy = 0;
var N = 0;
for (var i = 0, size = listOne.size(); i < size; i++) {
var x = listOne.getValue(i);
var y = listTwo.getValue(i);
if (isNaN(x) || isNaN(y)) {
continue;
}
sumx += x;
sumxx += x * x;
sumy += y;
sumyy += y * y;
sumxy += x * y;
N++;
}
var numr = sumxy - (sumx * sumy / N);
var denr = Math.sqrt((sumxx - (sumx * sumx / N))
* (sumyy - (sumy * sumy / N)));
return denr == 0 ? 1 : numr / denr;
};
phantasus.Pearson.toString = function () {
return 'Pearson correlation';
};
phantasus.Jaccard = function (listOne, listTwo) {
var orCount = 0;
var andCount = 0;
for (var i = 0, size = listOne.size(); i < size; i++) {
var xval = listOne.getValue(i);
var yval = listTwo.getValue(i);
if (isNaN(xval) || isNaN(yval)) {
continue;
}
var x = xval > 0;
var y = yval > 0;
if (x && y) {
andCount++;
} else if (x || y) {
orCount++;
}
}
if (orCount === 0) {
return 1;
}
return 1 - (andCount / orCount);
};
phantasus.Jaccard.toString = function () {
return 'Jaccard distance';
};
phantasus.Cosine = function (listOne, listTwo) {
var sumX2 = 0;
var sumY2 = 0;
var sumXY = 0;
for (var i = 0, size = listOne.size(); i < size; i++) {
var x = listOne.getValue(i);
var y = listTwo.getValue(i);
if (isNaN(x) || isNaN(y)) {
continue;
}
sumX2 += x * x;
sumY2 += y * y;
sumXY += x * y;
}
return (sumXY / Math.sqrt(sumX2 * sumY2));
};
phantasus.Cosine.toString = function () {
return 'Cosine similarity';
};
phantasus.Euclidean = function (x, y) {
var dist = 0;
for (var i = 0, size = x.size(); i < size; ++i) {
var x_i = x.getValue(i);
var y_i = y.getValue(i);
if (isNaN(x_i) || isNaN(y_i)) {
continue;
}
dist += (x_i - y_i) * (x_i - y_i);
}
return Math.sqrt(dist);
};
phantasus.Euclidean.toString = function () {
return 'Euclidean distance';
};
phantasus.OneMinusFunction = function (f) {
var dist = function (x, y) {
return 1 - f(x, y);
};
dist.toString = function () {
var s = f.toString();
return 'One minus ' + s[0].toLowerCase() + s.substring(1);
};
return dist;
};
phantasus.LinearRegression = function (xVector, yVector) {
var sumX = 0;
var sumY = 0;
var sumXX = 0;
var sumXY = 0;
var count = 0;
for (var i = 0, size = xVector.size(); i < size; i++) {
var x = xVector.getValue(i);
var y = yVector.getValue(i);
if (!isNaN(x) && !isNaN(y)) {
sumX += x;
sumY += y;
sumXX += x * x;
sumXY += x * y;
count++;
}
}
var m = ((count * sumXY) - (sumX * sumY)) /
((count * sumXX) - (sumX * sumX));
var b = (sumY / count) - ((m * sumX) / count);
return {
m: m,
b: b
};
};
phantasus.KendallsCorrelation = function (x, y) {
/**
* Returns the sum of the number from 1 .. n according to Gauss' summation formula:
* \[ \sum\limits_{k=1}^n k = \frac{n(n + 1)}{2} \]
*
* @param n the summation end
* @return the sum of the number from 1 to n
*/
function sum(n) {
return n * (n + 1) / 2;
}
var xArray = [];
var yArray = [];
for (var i = 0, size = x.size(); i < size; ++i) {
var x_i = x.getValue(i);
var y_i = y.getValue(i);
if (isNaN(x_i) || isNaN(y_i)) {
continue;
}
xArray.push(x_i);
yArray.push(y_i);
}
var n = xArray.length;
var numPairs = sum(n - 1);
var pairs = [];
for (var i = 0; i < n; i++) {
pairs[i] = [xArray[i], yArray[i]];
}
pairs.sort(function (pair1, pair2) {
var a = pair1[0];
var b = pair2[0];
var compareFirst = (a === b ? 0 : (a < b ? -1 : 1));
if (compareFirst !== 0) {
return compareFirst;
}
a = pair1[1];
b = pair2[1];
return (a === b ? 0 : (a < b ? -1 : 1));
});
var tiedXPairs = 0;
var tiedXYPairs = 0;
var consecutiveXTies = 1;
var consecutiveXYTies = 1;
var prev = pairs[0];
for (var i = 1; i < n; i++) {
var curr = pairs[i];
if (curr[0] === prev[0]) {
consecutiveXTies++;
if (curr[1] === prev[1]) {
consecutiveXYTies++;
} else {
tiedXYPairs += sum(consecutiveXYTies - 1);
consecutiveXYTies = 1;
}
} else {
tiedXPairs += sum(consecutiveXTies - 1);
consecutiveXTies = 1;
tiedXYPairs += sum(consecutiveXYTies - 1);
consecutiveXYTies = 1;
}
prev = curr;
}
tiedXPairs += sum(consecutiveXTies - 1);
tiedXYPairs += sum(consecutiveXYTies - 1);
var swaps = 0;
var pairsDestination = [];
for (var segmentSize = 1; segmentSize < n; segmentSize <<= 1) {
for (var offset = 0; offset < n; offset += 2 * segmentSize) {
var i = offset;
var iEnd = Math.min(i + segmentSize, n);
var j = iEnd;
var jEnd = Math.min(j + segmentSize, n);
var copyLocation = offset;
while (i < iEnd || j < jEnd) {
if (i < iEnd) {
if (j < jEnd) {
var c = (pairs[i][1] === pairs[j][1] ? 0 : (pairs[i][1] < pairs[j][1] ? -1 : 1));
if (c <= 0) {
pairsDestination[copyLocation] = pairs[i];
i++;
} else {
pairsDestination[copyLocation] = pairs[j];
j++;
swaps += iEnd - i;
}
} else {
pairsDestination[copyLocation] = pairs[i];
i++;
}
} else {
pairsDestination[copyLocation] = pairs[j];
j++;
}
copyLocation++;
}
}
var pairsTemp = pairs;
pairs = pairsDestination;
pairsDestination = pairsTemp;
}
var tiedYPairs = 0;
var consecutiveYTies = 1;
prev = pairs[0];
for (var i = 1; i < n; i++) {
var curr = pairs[i];
if (curr[1] === prev[1]) {
consecutiveYTies++;
} else {
tiedYPairs += sum(consecutiveYTies - 1);
consecutiveYTies = 1;
}
prev = curr;
}
tiedYPairs += sum(consecutiveYTies - 1);
var concordantMinusDiscordant = numPairs - tiedXPairs - tiedYPairs + tiedXYPairs - 2 * swaps;
var nonTiedPairsMultiplied = (numPairs - tiedXPairs) * (numPairs - tiedYPairs);
return concordantMinusDiscordant / Math.sqrt(nonTiedPairsMultiplied);
};
phantasus.KendallsCorrelation.toString = function () {
return 'Kendall\'s correlation';
};
/**
* Creates a new computed vector with the given name and size.
*
* @param name
* the vector name
* @param size
* the number of elements in this vector
* @param callback {Function} that takes an index and returns the value at the specified index
* @constructor
*/
phantasus.ComputedVector = function (name, size, callback) {
phantasus.AbstractVector.call(this, name, size);
this.callback = callback;
};
phantasus.ComputedVector.prototype = {
getValue: function (index) {
return this.callback(index);
}
};
phantasus.Util.extend(phantasus.ComputedVector, phantasus.AbstractVector);
phantasus.DatasetAdapter = function (dataset, rowMetadata, columnMetadata) {
if (dataset == null) {
throw 'dataset is null';
}
this.dataset = dataset;
this.rowMetadata = rowMetadata || dataset.getRowMetadata();
this.columnMetadata = columnMetadata || dataset.getColumnMetadata();
this.esSession = dataset.esSession;
this.esSource = 'copied';
};
phantasus.DatasetAdapter.prototype = {
getDataset: function () {
return this.dataset;
},
getName: function (seriesIndex) {
return this.dataset.getName(seriesIndex);
},
setName: function (seriesIndex, name) {
this.dataset.setName(seriesIndex, name);
},
getRowMetadata: function () {
return this.rowMetadata;
},
getColumnMetadata: function () {
return this.columnMetadata;
},
getRowCount: function () {
return this.dataset.getRowCount();
},
getColumnCount: function () {
return this.dataset.getColumnCount();
},
getValue: function (rowIndex, columnIndex, seriesIndex) {
return this.dataset.getValue(rowIndex, columnIndex, seriesIndex);
},
setValue: function (rowIndex, columnIndex, value, seriesIndex) {
this.dataset.setValue(rowIndex, columnIndex, value, seriesIndex);
},
addSeries: function (options) {
return this.dataset.addSeries(options);
},
removeSeries: function (seriesIndex) {
this.dataset.removeSeries(seriesIndex);
},
getSeriesCount: function () {
return this.dataset.getSeriesCount();
},
getDataType: function (seriesIndex) {
return this.dataset.getDataType(seriesIndex);
},
toString: function () {
return this.dataset.toString();
},
getESSession: function () {
return this.esSession;
},
setESSession: function (esSession) {
this.esSession = esSession;
},
getExperimentData: function () {
return this.dataset.getExperimentData();
}
};
phantasus.DatasetColumnView = function (dataset) {
this.dataset = dataset;
this.columnIndex = 0;
this.seriesIndex = 0;
};
phantasus.DatasetColumnView.prototype = {
columnIndex: -1,
size: function () {
return this.dataset.getRowCount();
},
getValue: function (rowIndex) {
return this.dataset.getValue(rowIndex, this.columnIndex,
this.seriesIndex);
},
setIndex: function (newColumnIndex) {
this.columnIndex = newColumnIndex;
return this;
},
setSeriesIndex: function (seriesIndex) {
this.seriesIndex = seriesIndex;
return this;
}
};
/**
* The interface for a dataset consisting of a two-dimensional matrix of
* values. A dataset may also optionally contain one or more series of
* two-dimensional matrices. A dataset also has metadata associated with each
* row and column.
*
* @interface phantasus.DatasetInterface
*/
/**
* Returns the name for the given series. Series can be used to store
* standard error of data points for example.
*
* @function
* @name phantasus.DatasetInterface#getName
* @param seriesIndex {number} the series
* @return {string} the series name
*/
/**
* Sets the name for the given series. Series can be used to store standard
* error of data points for example.
*
* @function
* @name phantasus.DatasetInterface#setName
* @param seriesIndex {number} the series
* @param name {string} the series name
*/
/**
* Gets the row metadata for this dataset.
*
* @function
* @name phantasus.DatasetInterface#getRowMetadata
* @return {phantasus.MetadataModelInterface} the row metadata
*/
/**
* Gets the column metadata for this dataset.
*
* @function
* @name phantasus.DatasetInterface#getColumnMetadata
* @return {phantasus.MetadataModelInterface} The column metadata
*/
/**
* Returns the number of rows in the dataset.
*
* @function
* @name phantasus.DatasetInterface#getRowCount
* @return {number} the number of rows
*/
/**
* Returns the number of columns in the dataset.
*
* @function
* @name phantasus.DatasetInterface#getColumnCount
* @return {number} the number of columns
*/
/**
* Returns the value at the given row and column for the given series.
* Series can be used to store standard error of data points for example.
*
* @function
* @name phantasus.DatasetInterface#getValue
* @param rowIndex {number} the row index
* @param columnIndex {number} the column index
* @param seriesIndex {number} the series index
* @return the value
*/
/**
* Sets the value at the given row and column for the given series.
*
* @function
* @name phantasus.DatasetInterface#setValue
* @param rowIndex {number} the row index
* @param columnIndex {number} the column index
* @param value the value
* @param seriesIndex {number} the series index
*/
/**
* Adds the specified series.
*
* @function
* @name phantasus.DatasetInterface#addSeries
* @param options.name {string} the series name
* @param options.dataType {string} the series data type (e.g. object, Float32, Int8)
* @return {number} the series index
*/
/**
* Removes the specified series.
*
* @function
* @name phantasus.DatasetInterface#removeSeries
* @param seriesIndex {number} The series index.
*/
/**
* Returns the number of matrix series. Series can be used to store standard
* error of data points for example.
*
* @function
* @name phantasus.DatasetInterface#getSeriesCount
* @return {number} the number of series
*/
/**
* Returns the data type at the specified series index.
*
* @function
* @name phantasus.DatasetInterface#getDataType
* @param seriesIndex {number} the series index
* @return {string} the series data type (e.g. Number, Float32, Int8)
*/
phantasus.DatasetRowView = function (dataset) {
this.dataset = dataset;
this.index = 0;
this.seriesIndex = 0;
};
phantasus.DatasetRowView.prototype = {
size: function () {
return this.dataset.getColumnCount();
},
getIndex: function () {
return this.index;
},
getValue: function (columnIndex) {
return this.dataset.getValue(this.index, columnIndex, this.seriesIndex);
},
setIndex: function (newRowIndex) {
this.index = newRowIndex;
return this;
},
setSeriesIndex: function (seriesIndex) {
this.seriesIndex = seriesIndex;
return this;
},
setDataset: function (dataset) {
this.dataset = dataset;
return this;
}
};
phantasus.DatasetSeriesView = function (dataset, seriesIndices) {
phantasus.DatasetAdapter.call(this, dataset);
this.seriesIndices = seriesIndices;
};
phantasus.DatasetSeriesView.prototype = {
getValue: function (i, j, seriesIndex) {
seriesIndex = seriesIndex || 0;
return this.dataset.getValue(i, j, this.seriesIndices[seriesIndex]);
},
setValue: function (i, j, value, seriesIndex) {
seriesIndex = seriesIndex || 0;
this.dataset.setValue(i, j, value, this.seriesIndices[seriesIndex]);
},
getName: function (seriesIndex) {
seriesIndex = seriesIndex || 0;
return this.dataset.getName(this.seriesIndices[seriesIndex]);
},
setName: function (seriesIndex, name) {
seriesIndex = seriesIndex || 0;
this.dataset.setName(this.seriesIndices[seriesIndex], name);
},
addSeries: function (options) {
var index = this.dataset.addSeries(options);
this.seriesIndices.push(index);
return index;
},
getSeriesCount: function () {
return this.seriesIndices.length;
},
toString: function () {
return this.getName();
}
};
phantasus.Util.extend(phantasus.DatasetSeriesView, phantasus.DatasetAdapter);
/**
* Static utilities for phantasus.DatasetInterface instances
*
* @class phantasus.DatasetUtil
*/
phantasus.DatasetUtil = function () {
};
phantasus.DatasetUtil.min = function (dataset, seriesIndex) {
seriesIndex = seriesIndex || 0;
var min = Number.MAX_VALUE;
for (var i = 0, rows = dataset.getRowCount(); i < rows; i++) {
for (var j = 0, columns = dataset.getColumnCount(); j < columns; j++) {
var d = dataset.getValue(i, j, seriesIndex);
if (isNaN(d)) {
continue;
}
min = Math.min(min, d);
}
}
return min;
};
phantasus.DatasetUtil.slicedView = function (dataset, rows, columns) {
return new phantasus.SlicedDatasetView(dataset, rows, columns);
};
phantasus.DatasetUtil.transposedView = function (dataset) {
return dataset instanceof phantasus.TransposedDatasetView ? dataset
.getDataset() : new phantasus.TransposedDatasetView(dataset);
};
phantasus.DatasetUtil.max = function (dataset, seriesIndex) {
seriesIndex = seriesIndex || 0;
var max = -Number.MAX_VALUE;
for (var i = 0, rows = dataset.getRowCount(); i < rows; i++) {
for (var j = 0, columns = dataset.getColumnCount(); j < columns; j++) {
var d = dataset.getValue(i, j, seriesIndex);
if (isNaN(d)) {
continue;
}
max = Math.max(max, d);
}
}
return max;
};
phantasus.DatasetUtil.getDatasetReader = function (ext, options) {
if (options == null) {
options = {};
}
var datasetReader = null;
if (ext === 'maf') {
datasetReader = new phantasus.MafFileReader();
if (options && options.mafGeneFilter) {
datasetReader.setGeneFilter(options.mafGeneFilter);
}
} else if (ext === 'gct') {
datasetReader = new phantasus.GctReader();
// datasetReader = new phantasus.StreamingGctReader();
} else if (ext === 'gmt') {
datasetReader = new phantasus.GmtDatasetReader();
} else if (ext === 'xlsx' || ext === 'xls') {
datasetReader = options.interactive ? new phantasus.Array2dReaderInteractive() : new phantasus.XlsxDatasetReader();
} else if (ext === 'segtab' || ext === 'seg') {
datasetReader = new phantasus.SegTabReader();
if (options && options.regions) {
datasetReader.setRegions(options.regions);
}
} else if (ext === 'txt' || ext === 'tsv' || ext === 'csv') {
datasetReader = options.interactive ? new phantasus.Array2dReaderInteractive() : new phantasus.TxtReader();
} else if (ext === 'json') {
datasetReader = new phantasus.JsonDatasetReader();
} else if (ext === 'gct') {
datasetReader = new phantasus.GctReader();
}
return datasetReader;
};
phantasus.DatasetUtil.readDatasetArray = function (datasets) {
var retDef = $.Deferred();
var loadedDatasets = [];
var promises = [];
_.each(datasets, function (url, i) {
var p = phantasus.DatasetUtil.read(url);
p.index = i;
p.done(function (dataset) {
loadedDatasets[this.index] = dataset;
});
p.fail(function (err) {
var message = [
'Error opening ' + phantasus.Util
.getFileName(url) + '.'];
if (err.message) {
message.push('<br />Cause: ');
message.push(err.message);
}
retDef.reject(message.join(''));
});
promises.push(p);
});
if (promises.length === 0) {
retDef.reject('No datasets specified.');
}
$.when
.apply($, promises)
.then(
function () {
retDef.resolve(phantasus.DatasetUtil.join(loadedDatasets, 'id'));
});
return retDef;
};
/**
* Annotate a dataset from external file or text.
*
* @param options.annotations -
* Array of file, datasetField, and fileField, and transposed.
* @param options.isColumns
* Whether to annotate columns
*
* @return A jQuery Deferred object that resolves to an array of functions to
* execute with a dataset parameter.
*/
phantasus.DatasetUtil.annotate = function (options) {
var retDef = $.Deferred();
var promises = [];
var functions = [];
var isColumns = options.isColumns;
_.each(options.annotations, function (ann, annotationIndex) {
if (phantasus.Util.isArray(ann.file)) { // already parsed text
functions[annotationIndex] = function (dataset) {
new phantasus.AnnotateDatasetTool().annotate(ann.file, dataset,
isColumns, null, ann.datasetField, ann.fileField,
ann.include);
};
} else {
var result = phantasus.Util.readLines(ann.file);
var fileName = phantasus.Util.getFileName(ann.file);
var deferred = $.Deferred();
promises.push(deferred);
result.fail(function (message) {
deferred.reject(message);
});
result.done(function (lines) {
if (phantasus.Util.endsWith(fileName, '.gmt')) {
var sets = new phantasus.GmtReader().parseLines(lines);
functions[annotationIndex] = function (dataset) {
new phantasus.AnnotateDatasetTool().annotate(null, dataset,
isColumns, sets, ann.datasetField,
ann.fileField);
};
deferred.resolve();
} else if (phantasus.Util.endsWith(fileName, '.cls')) {
functions[annotationIndex] = function (dataset) {
new phantasus.AnnotateDatasetTool().annotateCls(null, dataset,
fileName, isColumns, lines);
};
deferred.resolve();
} else {
functions[annotationIndex] = function (dataset) {
new phantasus.AnnotateDatasetTool().annotate(lines, dataset,
isColumns, null, ann.datasetField,
ann.fileField, ann.include, ann.transposed);
};
deferred.resolve();
}
});
}
});
$.when.apply($, promises).then(function () {
retDef.resolve(functions);
});
return retDef;
};
/**
* Reads a dataset at the specified URL or file
* @param fileOrUrl
* a File or URL
* @param options.background
* @params options.interactive
* @params options.extension
* @return A promise that resolves to phantasus.DatasetInterface
*/
phantasus.DatasetUtil.read = function (fileOrUrl, options) {
if (fileOrUrl == null) {
throw 'File is null';
}
if (options == null) {
options = {};
}
var isFile = phantasus.Util.isFile(fileOrUrl);
var isString = phantasus.Util.isString(fileOrUrl);
var ext = options.extension ? options.extension : phantasus.Util.getExtension(phantasus.Util.getFileName(fileOrUrl));
var datasetReader;
var str = fileOrUrl.toString();
if (options.isGEO) {
datasetReader = new phantasus.GeoReader();
}
else if (options.preloaded) {
datasetReader = new phantasus.PreloadedReader();
fileOrUrl = {
name: fileOrUrl,
exactName: options.exactName
}
} else if (options.session) {
datasetReader = new phantasus.SavedSessionReader();
}
else if (ext === '' && str != null && str.indexOf('blob:') === 0) {
datasetReader = new phantasus.TxtReader(); // copy from clipboard
} else {
datasetReader = phantasus.DatasetUtil.getDatasetReader(ext, options);
if (datasetReader == null) {
datasetReader = isFile ? (options.interactive ? new phantasus.Array2dReaderInteractive() : new phantasus.TxtReader()) : new phantasus.GctReader();
}
}
// console.log(typeof datasetReader);
if (isString || isFile) { // URL or file
var deferred = $.Deferred();
if (options.background) {
var path = phantasus.Util.getScriptPath();
var blob = new Blob(
[
'self.onmessage = function(e) {'
+ 'importScripts(e.data.path);'
+ 'var ext = phantasus.Util.getExtension(phantasus.Util'
+ '.getFileName(e.data.fileOrUrl));'
+ 'var datasetReader = phantasus.DatasetUtil.getDatasetReader(ext,'
+ ' e.data.options);'
+ 'datasetReader.read(e.data.fileOrUrl, function(err,dataset) {'
+ ' self.postMessage(dataset);' + ' });' + '}']);
var blobURL = window.URL.createObjectURL(blob);
var worker = new Worker(blobURL);
worker.addEventListener('message', function (e) {
deferred.resolve(phantasus.Dataset.fromJSON(e.data));
window.URL.revokeObjectURL(blobURL);
}, false);
// start the worker
worker.postMessage({
path: path,
fileOrUrl: fileOrUrl,
options: options
});
} else {
datasetReader.read(fileOrUrl, function (err, dataset) {
if (err) {
deferred.reject(err);
} else {
// console.log(dataset);
// console.log('ready to resolve with', dataset);
deferred.resolve(dataset);
}
});
}
var pr = deferred.promise();
// override toString so can determine file name
pr.toString = function () {
return '' + fileOrUrl;
};
return pr;
} else if (typeof fileOrUrl.done === 'function') { // assume it's a
// deferred
return fileOrUrl;
} else { // it's already a dataset?
var deferred = $.Deferred();
if (fileOrUrl.getRowCount) {
deferred.resolve(fileOrUrl);
} else {
deferred.resolve(phantasus.Dataset.fromJSON(fileOrUrl));
}
return deferred.promise();
}
};
/**
* @param dataset
* The dataset to convert to an array
* @param options.columns
* An array of column indices to include from the dataset
* @param options.columnFields
* An array of field names to use in the returned objects that
* correspond to the column indices in the dataset
* @param options.metadataFields
* An array of row metadata fields to include from the dataset
*
*/
phantasus.DatasetUtil.toObjectArray = function (dataset, options) {
var columns = options.columns || [0];
var columnFields = options.columnFields || ['value'];
if (columnFields.length !== columns.length) {
throw 'columns.length !== columnFields.length';
}
var metadataFields = options.metadataFields;
// grab all of the headers and filter the meta data vectors in the dataset
// down
// to the ones specified in metaFields. If metaFields is not passed, take
// all metadata
var rowMetadata = dataset.getRowMetadata();
if (!metadataFields) {
metadataFields = phantasus.MetadataUtil.getMetadataNames(rowMetadata);
}
var vectors = phantasus.MetadataUtil.getVectors(rowMetadata, metadataFields);
// build an object that contains the matrix values for the given columns
// along
// with any metadata
var array = [];
for (var i = 0; i < dataset.getRowCount(); i++) {
var obj = {};
for (var j = 0; j < columns.length; j++) {
obj[columnFields[j]] = dataset.getValue(i, columns[j]);
}
for (var j = 0; j < vectors.length; j++) {
obj[vectors[j].getName()] = vectors[j].getValue(i);
}
array.push(obj);
}
return array;
};
phantasus.DatasetUtil.fixL1K = function (dataset) {
var names = {
'cell_id': 'Cell Line',
'pert_idose': 'Dose (\u00B5M)',
'pert_iname': 'Name',
'pert_itime': 'Time (hr)',
'distil_ss': 'Signature Strength',
'pert_type': 'Type',
'cell_lineage': 'Lineage',
'cell_histology': 'Histology',
'cell_type': 'Cell Type'
};
var fixNames = function (metadata) {
for (var i = 0, count = metadata.getMetadataCount(); i < count; i++) {
var v = metadata.get(i);
var name = v.getName();
var mapped = names[name];
if (mapped) {
v.setName(mapped);
}
}
};
fixNames(dataset.getRowMetadata());
fixNames(dataset.getColumnMetadata());
var fix666 = function (metadata) {
for (var i = 0, count = metadata.getMetadataCount(); i < count; i++) {
var v = metadata.get(i);
if (v.getName() == 'Dose (\u00B5M)') { // convert to number
for (var j = 0, size = v.size(); j < size; j++) {
var value = v.getValue(j);
if (value != null) {
v.setValue(j, parseFloat(value));
}
}
}
var isNumber = false;
for (var j = 0, size = v.size(); j < size; j++) {
var value = v.getValue(j);
if (value != null) {
isNumber = _.isNumber(value);
break;
}
}
var newValue = isNumber || v.getName() == 'Dose (\u00B5M)' ? 0 : '';
for (var j = 0, size = v.size(); j < size; j++) {
var value = v.getValue(j);
if (value != null && value == '-666') {
v.setValue(j, newValue);
}
}
}
};
fix666(dataset.getRowMetadata());
fix666(dataset.getColumnMetadata());
var fixCommas = function (metadata) {
var regex = /(,)([^ ])/g;
_.each(['Lineage', 'Histology'], function (name) {
var v = metadata.getByName(name);
if (v != null) {
for (var i = 0, size = v.size(); i < size; i++) {
var val = v.getValue(i);
if (val) {
v.setValue(i, val.replace(regex, ', $2'));
}
}
}
});
};
fixCommas(dataset.getRowMetadata());
fixCommas(dataset.getColumnMetadata());
};
phantasus.DatasetUtil.geneSetsToDataset = function (name, sets) {
var uniqueIds = new phantasus.Map();
for (var i = 0, length = sets.length; i < length; i++) {
var ids = sets[i].ids;
for (var j = 0, nIds = ids.length; j < nIds; j++) {
uniqueIds.set(ids[j], 1);
}
}
var uniqueIdsArray = uniqueIds.keys();
var dataset = new phantasus.Dataset({
name: name,
rows: uniqueIdsArray.length,
columns: sets.length
});
var columnIds = dataset.getColumnMetadata().add('id');
for (var i = 0, length = sets.length; i < length; i++) {
columnIds.setValue(i, sets[i].name);
}
var rowIds = dataset.getRowMetadata().add('id');
for (var i = 0, size = uniqueIdsArray.length; i < size; i++) {
rowIds.setValue(i, uniqueIdsArray[i]);
}
var rowIdToIndex = phantasus.VectorUtil.createValueToIndexMap(rowIds);
for (var i = 0, length = sets.length; i < length; i++) {
var ids = sets[i].ids;
for (var j = 0, nIds = ids.length; j < nIds; j++) {
dataset.setValue(rowIdToIndex.get(ids[j]), i, 1);
}
}
return dataset;
};
phantasus.DatasetUtil.getRootDataset = function (dataset) {
while (dataset.getDataset) {
dataset = dataset.getDataset();
}
return dataset;
};
phantasus.DatasetUtil.getSeriesIndex = function (dataset, name) {
for (var i = 0, nseries = dataset.getSeriesCount(); i < nseries; i++) {
if (name === dataset.getName(i)) {
return i;
}
}
return -1;
};
phantasus.DatasetUtil.getSeriesNames = function (dataset) {
var names = [];
for (var i = 0, nseries = dataset.getSeriesCount(); i < nseries; i++) {
names.push(dataset.getName(i));
}
// names.sort(function (a, b) {
// a = a.toLowerCase();
// b = b.toLowerCase();
// return (a < b ? -1 : (a === b ? 0 : 1));
// });
return names;
};
/**
* Search dataset values.
*
* @param options.dataset
* The dataset
* @param options.text
* Search text
* @param options.defaultMatchMode
* 'exact' or 'contains'
* @param options.matchAllPredicates Whether to match all predicates
* @return Set of matching indices.
*
*/
phantasus.DatasetUtil.searchValues = function (options) {
if (text === '') {
return;
}
var dataset = options.dataset;
var text = options.text;
var tokens = phantasus.Util.getAutocompleteTokens(text);
if (tokens.length == 0) {
return;
}
var predicates = phantasus.Util.createSearchPredicates({
tokens: tokens,
defaultMatchMode: options.defaultMatchMode
});
var matchAllPredicates = options.matchAllPredicates === true;
var npredicates = predicates.length;
var viewIndices = new phantasus.Set();
function isMatch(object, toObject, predicate) {
if (object != null) {
if (toObject) {
var filterColumnName = predicate.getField();
if (filterColumnName != null) {
var value = object[filterColumnName];
return predicate.accept(value);
} else { // try all fields
for (var name in object) {
var value = object[name];
return predicate.accept(value);
}
}
} else {
var filterColumnName = predicate.getField();
if (filterColumnName == null || filterColumnName === dataset.getName(k)) {
return predicate.accept(object);
}
}
}
}
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var matches = false;
itemSearch:
if (matchAllPredicates) {
matches = true;
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
var pmatch = false;
for (var k = 0, nseries = dataset.getSeriesCount(); k < nseries; k++) {
var element = dataset.getValue(i, j, k);
var isObject = element != null && element.toObject != null;
if (isObject) {
element = element.toObject();
}
if (isMatch(element, isObject, predicate)) {
pmatch = true;
break;
}
}
if (!pmatch) {
matches = false;
break itemSearch;
}
}
} else {
for (var k = 0, nseries = dataset.getSeriesCount(); k < nseries; k++) {
var element = dataset.getValue(i, j, k);
var isObject = element != null && element.toObject != null;
if (isObject) {
element = element.toObject();
}
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
if (isMatch(element, isObject, predicate)) {
matches = true;
break itemSearch;
}
}
}
}
if (matches) {
viewIndices
.add(new phantasus.Identifier(
[i, j]));
}
}
}
return viewIndices;
};
/**
* Search dataset values.
*/
phantasus.DatasetUtil.autocompleteValues = function (dataset) {
return function (tokens, cb) {
var token = tokens != null && tokens.length > 0 ? tokens[tokens.selectionStartIndex]
: '';
token = $.trim(token);
var seriesIndices = [];
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
for (var k = 0, nseries = dataset.getSeriesCount(); k < nseries; k++) {
if (dataset.getDataType(i, k) === 'Number') {
seriesIndices.push([i, k]);
}
}
}
if (seriesIndices.length === 0) {
return cb();
}
var _val; // first non-null value
elementSearch: for (var k = 0, nseries = seriesIndices.length; k < nseries; k++) {
var pair = seriesIndices[k];
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var element = dataset.getValue(pair[0], j, pair[1]);
if (element != null && element.toObject) {
_val = element.toObject();
break elementSearch;
}
}
}
var matches = [];
var fields = _val == null ? [] : _.keys(_val);
if (token === '') {
fields.sort(function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
fields.forEach(function (field) {
matches.push({
value: field + ':',
label: '<span style="font-weight:300;">' + field
+ ':</span>',
show: true
});
});
return cb(matches);
}
var field = null;
var semi = token.indexOf(':');
if (semi > 0) { // field search?
if (token.charCodeAt(semi - 1) !== 92) { // \:
var possibleField = $.trim(token.substring(0, semi));
if (possibleField.length > 0 && possibleField[0] === '"'
&& possibleField[token.length - 1] === '"') {
possibleField = possibleField.substring(1,
possibleField.length - 1);
}
var index = fields.indexOf(possibleField);
if (index !== -1) {
token = $.trim(token.substring(semi + 1));
field = possibleField;
}
}
}
var set = new phantasus.Set();
// regex used to determine if a string starts with substring `q`
var regex = new RegExp('^' + phantasus.Util.escapeRegex(token), 'i');
// iterate through the pool of strings and for any string that
// contains the substring `q`, add it to the `matches` array
var max = 10;
loop: for (var k = 0, nseries = seriesIndices.length; k < nseries; k++) {
var pair = seriesIndices[k];
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var element = dataset.getValue(pair[0], j, pair[1]);
if (element && element.toObject) {
var object = element.toObject();
if (field !== null) {
var val = object[field];
if (val != null) {
var id = new phantasus.Identifier([val, field]);
if (!set.has(id) && regex.test(val)) {
set.add(id);
if (set.size() === max) {
break loop;
}
}
}
} else { // search all fields
for (var name in object) {
var val = object[name];
var id = new phantasus.Identifier([val, name]);
if (!set.has(id) && regex.test(val)) {
set.add(id);
if (set.size() === max) {
break loop;
}
}
}
}
}
}
}
set.forEach(function (id) {
var array = id.getArray();
var field = array[1];
var val = array[0];
matches.push({
value: field + ':' + val,
label: '<span style="font-weight:300;">' + field + ':</span>'
+ '<span style="font-weight:900;">' + val + '</span>'
});
});
if (field == null) {
fields.forEach(function (field) {
if (regex.test(field)) {
matches.push({
value: field + ':',
label: '<span style="font-weight:300;">' + field
+ ':</span>',
show: true
});
}
});
}
cb(matches);
};
};
// phantasus.DatasetUtil.toJSON = function(dataset) {
// var json = [];
// json.push('{');
// json.push('"name":"' + dataset.getName() + '", ');
// json.push('"v":['); // row major 2d array
// for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
// if (i > 0) {
// json.push(',\n');
// }
// json.push('[');
// for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
// if (j > 0) {
// json.push(',');
// }
// json.push(JSON.stringify(dataset.getValue(i, j)));
// }
// json.push(']');
// }
// json.push(']'); // end v
// var metadatatoJSON = function(model) {
// json.push('[');
// for (var i = 0, count = model.getMetadataCount(); i < count; i++) {
// var v = model.get(i);
// if (i > 0) {
// json.push(',\n');
// }
// json.push('{');
// json.push('"id":"' + v.getName() + '"');
// json.push(', "v":[');
// for (var j = 0, nitems = v.size(); j < nitems; j++) {
// if (j > 0) {
// json.push(',');
// }
// json.push(JSON.stringify(v.getValue(j)));
// }
// json.push(']'); // end v array
// json.push('}');
// }
// json.push(']');
// };
// json.push(', "cols":');
// metadatatoJSON(dataset.getColumnMetadata());
// json.push(', "rows":');
// metadatatoJSON(dataset.getRowMetadata());
// json.push('}'); // end json object
// return json.join('');
// };
phantasus.DatasetUtil.fill = function (dataset, value, seriesIndex) {
seriesIndex = seriesIndex || 0;
for (var i = 0, nrows = dataset.getRowCount(), ncols = dataset
.getColumnCount(); i < nrows; i++) {
for (var j = 0; j < ncols; j++) {
dataset.setValue(i, j, value, seriesIndex);
}
}
};
/**
* Add an additional series to a dataset from another dataset.
* @param options.dataset The dataset to add a series to
* @param options.newDataset The dataset that is used as the source for the overlay
* @param options.rowAnnotationName dataset row annotation name to use for matching
* @param options.columnAnnotationName dataset column annotation name to use for matching
* @param options.newRowAnnotationName newDataset row annotation name to use for matching
* @param options.newColumnAnnotationName newDataset column annotation name to use for matching
*
*/
phantasus.DatasetUtil.overlay = function (options) {
var dataset = options.dataset;
var newDataset = options.newDataset;
var current_dataset_row_annotation_name = options.rowAnnotationName;
var current_dataset_column_annotation_name = options.columnAnnotationName;
var new_dataset_row_annotation_name = options.newRowAnnotationName;
var new_dataset_column_annotation_name = options.newColumnAnnotationName;
var rowValueToIndexMap = phantasus.VectorUtil
.createValueToIndexMap(dataset
.getRowMetadata()
.getByName(
current_dataset_row_annotation_name));
var columnValueToIndexMap = phantasus.VectorUtil
.createValueToIndexMap(dataset
.getColumnMetadata()
.getByName(
current_dataset_column_annotation_name));
var seriesIndex = dataset
.addSeries({
name: newDataset
.getName(),
dataType: newDataset.getDataType(0)
});
var rowVector = newDataset
.getRowMetadata()
.getByName(
new_dataset_row_annotation_name);
var rowIndices = [];
var newDatasetRowIndicesSubset = [];
for (var i = 0, size = rowVector
.size(); i < size; i++) {
var index = rowValueToIndexMap
.get(rowVector
.getValue(i));
if (index !== undefined) {
rowIndices.push(index);
newDatasetRowIndicesSubset
.push(i);
}
}
var columnVector = newDataset
.getColumnMetadata()
.getByName(
new_dataset_column_annotation_name);
var columnIndices = [];
var newDatasetColumnIndicesSubset = [];
for (var i = 0, size = columnVector
.size(); i < size; i++) {
var index = columnValueToIndexMap
.get(columnVector
.getValue(i));
if (index !== undefined) {
columnIndices.push(index);
newDatasetColumnIndicesSubset
.push(i);
}
}
newDataset = new phantasus.SlicedDatasetView(
newDataset,
newDatasetRowIndicesSubset,
newDatasetColumnIndicesSubset);
for (var i = 0, nrows = newDataset
.getRowCount(); i < nrows; i++) {
for (var j = 0, ncols = newDataset
.getColumnCount(); j < ncols; j++) {
dataset.setValue(
rowIndices[i],
columnIndices[j],
newDataset
.getValue(
i,
j),
seriesIndex);
}
}
};
/**
* Joins datasets by appending rows.
* @param datasets
* @param field
* @return {phantasus.AbstractDataset} The joined dataset.
*/
phantasus.DatasetUtil.join = function (datasets, field) {
if (datasets.length === 0) {
throw 'No datasets';
}
if (datasets.length === 1) {
var name = datasets[0].getName();
var sourceVector = datasets[0].getRowMetadata().add('Source');
for (var i = 0, size = sourceVector.size(); i < size; i++) {
sourceVector.setValue(i, name);
}
return datasets[0];
}
// take union of all ids
var ids = new phantasus.Set();
for (var i = 0; i < datasets.length; i++) {
var idVector = datasets[i].getColumnMetadata().getByName(field);
for (var j = 0, size = idVector.size(); j < size; j++) {
ids.add(idVector.getValue(j));
}
}
var dummyDataset = new phantasus.Dataset({
rows: 0,
columns: ids.size(),
name: datasets[0].getName()
});
var dummyIdVector = dummyDataset.getColumnMetadata().add(field);
var counter = 0;
ids.forEach(function (id) {
dummyIdVector.setValue(counter++, id);
});
var dataset = new phantasus.JoinedDataset(
dummyDataset, datasets[0], field,
field);
for (var i = 1; i < datasets.length; i++) {
dataset = new phantasus.JoinedDataset(dataset,
datasets[i], field, field);
}
return dataset;
};
phantasus.DatasetUtil.shallowCopy = function (dataset) {
// make a shallow copy of the dataset, metadata is immutable via the UI
var rowMetadataModel = phantasus.MetadataUtil.shallowCopy(dataset
.getRowMetadata());
var columnMetadataModel = phantasus.MetadataUtil.shallowCopy(dataset
.getColumnMetadata());
dataset.getRowMetadata = function () {
return rowMetadataModel;
};
dataset.getColumnMetadata = function () {
return columnMetadataModel;
};
return dataset;
};
phantasus.DatasetUtil.copy = function (dataset) {
var newDataset = new phantasus.Dataset({
name: dataset.getName(),
rows: dataset.getRowCount(),
columns: dataset.getColumnCount(),
dataType: dataset.getDataType(0)
});
for (var seriesIndex = 0,
nseries = dataset.getSeriesCount(); seriesIndex < nseries; seriesIndex++) {
if (seriesIndex > 0) {
newDataset.addSeries({
name: dataset.getName(seriesIndex),
rows: dataset.getRowCount(),
columns: dataset.getColumnCount(),
dataType: dataset.getDataType(seriesIndex)
});
}
for (var i = 0, nrows = dataset.getRowCount(), ncols = dataset
.getColumnCount(); i < nrows; i++) {
for (var j = 0; j < ncols; j++) {
newDataset.setValue(i, j, dataset.getValue(i, j, seriesIndex),
seriesIndex);
}
}
}
var rowMetadataModel = phantasus.MetadataUtil.shallowCopy(dataset
.getRowMetadata());
var columnMetadataModel = phantasus.MetadataUtil.shallowCopy(dataset
.getColumnMetadata());
newDataset.getRowMetadata = function () {
return rowMetadataModel;
};
newDataset.getColumnMetadata = function () {
return columnMetadataModel;
};
if (dataset.getESSession()) {
newDataset.setESSession(dataset.getESSession());
}
return newDataset;
};
phantasus.DatasetUtil.toString = function (dataset, value, seriesIndex) {
seriesIndex = seriesIndex || 0;
var s = [];
for (var i = 0, nrows = dataset.getRowCount(), ncols = dataset
.getColumnCount(); i < nrows; i++) {
for (var j = 0; j < ncols; j++) {
if (j > 0) {
s.push(', ');
}
s.push(phantasus.Util.nf(dataset.getValue(i, j, seriesIndex)));
}
s.push('\n');
}
return s.join('');
};
phantasus.DatasetUtil.getNonEmptyRows = function (dataset) {
var rowsToKeep = [];
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
var keep = false;
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var value = dataset.getValue(i, j);
if (!isNaN(value)) {
keep = true;
break;
}
}
if (keep) {
rowsToKeep.push(i);
}
}
return rowsToKeep;
};
phantasus.DatasetUtil.getContentArray = function (dataset) {
var array = [];
var nr = dataset.getRowCount();
var nc = dataset.getColumnCount();
for (var i = 0; i < nc; i++) {
for (var j = 0; j < nr; j++) {
array.push(dataset.getValue(j, i));
}
}
return array;
};
phantasus.DatasetUtil.getMetadataArray = function (dataset) {
var pDataArray = [];
var labelDescription = [];
//console.log("phantasus.DatasetUtil.getMetadataArray ::", dataset);
var columnMeta = dataset.getColumnMetadata();
var features = columnMeta.getMetadataCount();
var participants = dataset.getColumnCount();
for (var j = 0; j < features; j++) {
var vecJ = columnMeta.get(j);
for (var l = 0; l < participants; l++) {
pDataArray.push({
strval: vecJ.getValue(l) ? vecJ.getValue(l).toString() : "",
isNA: false
});
}
labelDescription.push({
strval: vecJ.getName(),
isNA: false
});
}
var rowMeta = dataset.getRowMetadata();
var fDataArray = [];
var varLabels = [];
for (var j = 0; j < rowMeta.getMetadataCount(); j++) {
var vecJ = rowMeta.get(j);
for (var l = 0; l < dataset.getRowCount(); l++) {
fDataArray.push({
strval: vecJ.getValue(l) ? vecJ.getValue(l).toString() : "",
isNA: false
});
}
varLabels.push({
strval: vecJ.getName(),
isNA: false
});
}
return {
pdata: pDataArray,
varLabels: labelDescription,
fdata: fDataArray,
fvarLabels: varLabels
};
};
phantasus.DatasetUtil.toESSessionPromise = function (dataset) {
var datasetSession = dataset.getESSession();
dataset.setESSession(new Promise(function (resolve, reject) {
phantasus.DatasetUtil.probeDataset(dataset, datasetSession).then(function (result) {
if (result) { // dataset identical to one in session.
resolve(datasetSession);
dataset.esSource = 'original';
return;
}
var array = phantasus.DatasetUtil.getContentArray(dataset);
var meta = phantasus.DatasetUtil.getMetadataArray(dataset);
var expData = dataset.getExperimentData() || {
name: { values: "" },
lab: { values: "" },
contact: { values: "" },
title: { values: "" },
url: { values: "" },
other: { empty: { values: "" } },
pubMedIds: { values: "" },
};
var messageJSON = {
rclass: "LIST",
rexpValue: [{
rclass: "REAL",
realValue: array,
attrName: "dim",
attrValue: {
rclass: "INTEGER",
intValue: [dataset.getRowCount(), dataset.getColumnCount()]
}
}, {
rclass: "STRING",
stringValue: meta.pdata,
attrName: "dim",
attrValue: {
rclass: "INTEGER",
intValue: [dataset.getColumnCount(), meta.varLabels.length]
}
}, {
rclass: "STRING",
stringValue: meta.varLabels
}, {
rclass: "STRING",
stringValue: meta.fdata,
attrName: "dim",
attrValue: {
rclass: "INTEGER",
intValue: [dataset.getRowCount(), meta.fvarLabels.length]
}
}, {
rclass: "STRING",
stringValue: meta.fvarLabels
}],
attrName: "names",
attrValue: {
rclass: "STRING",
stringValue: [{
strval: "data",
isNA: false
}, {
strval: "pData",
isNA: false
}, {
strval: "varLabels",
isNA: false
}, {
strval: "fData",
isNA: false
}, {
strval: "fvarLabels",
isNA: false
}, {
strval: "eData",
isNA: false
}]
}
};
messageJSON.rexpValue.push({
rclass: "LIST",
attrName: "names",
attrValue: {
rclass: "STRING",
stringValue: Object.keys(expData).map(function (name) {
return {
strval: name,
isNA: false
}
})
},
rexpValue: [{
rclass: "STRING",
stringValue: [{
strval: expData.name.values.toString(),
isNA: false
}]
}, {
rclass: "STRING",
stringValue: [{
strval: expData.lab.values.toString(),
isNA: false
}]
}, {
rclass: "STRING",
stringValue: [{
strval: expData.contact.values.toString(),
isNA: false
}]
}, {
rclass: "STRING",
stringValue: [{
strval: expData.title.values.toString(),
isNA: false
}]
}, {
rclass: "STRING",
stringValue: [{
strval: expData.url.values.toString(),
isNA: false
}]
}, {
rclass: "LIST",
attrName: "names",
attrValue: {
rclass: "STRING",
stringValue: Object.keys(expData.other).map(function (name) {
return {
strval: name,
isNA: false
}
})
},
rexpValue: _.map(expData.other, function (value) {
return {
rclass: "STRING",
stringValue: [{strval: value.values.toString(), isNA: false}]
}
})
}, {
rclass: "STRING",
stringValue: [{
strval: expData.pubMedIds.values.toString(),
isNA: false
}]
}]
});
var ProtoBuf = dcodeIO.ProtoBuf;
ProtoBuf.protoFromFile('./message.proto', function (error, success) {
if (error) {
alert(error);
return;
}
var builder = success,
rexp = builder.build('rexp'),
REXP = rexp.REXP;
var proto = new REXP(messageJSON);
var req = ocpu.call('createES', proto, function (session) {
dataset.esSource = 'original';
resolve(session);
}, true);
req.fail(function () {
reject(req.responseText);
});
});
});
}));
};
phantasus.DatasetUtil.probeDataset = function (dataset, session) {
var targetSession = session || dataset.getESSession();
return new Promise(function (resolve) {
if (!targetSession) {
return resolve(false);
}
var meta = phantasus.DatasetUtil.getMetadataArray(dataset);
var fData = dataset.getRowMetadata();
var fvarLabels = meta.fvarLabels.map(function (fvarLabel) { return (fvarLabel.isNA)?'NA':fvarLabel.strval});
var query = {
exprs: [],
fData: []
};
var epsExprs = 0.01;
var epsFdata = 0.1;
var verifyExprs = function (value, index) {
var ij = query.exprs[index];
var testValue = dataset.getValue(ij[0] - 1, ij[1] - 1);
var rdaValue = parseFloat(value);
return (isNaN(rdaValue) && isNaN(testValue)) || Math.abs(rdaValue - testValue) < epsExprs;
};
var verifyFeature = function (name, backendValues) {
var indices = _.find(query.fData, {name: name}).indices;
var column = fData.getByName(name);
var frontendValues = _.map(indices, function (index) {return column.getValue(index - 1)});
var type = column.getProperties().get(phantasus.VectorKeys.DATA_TYPE);
if (type === 'number' || type === '[number]') {
return frontendValues.every(function (value, index) {
var backendValue = parseFloat(backendValues[index]); // backend might be string, frontend number
return (isNaN(value) && isNaN(backendValue) === isNaN(value)) || // both NaN
Math.abs(value - backendValue) < epsFdata;
});
} else {
backendValues = _.map(backendValues, function (value) { // backend might be numbers, frontend string
return value === null ||
value === undefined ||
value === '' ||
value === 'NA' ? 'NA' : value.toString();
});
frontendValues = _.map(frontendValues, function (value) {
return value || 'NA';
});
return _.isEqual(backendValues,frontendValues);
}
};
query.exprs = _.times(100, function () {
var jIdx = _.random(0, dataset.getColumnCount() - 1) + 1;
var iIdx = _.random(0, dataset.getRowCount() - 1) + 1;
return [iIdx, jIdx];
});
query.fData = _.map(fData.vectors, function (fDataVector) {
var fDataVectorMeta = {name: fDataVector.getName()};
fDataVectorMeta.indices = _.times(20, function () {
return _.random(0, fDataVector.size() - 1) + 1;
});
return fDataVectorMeta;
});
targetSession.then(function (essession) {
var request = {
es: essession,
query: query
};
var req = ocpu.call("probeDataset/print", request, function (newSession) {
var backendProbe = JSON.parse(newSession.txt);
var isRowCountEqual = backendProbe.dims[0] === dataset.getRowCount();
var isColumnCountEqual = backendProbe.dims[1] === dataset.getColumnCount();
var exprsEqual = backendProbe.probe.every(verifyExprs);
var fDataValuesEqual = true;
var fDataNamesEqual = fvarLabels.every(function (value) {
return backendProbe.fvarLabels.indexOf(value) !== -1;
});
if (fDataNamesEqual) {
_.each(backendProbe.fdata, function (values, name) {
if (!fDataValuesEqual) {
return;
}
fDataValuesEqual = verifyFeature(name, values);
});
}
resolve(isRowCountEqual && isColumnCountEqual && exprsEqual && fDataNamesEqual && fDataValuesEqual);
}, false, "::es");
req.fail(function () {
resolve(false);
});
}, function () { resolve(false); });
});
};
/**
* Default implementation of a dataset.
*
* @extends {phantasus.AbstractDataset}
* @param options.rows {number} Number of rows
* @param options.columns {number} Number of columns
* @param options.name {string} Dataset name
* @param options.dataType {string=} Data type that 1st series holds.
* @param options.esSession {Promise} openCPU session, which contains ExpressionSet version of the dataset
* @constructor
*/
phantasus.Dataset = function (options) {
phantasus.AbstractDataset.call(this, options.rows,
options.columns);
if (options.dataType == null) {
options.dataType = 'Float32';
}
if (options.esSession) {
this.esSession = options.esSession;
}
this.isGEO = options.isGEO; // geo and preloaded datasets doesn't need to renew essession, they already have valid one
this.preloaded = options.preloaded;
this.seriesNames.push(options.name);
this.seriesArrays.push(options.array ? options.array : phantasus.Dataset
.createArray(options));
this.seriesDataTypes.push(options.dataType);
this.experimentData = options.experimentData;
//// console.log(this);
};
/**
*
* @param dataset
* @param options.rowFields
* @param options.columnFields
* @param options.seriesIndices
* @return JSON representation of a dataset
*/
phantasus.Dataset.toJSON = function (dataset, options) {
options = options || {};
var seriesArrays = [];
var seriesDataTypes = [];
var seriesNames = [];
var seriesIndices = options.seriesIndices;
if (seriesIndices == null) {
seriesIndices = phantasus.Util.sequ32(dataset.getSeriesCount());
}
for (var series = 0; series < seriesIndices.length; series++) {
var seriesIndex = seriesIndices[series];
seriesNames.push(dataset.getName(seriesIndex));
seriesDataTypes.push(dataset.getDataType(seriesIndex));
var data = [];
seriesArrays.push(data);
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
var row = [];
data.push(row);
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
row[j] = dataset.getValue(i, j, seriesIndex);
}
}
}
var vectorToJSON = function (vector) {
var array = [];
for (var i = 0, size = vector.size(); i < size; i++) {
array[i] = vector.getValue(i);
}
var properties = new phantasus.Map();
vector.getProperties().forEach(function (value, key) {
if (phantasus.VectorKeys.JSON_WHITELIST.has(key)) {
properties.set(key, value);
}
});
return {
properties: properties,
name: vector.getName(),
array: array
};
};
var metadatatoJSON = function (metadata, fields) {
var vectors = [];
var filter;
if (fields) {
filter = new phantasus.Set();
fields.forEach(function (field) {
filter.add(field);
});
}
for (var i = 0, count = metadata.getMetadataCount(); i < count; i++) {
var v = metadata.get(i);
if (!v.getProperties().has(phantasus.VectorKeys.IS_INDEX)) {
if (filter) {
if (filter.has(v.getName())) {
vectors.push(vectorToJSON(v));
}
} else {
vectors.push(vectorToJSON(v));
}
}
}
return vectors;
};
return {
rows: dataset.getRowCount(),
columns: dataset.getColumnCount(),
seriesArrays: seriesArrays,
seriesDataTypes: seriesDataTypes,
seriesNames: seriesNames,
rowMetadataModel: {
vectors: metadatatoJSON(dataset.getRowMetadata(),
options.rowFields)
},
columnMetadataModel: {
vectors: metadatatoJSON(dataset.getColumnMetadata(),
options.columnFields)
}
};
};
phantasus.Dataset.fromJSON = function (options) {
// Object {seriesNames:
// Array[1], seriesArrays:
// Array[1], rows:
// 6238, columns: 7251,
// rowMetadataModel: Object…}
// columnMetadataModel: Object
// itemCount: 7251
// vectors: Array[3]
// array: Array[7251]
// n: 7251
// name: "pert_id"
// properties: Object
// columns: 7251
// rowMetadataModel: Object
// rows: 6238
// seriesArrays: Array[1]
// seriesNames: Array[1]
// var array = phantasus.Dataset.createArray(options);
// for (var i = 0; i < options.rows; i++) {
// var row = array[i];
// var jsonRow = options.array[i];
// for (var j = 0; j < options.columns; j++) {
// row[j] = jsonRow[j];
// }
// }
if (options.seriesMappings) {
for (var seriesIndex = 0; seriesIndex < options.seriesMappings.length; seriesIndex++) {
// map ordinal values
if (options.seriesMappings[seriesIndex]) {
var map = options.seriesMappings[seriesIndex]; // e.g. foo:1, bar:3
var valueMap = new phantasus.Map();
for (var key in map) {
var value = map[key];
valueMap.set(value, phantasus.Util.wrapNumber(value, key));
}
var array = options.seriesArrays[seriesIndex];
for (var i = 0; i < options.rows; i++) {
for (var j = 0; j < options.columns; j++) {
var value = array[i][j];
array[i][j] = valueMap.get(value);
}
}
options.seriesDataTypes[seriesIndex] = 'Number';
}
}
}
for (var seriesIndex = 0; seriesIndex < options.seriesArrays.length; seriesIndex++) {
var array = options.seriesArrays[seriesIndex];
for (var i = 0; i < options.rows; i++) {
for (var j = 0; j < options.columns; j++) {
var value = array[i][j];
if (value == null) {
array[i][j] = NaN;
}
}
}
}
var dataset = new phantasus.Dataset({
name: options.seriesNames[0],
dataType: options.seriesDataTypes[0],
array: options.seriesArrays[0],
rows: options.rows,
columns: options.columns
});
if (options.rowMetadataModel) {
options.rowMetadataModel.vectors.forEach(function (v) {
var vector = new phantasus.Vector(v.name, dataset.getRowCount());
vector.array = v.array;
vector.properties = phantasus.Map.fromJSON(v.properties);
dataset.rowMetadataModel.vectors.push(vector);
});
}
if (options.columnMetadataModel) {
options.columnMetadataModel.vectors.forEach(function (v) {
var vector = new phantasus.Vector(v.name, dataset.getColumnCount());
vector.array = v.array;
vector.properties = phantasus.Map.fromJSON(v.properties);
dataset.columnMetadataModel.vectors.push(vector);
});
}
for (var i = 1; i < options.seriesArrays.length; i++) {
dataset.addSeries({
name: options.seriesNames[i],
dataType: options.seriesDataTypes[i],
array: options.seriesArrays[i]
});
}
return dataset;
};
phantasus.Dataset.createArray = function (options) {
var array = [];
if (options.dataType == null || options.dataType === 'Float32') {
for (var i = 0; i < options.rows; i++) {
array.push(new Float32Array(options.columns));
}
} else if (options.dataType === 'Int8') {
for (var i = 0; i < options.rows; i++) {
array.push(new Int8Array(options.columns));
}
} else if (options.dataType === 'Int16') {
for (var i = 0; i < options.rows; i++) {
array.push(new Int16Array(options.columns));
}
} else { // [object, number, Number] array of arrays
for (var i = 0; i < options.rows; i++) {
array.push([]);
}
}
return array;
};
phantasus.Dataset.prototype = {
getValue: function (i, j, seriesIndex) {
seriesIndex = seriesIndex || 0;
return this.seriesArrays[seriesIndex][i][j];
},
toString: function () {
return this.getName();
},
setValue: function (i, j, value, seriesIndex) {
seriesIndex = seriesIndex || 0;
this.seriesArrays[seriesIndex][i][j] = value;
},
addSeries: function (options) {
options = $.extend({}, {
rows: this.getRowCount(),
columns: this.getColumnCount(),
dataType: 'Float32'
}, options);
this.seriesDataTypes.push(options.dataType);
this.seriesNames.push(options.name);
this.seriesArrays.push(options.array != null ? options.array
: phantasus.Dataset.createArray(options));
return this.seriesNames.length - 1;
},
setESSession: function (session) {
//// console.log("phantasus.Dataset.prototype.setESSession ::", this, session);
this.esSession = session;
},
getESSession: function () {
//// console.log("phantasus.Dataset.prototype.getESSession ::", this);
return this.esSession;
},
getExperimentData: function () {
return this.experimentData;
}
};
phantasus.Util.extend(phantasus.Dataset, phantasus.AbstractDataset);
phantasus.ElementSelectionModel = function (project) {
this.viewIndices = new phantasus.Set();
this.project = project;
};
phantasus.ElementSelectionModel.prototype = {
click: function (rowIndex, columnIndex, add) {
var id = new phantasus.Identifier([rowIndex, columnIndex]);
var isSelected = this.viewIndices.has(id);
if (add) {
isSelected ? this.viewIndices.remove(id) : this.viewIndices.add(id);
} else {
this.viewIndices.clear();
if (!isSelected) {
this.viewIndices.add(id);
}
}
this.trigger('selectionChanged');
},
getProject: function () {
return this.project;
},
setViewIndices: function (indices) {
this.viewIndices = indices;
this.trigger('selectionChanged');
},
clear: function () {
this.viewIndices = new phantasus.Set();
},
/**
*
* @returns {phantasus.Set}
*/
getViewIndices: function () {
return this.viewIndices;
},
count: function () {
return this.viewIndices.size();
},
toModelIndices: function () {
var project = this.project;
var modelIndices = [];
this.viewIndices.forEach(function (id) {
modelIndices.push(project
.convertViewRowIndexToModel(id.getArray()[0]), project
.convertViewColumnIndexToModel(id.getArray()[1]));
});
return modelIndices;
},
save: function () {
this.modelIndices = this.toModelIndices();
},
restore: function () {
var project = this.project;
this.viewIndices = new phantasus.Set();
for (var i = 0, length = this.modelIndices.length; i < length; i++) {
var rowIndex = project
.convertModelRowIndexToView(this.modelIndices[i][0]);
var columnIndex = project
.convertModelColumnIndexToView(this.modelIndices[i][1]);
if (rowIndex !== -1 && columnIndex !== -1) {
this.viewIndices.add(new phantasus.Identifier([rowIndex,
columnIndex]));
}
}
}
};
phantasus.Util.extend(phantasus.ElementSelectionModel, phantasus.Events);
phantasus.CombinedFilter = function (isAndFilter) {
this.filters = [];
this.isAndFilter = isAndFilter;
this.enabledFilters = [];
this.name = 'combined filter';
};
phantasus.CombinedFilter.prototype = {
shallowClone: function () {
var f = new phantasus.CombinedFilter(this.isAndFilter);
f.filters = this.filters.slice(0);
return f;
},
isColumns: function () {
return this.filters[0].isColumns();
},
toString: function () {
return this.name;
},
setAnd: function (isAndFilter, notify) {
this.isAndFilter = isAndFilter;
if (notify) {
this.trigger('and', {});
}
},
isAnd: function () {
return this.isAndFilter;
},
equals: function (f) {
if (!(f instanceof phantasus.CombinedFilter)) {
return false;
}
if (this.isAndFilter !== f.isAndFilter) {
return false;
}
if (this.filters.length !== f.filters.length) {
return false;
}
for (var i = 0, length = this.filters.length; i < length; i++) {
if (!this.filters[i].equals(f.filters[i])) {
return false;
}
}
return true;
},
add: function (filter, notify) {
this.filters.push(filter);
if (notify) {
this.trigger('add', {
filter: filter,
});
}
},
getFilters: function () {
return this.filters;
},
get: function (index) {
return this.filters[index];
},
indexOf: function (name, type) {
for (var i = 0, length = this.filters.length; i < length; i++) {
if (this.filters[i].toString() === name
&& (type == null ? true : this.filters[i] instanceof type)) {
return i;
}
}
return -1;
},
remove: function (index, notify) {
this.filters.splice(index, 1);
if (notify) {
this.trigger('remove', {
index: index,
});
}
},
set: function (index, filter) {
this.filters[index] = filter;
},
insert: function (index, filter) {
this.filters.splice(index, 0, filter);
},
clear: function () {
this.filters = [];
},
init: function (dataset) {
for (var i = 0, nfilters = this.filters.length; i < nfilters; i++) {
if (this.filters[i].isColumns()) { // all filters operate on rows
this.filters[i].init(new phantasus.TransposedDatasetView(dataset));
} else {
this.filters[i].init(dataset);
}
}
this.enabledFilters = this.filters.filter(function (filter) {
return filter.isEnabled();
});
},
accept: function (index) {
var filters = this.enabledFilters;
if (this.isAndFilter) {
for (var i = 0, nfilters = filters.length; i < nfilters; i++) {
if (filters[i].accept(index) === false) {
return false;
}
}
return true;
} else {
for (var i = 0, nfilters = filters.length; i < nfilters; i++) {
if (filters[i].accept(index)) {
return true;
}
}
return false;
}
},
isEnabled: function () {
return this.enabledFilters.length > 0;
},
};
phantasus.Util.extend(phantasus.CombinedFilter, phantasus.Events);
/**
* @param acceptIndicesSet
* a phantasus.Set that contains the model indices in the dataset to
* retain.
*/
phantasus.IndexFilter = function (acceptIndicesSet, name, isColumns) {
this.acceptIndicesSet = acceptIndicesSet;
this.name = name;
this.columns = isColumns;
};
phantasus.IndexFilter.prototype = {
enabled: true,
isColumns: function () {
return this.columns;
},
isEnabled: function () {
return this.enabled;
},
setAcceptIndicesSet: function (acceptIndicesSet) {
this.acceptIndicesSet = acceptIndicesSet;
},
setEnabled: function (enabled) {
this.enabled = enabled;
},
equals: function (filter) {
return filter instanceof phantasus.IndexFilter
&& this.acceptIndicesSet.equals(filter.acceptIndicesSet);
},
init: function (dataset) {
},
toString: function () {
return this.name;
},
/**
*
* @param index
* The model index in the dataset
* @returns {Boolean} true if index passes filter
*/
accept: function (index) {
return this.acceptIndicesSet.has(index);
},
};
phantasus.VectorFilter = function (set, maxSetSize, name, isColumns) {
this.set = set;
this.name = name;
this.maxSetSize = maxSetSize;
this.columns = isColumns;
};
phantasus.VectorFilter.prototype = {
enabled: true,
isColumns: function () {
return this.columns;
},
isEnabled: function () {
return this.enabled && this.set.size() > 0
&& this.set.size() !== this.maxSetSize && this.vector != null;
},
setEnabled: function (enabled) {
this.enabled = enabled;
},
equals: function (filter) {
return filter instanceof phantasus.VectorFilter
&& this.name === filter.name;
},
init: function (dataset) {
this.vector = dataset.getRowMetadata().getByName(this.name);
},
toString: function () {
return this.name;
},
/**
*
* @param index
* The model index in the dataset
* @returns {Boolean} true if index passes filter
*/
accept: function (index) {
return this.set.has(this.vector.getValue(index));
},
};
phantasus.NotNullFilter = function (name, isColumns) {
this.name = name;
this.columns = isColumns;
};
phantasus.NotNullFilter.prototype = {
enabled: true,
isColumns: function () {
return this.columns;
},
isEnabled: function () {
return this.enabled && this.vector != null;
},
setEnabled: function (enabled) {
this.enabled = enabled;
},
equals: function (filter) {
return filter instanceof phantasus.NotNullFilter
&& this.name === filter.name;
},
init: function (dataset) {
this.vector = dataset.getRowMetadata().getByName(this.name);
},
toString: function () {
return this.name;
},
/**
*
* @param index
* The model index in the dataset
* @returns {Boolean} true if index passes filter
*/
accept: function (index) {
return this.vector.getValue(index) != null;
},
};
phantasus.RangeFilter = function (min, max, name, isColumns) {
this.min = min;
this.max = max;
this.name = name;
this.columns = isColumns;
};
phantasus.RangeFilter.prototype = {
enabled: true,
isColumns: function () {
return this.columns;
},
isEnabled: function () {
return this.enabled && (!isNaN(this.min) || !isNaN(this.max))
&& this.vector;
},
setEnabled: function (enabled) {
this.enabled = enabled;
},
setMin: function (value) {
this.min = isNaN(value) ? -Number.MAX_VALUE : value;
},
setMax: function (value) {
this.max = isNaN(value) ? Number.MAX_VALUE : value;
},
equals: function (filter) {
return filter instanceof phantasus.RangeFilter
&& this.name === filter.name;
},
init: function (dataset) {
this.vector = dataset.getRowMetadata().getByName(this.name);
},
toString: function () {
return this.name;
},
/**
*
* @param index
* The model index in the dataset
* @returns {Boolean} true if index passes filter
*/
accept: function (index) {
var value = this.vector.getValue(index);
return value >= this.min && value <= this.max;
},
};
phantasus.TopNFilter = function (n, direction, name, isColumns) {
this.n = n;
this.direction = direction;
this.name = name;
this.columns = isColumns;
};
phantasus.TopNFilter.TOP = 0;
phantasus.TopNFilter.BOTTOM = 1;
phantasus.TopNFilter.TOP_BOTTOM = 2;
phantasus.TopNFilter.prototype = {
enabled: true,
isColumns: function () {
return this.columns;
},
isEnabled: function () {
return this.enabled && this.n > 0 && this.vector;
},
setEnabled: function (enabled) {
this.enabled = enabled;
},
setN: function (value) {
this.n = value;
},
/**
*
* @param direction
* one of '
*/
setDirection: function (direction) {
this.direction = direction;
},
equals: function (filter) {
return filter instanceof phantasus.TopNFilter
&& this.name === filter.name && this.n === filter.n
&& this.direction === filter.direction;
},
init: function (dataset) {
if (!this.vector ||
this.vector !== dataset.getRowMetadata().getByName(this.name)) {
var vector = dataset.getRowMetadata().getByName(this.name);
if (vector == null) {
vector = {
getValue: function () {
},
size: function () {
return 0;
},
};
}
this.vector = vector;
// Get exactly N top genes, not values
// var set = new phantasus.Set();
// for (var i = 0, size = vector.size(); i < size; i++) {
// var value = vector.getValue(i);
// if (!isNaN(value)) {
// set.add(value);
// }
// }
var values = phantasus.VectorUtil.toArray(this.vector);
// ascending order
values.sort(function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
this.sortedValues = values;
}
var topAndBottomIndices = [
(this.sortedValues.length - this.n),
(this.n - 1)];
for (var i = 0; i < topAndBottomIndices.length; i++) {
topAndBottomIndices[i] = Math.max(0, topAndBottomIndices[i]);
topAndBottomIndices[i] = Math.min(this.sortedValues.length - 1,
topAndBottomIndices[i]);
}
var topAndBottomValues = [
this.sortedValues[topAndBottomIndices[0]],
this.sortedValues[topAndBottomIndices[1]]];
if (this.direction === phantasus.TopNFilter.TOP) {
this.f = function (val) {
return isNaN(val) ? false : val >= topAndBottomValues[0];
};
} else if (this.direction === phantasus.TopNFilter.BOTTOM) {
this.f = function (val) {
return isNaN(val) ? false : val <= topAndBottomValues[1];
};
} else {
this.f = function (val) {
return isNaN(val) ? false
: (val >= topAndBottomValues[0] || val <= topAndBottomValues[1]);
};
}
},
/**
*
* @param index
* The model index in the dataset
* @returns {Boolean} true if index passes filter
*/
accept: function (index) {
return this.f(this.vector.getValue(index));
},
toString: function () {
return this.name;
},
};
phantasus.AlwaysTrueFilter = function () {
};
phantasus.AlwaysTrueFilter.prototype = {
isEnabled: function () {
return false;
},
setEnabled: function (enabled) {
},
equals: function (filter) {
return filter instanceof phantasus.AlwaysTrueFilter;
},
init: function (dataset) {
},
toString: function () {
return 'AlwaysTrue';
},
/**
*
* @param index
* The model index in the dataset
* @returns {Boolean} true if index passes filter
*/
accept: function (index) {
return true;
},
};
phantasus.CombinedFilter.fromJSON = function (combinedFilter, json) {
combinedFilter.setAnd(json.isAnd);
json.filters.forEach(function (filter) {
var name = filter.name != null ? filter.name : filter.field;
if (filter.type === 'set') {
var set = new phantasus.Set();
filter.values.forEach(function (value) {
set.add(value);
});
combinedFilter.add(new phantasus.VectorFilter(
set,
filter.maxSetSize,
name,
filter.isColumns
));
} else if (filter.type === 'range') {
combinedFilter.add(new phantasus.RangeFilter(
filter.min,
filter.max,
name,
filter.isColumns
));
} else if (filter.type === 'top') {
if (_.isString(filter.direction)) {
if (filter.direction === 'top') {
filter.direction = phantasus.TopNFilter.TOP;
} else if (filter.direction === 'bottom') {
filter.direction = phantasus.TopNFilter.BOTTOM;
} else if (filter.direction === 'topAndBottom') {
filter.direction = phantasus.TopNFilter.TOP_BOTTOM;
}
}
combinedFilter.add(new phantasus.TopNFilter(
filter.n,
filter.direction,
name,
filter.isColumns
));
} else if (filter.type === 'index') {
var set = new phantasus.Set();
filter.indices.forEach(function (value) {
set.add(value);
});
combinedFilter.add(new phantasus.IndexFilter(
set,
name,
filter.isColumns
));
} else {
// console.log('Unknown filter type');
}
});
};
phantasus.CombinedFilter.toJSON = function (filter) {
var json = {
isAnd: filter.isAnd(),
filters: [],
};
filter.getFilters().forEach(function (filter) {
if (filter.isEnabled()) {
if (filter instanceof phantasus.VectorFilter) {
// phantasus.VectorFilter = function (set, maxSetSize, name, isColumns)
json.filters.push({
name: filter.name,
isColumns: filter.isColumns(),
values: filter.set.values(),
maxSetSize: filter.maxSetSize,
type: 'set',
});
} else if (filter instanceof phantasus.RangeFilter) {
// phantasus.RangeFilter = function (min, max, name, isColumns)
json.filters.push({
name: filter.name,
isColumns: filter.isColumns(),
min: filter.min,
max: filter.max,
type: 'range',
});
} else if (filter instanceof phantasus.TopNFilter) {
// phantasus.TopNFilter = function (n, direction, name, isColumns)
json.filters.push({
name: filter.name,
isColumns: filter.isColumns(),
n: filter.n,
direction: filter.direction,
type: 'top',
});
} else if (filter instanceof phantasus.IndexFilter) {
// phantasus.IndexFilter = function (acceptIndicesSet, name, isColumns
json.filters.push({
name: filter.name,
isColumns: filter.isColumns(),
indices: filter.acceptIndicesSet.values(),
type: 'index',
});
}
}
});
return json;
};
phantasus.IndexMapper = function (project, isRows) {
this.project = project;
this.isRows = isRows;
this.sortKeys = [];
/**
* {phantasus.Map} Maps from model index to view index. Note that not all
* model indices are contained in the map because they might have been
* filtered from the view.
*/
this.modelToView = null;
/** {Array} filtered model indices */
this.filteredModelIndices = null;
/** {Array} sorted and filtered model indices */
this.filteredSortedModelIndices = null;
this.filter = new phantasus.CombinedFilter(true);
this._filter();
this._sort();
};
phantasus.IndexMapper.prototype = {
convertModelIndexToView: function (modelIndex) {
var index = this.modelToView.get(modelIndex);
return index !== undefined ? index : -1;
},
convertViewIndexToModel: function (viewIndex) {
return (viewIndex < this.filteredSortedModelIndices.length
&& viewIndex >= 0 ? this.filteredSortedModelIndices[viewIndex]
: -1);
},
convertToView: function () {
return this.filteredSortedModelIndices;
},
setFilter: function (filter) {
this.filter = filter;
this._filter();
this._sort();
},
_filter: function () {
var filter = this.filter;
var dataset = this.project.getFullDataset();
var count = this.isRows ? dataset.getRowCount() : dataset.getColumnCount();
var filteredModelIndices;
if (filter != null) {
filter.init(dataset); // filter needs to transpose if columns
if (filter.isEnabled()) {
filteredModelIndices = [];
for (var i = 0; i < count; i++) {
if (filter.accept(i)) {
filteredModelIndices.push(i);
}
}
}
}
this.filteredModelIndices = filteredModelIndices != null ? filteredModelIndices
: phantasus.Util.seq(count);
},
_sort: function () {
var sortKeys = this.sortKeys;
if (sortKeys.length > 0) {
var dataset = this.project.getFullDataset();
var nkeys = sortKeys.length;
for (var i = 0; i < nkeys; i++) {
sortKeys[i].init(sortKeys[i].isColumns() ? new phantasus.TransposedDatasetView(dataset) : dataset, this.filteredSortedModelIndices);
}
this.filteredSortedModelIndices = this.filteredModelIndices
.slice(0);
this.filteredSortedModelIndices.sort(function (a, b) {
for (var i = 0; i < nkeys; i++) {
var key = sortKeys[i];
var comparator = key.getComparator();
var val1 = key.getValue(a);
var val2 = key.getValue(b);
var c = comparator(val1, val2);
if (c !== 0) {
return c;
}
}
return 0;
});
} else {
this.filteredSortedModelIndices = this.filteredModelIndices;
}
var modelToView = new phantasus.Map();
for (var i = 0, length = this.filteredSortedModelIndices.length; i < length; i++) {
modelToView.set(this.filteredSortedModelIndices[i], i);
}
this.modelToView = modelToView;
},
getFilter: function () {
return this.filter;
},
getViewCount: function () {
if (this.project.getFullDataset() == null) {
return 0;
}
return this.filteredSortedModelIndices.length;
},
setSelectedModelIndices: function (selectedModelIndices) {
this.selectionModel.setSelectedModelIndices(selectedModelIndices);
},
setSortKeys: function (sortKeys) {
if (sortKeys == null) {
sortKeys = [];
}
this.sortKeys = sortKeys;
this._sort();
}
};
/**
* Adds rows in dataset2 to dataset1
*/
phantasus.JoinedDataset = function (dataset1, dataset2, dataset1Field,
dataset2Field, sourceFieldName) {
sourceFieldName = sourceFieldName || 'Source';
this.dataset1Field = dataset1Field;
this.dataset2Field = dataset2Field;
if (dataset1 == null) {
throw 'dataset1 is null';
}
if (dataset2 == null) {
throw 'dataset2 is null';
}
if (dataset1Field) { // reorder dataset 2 to match dataset 1
var v1 = dataset1.getColumnMetadata().getByName(dataset1Field);
var dataset2ValueToIndex = phantasus.VectorUtil
.createValueToIndexMap(dataset2.getColumnMetadata().getByName(
dataset2Field));
var dataset2ColumnIndices = [];
for (var i = 0; i < v1.size(); i++) {
dataset2ColumnIndices[i] = dataset2ValueToIndex.get(v1.getValue(i));
// undefined indices are handles in SlicedDatasetWithNulls
}
dataset2 = new phantasus.SlicedDatasetWithNulls(dataset2,
dataset2ColumnIndices, dataset1.getColumnCount(), dataset1
.getColumnMetadata());
}
if (!dataset1.getRowMetadata().getByName(sourceFieldName)) {
var sourceVector = dataset1.getRowMetadata().add(sourceFieldName);
var name = dataset1.getName();
for (var i = 0, nrows = sourceVector.size(); i < nrows; i++) {
sourceVector.setValue(i, name);
}
}
if (!dataset2.getRowMetadata().getByName(sourceFieldName)) {
var sourceVector = dataset2.getRowMetadata().add(sourceFieldName);
var name = dataset2.getName();
for (var i = 0, nrows = sourceVector.size(); i < nrows; i++) {
sourceVector.setValue(i, name);
}
}
// make sure dataset1 and dataset2 have the same row metadata fields in the
// same order
for (var i = 0, count = dataset1.getRowMetadata().getMetadataCount(); i < count; i++) {
var name = dataset1.getRowMetadata().get(i).getName();
if (dataset2.getRowMetadata().getByName(name) == null) {
dataset2.getRowMetadata().add(name);
}
}
for (var i = 0, count = dataset2.getRowMetadata().getMetadataCount(); i < count; i++) {
var name = dataset2.getRowMetadata().get(i).getName();
if (dataset1.getRowMetadata().getByName(name) == null) {
dataset1.getRowMetadata().add(name);
}
}
// put dataset2 row metadata names in same order as dataset1
var dataset2RowMetadataOrder = [];
var metadataInDifferentOrder = false;
for (var i = 0, count = dataset1.getRowMetadata().getMetadataCount(); i < count; i++) {
var name = dataset1.getRowMetadata().get(i).getName();
var index = phantasus.MetadataUtil.indexOf(dataset2.getRowMetadata(),
name);
dataset2RowMetadataOrder.push(index);
if (index !== i) {
metadataInDifferentOrder = true;
}
}
this.dataset1 = dataset1;
this.dataset2 = dataset2;
// TODO put series in same order
var maxSeriesCount = Math.max(this.dataset1.getSeriesCount(), this.dataset2
.getSeriesCount());
for (var i = this.dataset1.getSeriesCount(); i < maxSeriesCount; i++) {
this.dataset1.addSeries({
name: this.dataset2.getName(i)
});
}
for (var i = this.dataset2.getSeriesCount(); i < maxSeriesCount; i++) {
this.dataset2.addSeries({
name: this.dataset1.getName(i)
});
}
this.rowMetadata = new phantasus.JoinedMetadataModel(this.dataset1
.getRowMetadata(), !metadataInDifferentOrder ? this.dataset2
.getRowMetadata() : new phantasus.MetadataModelColumnView(
this.dataset2.getRowMetadata(), dataset2RowMetadataOrder));
};
phantasus.JoinedDataset.prototype = {
getName: function (seriesIndex) {
return this.dataset1.getName(seriesIndex);
},
setName: function (seriesIndex, name) {
this.dataset1.setName(seriesIndex, name);
},
getDataType: function (seriesIndex) {
return this.dataset1.getDataType(seriesIndex);
},
getDatasets: function () {
return [this.dataset1, this.dataset2];
},
getDataset1: function () {
return this.dataset1;
},
getRowMetadata: function () {
return this.rowMetadata;
},
getColumnMetadata: function () {
return this.dataset1.getColumnMetadata();
},
getRowCount: function () {
return this.dataset1.getRowCount() + this.dataset2.getRowCount();
},
getColumnCount: function () {
return this.dataset1.getColumnCount();
},
getValue: function (i, j, seriesIndex) {
return i < this.dataset1.getRowCount() ? this.dataset1.getValue(i, j,
seriesIndex) : this.dataset2.getValue(i
- this.dataset1.getRowCount(), j, seriesIndex);
},
setValue: function (i, j, value, seriesIndex) {
i < this.dataset1.getRowCount() ? this.dataset1.setValue(i, j, value,
seriesIndex) : this.dataset2.setValue(i
- this.dataset1.getRowCount(), j, value, seriesIndex);
},
getSeriesCount: function () {
return this.dataset1.getSeriesCount();
},
addSeries: function (options) {
this.dataset1.addSeries(options);
return this.dataset2.addSeries(options);
},
removeSeries: function (seriesIndex) {
this.dataset1.removeSeries(seriesIndex);
this.dataset2.removeSeries(seriesIndex);
},
toString: function () {
return this.getName();
}
};
phantasus.SlicedDatasetWithNulls = function (dataset, columnIndices, columnCount,
columnMetadata) {
phantasus.DatasetAdapter.call(this, dataset);
this.columnIndices = columnIndices;
this.columnCount = columnCount;
this.columnMetadata = columnMetadata;
};
phantasus.SlicedDatasetWithNulls.prototype = {
getColumnMetadata: function () {
return this.columnMetadata;
},
getColumnCount: function () {
return this.columnCount;
},
getValue: function (i, j, seriesIndex) {
var index = this.columnIndices[j];
return index === undefined ? undefined : this.dataset.getValue(i,
index, seriesIndex);
},
setValue: function (i, j, value, seriesIndex) {
var index = this.columnIndices[j];
if (index !== undefined) {
this.dataset.setValue(i, index, value, seriesIndex);
} else {
// console.log(j + ' out of range');
}
}
};
phantasus.Util.extend(phantasus.SlicedDatasetWithNulls, phantasus.DatasetAdapter);
phantasus.JoinedVector = function (v1, v2) {
this.v1 = v1;
this.v2 = v2;
phantasus.VectorAdapter.call(this, v1);
this.properties = new phantasus.Map();
this.levels = v1.getFactorLevels().concat(v2.getFactorLevels());
};
phantasus.JoinedVector.prototype = {
setValue: function (i, value) {
i < this.v1.size() ? this.v1.setValue(i, value) : this.v2.setValue(i
- this.v1.size(), value);
},
getValue: function (i) {
return i < this.v1.size() ? this.v1.getValue(i) : this.v2.getValue(i
- this.v1.size());
},
size: function () {
return this.v1.size() + this.v2.size();
},
getProperties: function () {
return this.properties;
},
factorize: function (levels) {
if (!levels || _.size(levels) === 0 || !_.isArray(levels)) {
return this.defactorize();
}
if (this.isFactorized()) {
this.defactorize();
}
var uniqueValuesInVector = _.uniq(phantasus.VectorUtil.getSet(this).values());
var allLevelsArePresent = levels.every(function (value) {
return _.indexOf(uniqueValuesInVector, value) !== -1; // all levels are present in current array
}) && uniqueValuesInVector.every(function (value) {
return _.indexOf(levels, value) !== -1; // all current values present in levels
});
if (!allLevelsArePresent) {
throw Error('Cannot factorize vector. Invalid levels');
}
this.levels = levels;
},
defactorize: function () {
if (!this.isFactorized()) {
return;
}
this.levels = null;
},
isFactorized: function () {
return _.size(this.levels) > 0;
},
getFactorLevels: function () {
return this.levels;
}
};
phantasus.Util.extend(phantasus.JoinedVector, phantasus.VectorAdapter);
phantasus.JoinedMetadataModel = function (m1, m2) {
this.m1 = m1;
this.m2 = m2;
this.vectors = [];
for (var i = 0, count = m1.getMetadataCount(); i < count; i++) {
var v1 = this.m1.get(i);
var v2 = this.m2.get(i);
var v = new phantasus.JoinedVector(v1, v2);
// copy properties
v1.getProperties().forEach(function (val, key) {
if (!phantasus.VectorKeys.COPY_IGNORE.has(key)) {
v.properties.set(key, val);
}
});
v2.getProperties().forEach(function (val, key) {
if (!phantasus.VectorKeys.COPY_IGNORE.has(key)) {
v.properties.set(key, val);
}
});
this.vectors.push(v);
}
};
phantasus.JoinedMetadataModel.prototype = {
add: function (name) {
var index = phantasus.MetadataUtil.indexOf(this, name);
var oldVector;
if (index !== -1) {
oldVector = this.remove(index);
}
var v = new phantasus.Vector(name, this.getItemCount());
if (oldVector != null) {
// copy properties
oldVector.getProperties().forEach(function (val, key) {
v.getProperties().set(key, val);
});
// copy values
for (var i = 0, size = oldVector.size(); i < size; i++) {
v.setValue(i, oldVector.getValue(i));
}
}
this.vectors.push(v);
return v;
},
getItemCount: function () {
return this.m1.getItemCount() + this.m2.getItemCount();
},
get: function (index) {
return this.vectors[index];
},
remove: function (index) {
return this.vectors.splice(index, 1)[0];
},
getByName: function (name) {
for (var i = 0, length = this.vectors.length; i < length; i++) {
if (name === this.vectors[i].getName()) {
return this.vectors[i];
}
}
},
getMetadataCount: function () {
return this.vectors.length;
}
};
/**
*
*@implements {phantasus.MetadataModelInterface}
*/
phantasus.MetadataModelAdapter = function (model) {
this.model = model;
};
phantasus.MetadataModelAdapter.prototype = {
add: function (name) {
return this.model.add(name);
},
getItemCount: function () {
return this.model.getItemCount();
},
get: function (index) {
return this.model.get(index);
},
remove: function (index) {
return this.model.remove(index);
},
getByName: function (name) {
return this.model.getByName(name);
},
getMetadataCount: function () {
return this.model.getMetadataCount();
}
};
phantasus.MetadataModelColumnView = function (model, indices) {
this.model = model;
this.indices = indices;
};
phantasus.MetadataModelColumnView.prototype = {
add: function (name) {
var vector = this.model.add(name);
var index = phantasus.MetadataUtil.indexOf(this.model, name);
this.indices.push(index);
return vector;
},
getMetadataCount: function () {
return this.indices.length;
},
get: function (index) {
if (index < 0 || index >= this.indices.length) {
throw 'index out of bounds';
}
return this.model.get(this.indices[index]);
},
remove: function (index) {
if (index < 0 || index >= this.indices.length) {
throw 'index out of bounds';
}
var v = this.model.remove(this.indices[index]);
this.indices.splice(index, 1);
return v;
},
getByName: function (name) {
var index = phantasus.MetadataUtil.indexOf(this, name);
return index !== -1 ? this.get(index) : undefined;
}
};
phantasus.Util.extend(phantasus.MetadataModelColumnView,
phantasus.MetadataModelAdapter);
/**
* Stores annotations for the rows or columns of a dataset.
* @interface phantasus.MetadataModelInterface
*
*/
/**
* Appends the specified vector to this meta data. If an existing vector
* with the same name already exists, it is removed and existing properties
* and values copied to the new vector before appending the new vector.
* @function
* @name phantasus.MetadataModelInterface#add
* @param name {String} The vector name to be inserted into this meta data instance.
* @param options {object}
* @return {phantasus.VectorInterface} the added vector.
*/
/**
* Returns the number of items that a vector in this meta data model
* contains.
*
* @function
* @name phantasus.MetadataModelInterface#getItemCount
* @return {number} the item count
*/
/**
* Returns the vector at the specified metadata index.
*
* @function
* @name phantasus.MetadataModelInterface#get
* @param index {number} the metadata index
* @return {phantasus.VectorInterface} the vector
*/
/**
* Removes the column at the specified position in this meta data instance
* Shifts any subsequent columns to the left (subtracts one from their
* indices).
*
* @function
* @name phantasus.MetadataModelInterface#remove
* @param index {number} the meta data index to remove.
* @return {phantasus.VectorInterface} the removed vector
* @throws Error if index < 0 or >= getMetadataCount
*/
/**
* Returns the vector witht the specified name.
*
* @function
* @name phantasus.MetadataModelInterface#getByName
* @param name {string} the vector name
* @return {phantasus.VectorInterface} the vector
*/
/**
* Returns the number of vectors in this meta data instance.
*
* @function
* @name phantasus.MetadataModelInterface#getMetadataCount
* @return {number} the number of vectors.
*/
phantasus.MetadataModelItemView = function (model, indices) {
this.model = model;
this.indices = indices;
};
phantasus.MetadataModelItemView.prototype = {
add: function (name) {
var v = this.model.add(name);
return new phantasus.SlicedVector(v, this.indices);
},
getItemCount: function () {
return this.indices.length;
},
get: function (index) {
var v = this.model.get(index);
if (v === undefined) {
return undefined;
}
return new phantasus.SlicedVector(v, this.indices);
},
getByName: function (name) {
var v = this.model.getByName(name);
if (v === undefined) {
return undefined;
}
return new phantasus.SlicedVector(v, this.indices);
},
getMetadataCount: function () {
return this.model.getMetadataCount();
}
};
phantasus.Util.extend(phantasus.MetadataModelItemView,
phantasus.MetadataModelAdapter);
/**
* Creates a new meta data model instance.
*
* @param itemCount {number}
* the number of items that vectors in this instances will hold.
* @implements {phantasus.MetadataModelInterface}
* @constructor
*/
phantasus.MetadataModel = function (itemCount) {
this.itemCount = itemCount;
this.vectors = [];
};
phantasus.MetadataModel.prototype = {
add: function (name, options) {
var index = phantasus.MetadataUtil.indexOf(this, name);
var oldVector;
if (index !== -1) {
oldVector = this.get(index);
}
var v = new phantasus.Vector(name, this.getItemCount());
if (oldVector != null) {
// copy values
for (var i = 0, size = oldVector.size(); i < size; i++) {
var val = oldVector.getValue(i);
v.setValue(i, val);
}
}
if (index !== -1) {
// replace old vector
this.vectors.splice(index, 1, v);
} else {
this.vectors.push(v);
}
return v;
},
getItemCount: function () {
return this.itemCount;
},
get: function (index) {
return this.vectors[index];
},
remove: function (index) {
return this.vectors.splice(index, 1)[0];
},
getByName: function (name) {
var index = phantasus.MetadataUtil.indexOf(this, name);
return index !== -1 ? this.get(index) : undefined;
},
getMetadataCount: function () {
return this.vectors.length;
}
};
phantasus.MetadataUtil = function () {
};
phantasus.MetadataUtil.renameFields = function (dataset, options) {
_.each(options.rows, function (item) {
if (item.renameTo) {
var v = dataset.getRowMetadata().getByName(item.field);
if (v) {
v.setName(item.renameTo);
}
}
});
_.each(options.columns, function (item) {
if (item.renameTo) {
var v = dataset.getColumnMetadata().getByName(item.field);
if (v) {
v.setName(item.renameTo);
}
}
});
};
/**
* @param options.model
* Metadata model of currently visible tracks
* @param options.fullModel
* Metadata model of all metadata tracks
* @param options.text
* Search text
* @param options.isColumns
* Whether to search columns
* @param options.defaultMatchMode
* 'exact' or 'contains'
* @param options.matchAllPredicates Whether to match all predicates
*
*/
phantasus.MetadataUtil.search = function (options) {
var model = options.model;
var fullModel = options.fullModel;
if (!fullModel) {
fullModel = model;
}
var text = options.text;
var isColumns = options.isColumns;
text = $.trim(text);
if (text === '') {
return null;
}
var tokens = phantasus.Util.getAutocompleteTokens(text);
if (tokens.length == 0) {
return null;
}
var indexField = '#';
var fieldNames = phantasus.MetadataUtil.getMetadataNames(fullModel);
fieldNames.push(indexField);
var predicates = phantasus.Util.createSearchPredicates({
tokens: tokens,
fields: fieldNames,
defaultMatchMode: options.defaultMatchMode
});
var vectors = [];
var nameToVector = new phantasus.Map();
for (var j = 0; j < fullModel.getMetadataCount(); j++) {
var v = fullModel.get(j);
var dataType = phantasus.VectorUtil.getDataType(v);
var wrapper = {
vector: v,
dataType: dataType,
isArray: dataType.indexOf('[') === 0
};
nameToVector.set(v.getName(), wrapper);
if (model.getByName(v.getName()) != null) {
vectors.push(wrapper);
}
}
// TODO only search numeric fields for range searches
var indices = [];
var npredicates = predicates.length;
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
var filterColumnName = predicate.getField();
if (filterColumnName != null && !predicate.isNumber()) {
var wrapper = nameToVector.get(filterColumnName);
if (wrapper && (wrapper.dataType === 'number' || wrapper.dataType === '[number]')) {
if (predicate.getText) {
predicates[p] = new phantasus.Util.EqualsPredicate(filterColumnName, parseFloat(predicate.getText()));
} else if (predicate.getValues) {
var values = [];
predicate.getValues().forEach(function (val) {
values.push(parseFloat(val));
});
predicate[p] = new phantasus.Util.ExactTermsPredicate(filterColumnName, values);
}
}
}
}
var matchAllPredicates = options.matchAllPredicates === true;
function isPredicateMatch(predicate) {
var filterColumnName = predicate.getField();
if (filterColumnName != null) {
var value = null;
if (filterColumnName === indexField) {
value = i + 1;
if (predicate.accept(value)) {
return true;
}
} else {
var wrapper = nameToVector.get(filterColumnName);
if (wrapper) {
value = wrapper.vector.getValue(i);
if (wrapper.isArray) {
if (value != null) {
for (var k = 0; k < value.length; k++) {
if (predicate.accept(value[k])) {
return true;
}
}
}
} else {
if (predicate.accept(value)) {
return true;
}
}
}
}
}
else { // try all fields
for (var j = 0; j < nfields; j++) {
var wrapper = vectors[j];
var value = wrapper.vector.getValue(i);
if (wrapper.isArray) {
if (value != null) {
for (var k = 0; k < value.length; k++) {
if (predicate.accept(value[k])) {
return true;
}
}
}
} else {
if (predicate.accept(value)) {
return true;
}
}
}
}
}
var nfields = vectors.length;
for (var i = 0, nitems = model.getItemCount(); i < nitems; i++) {
if (!matchAllPredicates) { // at least one predicate matches
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
if (isPredicateMatch(predicate)) {
indices.push(i);
break;
}
}
} else {
var matches = true;
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
if (!isPredicateMatch(predicate)) {
matches = false;
break;
}
}
if (matches) {
indices.push(i);
}
}
}
return indices;
};
phantasus.MetadataUtil.shallowCopy = function (model) {
var copy = new phantasus.MetadataModel(model.getItemCount());
for (var i = 0; i < model.getMetadataCount(); i++) {
var v = model.get(i);
// copy properties b/c they can be modified via ui
var newVector = new phantasus.VectorAdapter(v);
newVector.properties = new phantasus.Map();
newVector.getProperties = function () {
return this.properties;
};
v.getProperties().forEach(function (val, key) {
if (!phantasus.VectorKeys.COPY_IGNORE.has(key)) {
newVector.properties.set(key, val);
}
});
copy.vectors.push(newVector);
}
return copy;
};
phantasus.MetadataUtil.autocomplete = function (model) {
return function (tokens, cb) {
// check for term:searchText
var matches = [];
var regex = null;
var regexMatch = null;
var searchModel = model;
var token = tokens != null && tokens.length > 0 ? tokens[tokens.selectionStartIndex]
: '';
token = $.trim(token);
var fieldSearchFieldName = null;
if (token !== '') {
var semi = token.indexOf(':');
if (semi > 0) { // field search?
if (token.charCodeAt(semi - 1) !== 92) { // \:
var possibleField = $.trim(token.substring(0, semi));
if (possibleField.length > 0
&& possibleField[0] === '"'
&& possibleField[token.length - 1] === '"') {
possibleField = possibleField.substring(1,
possibleField.length - 1);
}
var index = phantasus.MetadataUtil.indexOf(searchModel,
possibleField);
if (index !== -1) {
fieldSearchFieldName = possibleField;
token = $.trim(token.substring(semi + 1));
searchModel = new phantasus.MetadataModelColumnView(
model, [index]);
}
}
}
var set = new phantasus.Set();
// regex used to determine if a string starts with substring `q`
regex = new RegExp(phantasus.Util.escapeRegex(token), 'i');
regexMatch = new RegExp('(' + phantasus.Util.escapeRegex(token) + ')', 'i');
// iterate through the pool of strings and for any string that
// contains the substring `q`, add it to the `matches` array
var max = 10;
var vectors = [];
var isArray = [];
for (var j = 0; j < searchModel.getMetadataCount(); j++) {
var v = searchModel.get(j);
var dataType = phantasus.VectorUtil.getDataType(v);
if (dataType === 'string' || dataType === '[string]') { // skip
// numeric
// fields
vectors.push(v);
isArray.push(dataType === '[string]');
}
}
var nfields = vectors.length;
loop: for (var i = 0, nitems = searchModel.getItemCount(); i < nitems; i++) {
for (var j = 0; j < nfields; j++) {
var v = vectors[j];
var val = v.getValue(i);
if (val != null) {
if (isArray[j]) {
for (var k = 0; k < val.length; k++) {
var id = new phantasus.Identifier([val[k],
v.getName()]);
if (!set.has(id) && regex.test(val[k])) {
set.add(id);
if (set.size() === max) {
break loop;
}
}
}
} else {
var id = new phantasus.Identifier([val,
v.getName()]);
if (!set.has(id) && regex.test(val)) {
set.add(id);
if (set.size() === max) {
break loop;
}
}
}
}
}
}
set.forEach(function (id) {
var array = id.getArray();
var field = array[1];
var val = array[0];
var quotedField = field;
if (quotedField.indexOf(' ') !== -1) {
quotedField = '"' + quotedField + '"';
}
var quotedValue = val;
if (quotedValue.indexOf(' ') !== -1) {
quotedValue = '"' + quotedValue + '"';
}
matches.push({
value: quotedField + ':' + quotedValue,
label: '<span style="font-weight:300;">' + field
+ ':</span>'
+ '<span>' + val.replace(regexMatch, '<b>$1</b>')
+ '</span>'
});
});
}
// field names
if (regex == null) {
regex = new RegExp('.*', 'i');
}
for (var j = 0; j < searchModel.getMetadataCount(); j++) {
var v = searchModel.get(j);
var dataType = phantasus.VectorUtil.getDataType(v);
var field = v.getName();
if (dataType === 'number' || dataType === 'string'
|| dataType === '[string]') {
if (regex.test(field) && field !== fieldSearchFieldName) {
var quotedField = field;
if (quotedField.indexOf(' ') !== -1) {
quotedField = '"' + quotedField + '"';
}
matches.push({
value: quotedField + ':',
label: '<span style="font-weight:300;">' + (regexMatch == null ? field : field.replace(regexMatch, '<b>$1</b>'))
+ ':</span>' + (dataType === 'number' ? ('<span' +
' style="font-weight:300;font-size:85%;">.., >, <, >=, <=,' +
' =</span>') : ''),
show: true
});
}
}
}
cb(matches);
};
};
phantasus.MetadataUtil.getMetadataNames = function (metadataModel, unsorted) {
var names = [];
for (var i = 0, count = metadataModel.getMetadataCount(); i < count; i++) {
names.push(metadataModel.get(i).getName(i));
}
if (!unsorted) {
names.sort(function (a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return (a < b ? -1 : (a === b ? 0 : 1));
});
}
return names;
};
phantasus.MetadataUtil.getMetadataSignedNumericFields = function (metadataModel) {
var fields = [];
for (var i = 0, count = metadataModel.getMetadataCount(); i < count; i++) {
var field = metadataModel.get(i);
var properties = field.getProperties();
if (properties.get('phantasus.dataType') === 'number') {
var hasPositive = false;
var hasNegative = false;
for (var j = 0; j < field.size(); j++) {
if (field.getValue(j) > 0) {
hasPositive = true;
}
if (field.getValue(j) < 0) {
hasNegative = true;
}
if (hasPositive && hasNegative) {
break;
}
}
if (hasPositive && hasNegative) {
fields.push(field);
}
}
}
return fields;
};
phantasus.MetadataUtil.getVectors = function (metadataModel, names) {
var vectors = [];
names.forEach(function (name) {
var v = metadataModel.getByName(name);
if (!v) {
throw name + ' not found. Available fields are '
+ phantasus.MetadataUtil.getMetadataNames(metadataModel);
}
vectors.push(v);
});
return vectors;
};
phantasus.MetadataUtil.indexOf = function (metadataModel, name) {
for (var i = 0, length = metadataModel.getMetadataCount(); i < length; i++) {
if (name === metadataModel.get(i).getName()) {
return i;
}
}
return -1;
};
phantasus.MetadataUtil.DEFAULT_STRING_ARRAY_FIELDS = ['target', 'gene_target', 'moa'];
phantasus.MetadataUtil.DEFAULT_HIDDEN_FIELDS = new phantasus.Set();
['pr_analyte_id', 'pr_gene_title', 'pr_gene_id', 'pr_analyte_num',
'pr_bset_id', 'pr_lua_id', 'pr_pool_id', 'pr_is_bing', 'pr_is_inf',
'pr_is_lmark', 'qc_slope', 'qc_f_logp', 'qc_iqr', 'bead_batch',
'bead_revision', 'bead_set', 'det_mode', 'det_plate', 'det_well',
'mfc_plate_dim', 'mfc_plate_id', 'mfc_plate_name', 'mfc_plate_quad',
'mfc_plate_well', 'pert_dose_unit', 'pert_id_vendor', 'pert_mfc_desc',
'pert_mfc_id', 'pert_time', 'pert_time_unit', 'pert_univ_id',
'pert_vehicle', 'pool_id', 'rna_plate', 'rna_well', 'count_mean',
'count_cv', 'provenance_code'].forEach(function (name) {
phantasus.MetadataUtil.DEFAULT_HIDDEN_FIELDS.add(name);
});
phantasus.MetadataUtil.maybeConvertStrings = function (metadata,
metadataStartIndex) {
for (var i = metadataStartIndex, count = metadata.getMetadataCount(); i < count; i++) {
phantasus.VectorUtil.maybeConvertStringToNumber(metadata.get(i));
}
phantasus.MetadataUtil.DEFAULT_STRING_ARRAY_FIELDS.forEach(function (field) {
if (metadata.getByName(field)) {
phantasus.VectorUtil.maybeConvertToStringArray(metadata
.getByName(field), ',');
}
});
};
phantasus.MetadataUtil.copy = function (src, dest) {
if (src.getItemCount() != dest.getItemCount()) {
throw 'Item count not equal in source and destination. '
+ src.getItemCount() + ' != ' + dest.getItemCount();
}
var itemCount = src.getItemCount();
var metadataColumns = src.getMetadataCount();
for (var j = 0; j < metadataColumns; j++) {
var srcVector = src.get(j);
var destVector = dest.getByName(srcVector.getName());
if (destVector == null) {
destVector = dest.add(srcVector.getName());
}
for (var i = 0; i < itemCount; i++) {
destVector.setValue(i, srcVector.getValue(i));
}
}
};
phantasus.MetadataUtil.addVectorIfNotExists = function (metadataModel, name) {
var v = metadataModel.getByName(name);
if (!v) {
v = metadataModel.add(name);
}
return v;
};
phantasus.MetadataUtil.getMatchingIndices = function (metadataModel, tokens) {
var indices = {};
for (var itemIndex = 0, nitems = metadataModel.getItemCount(); itemIndex < nitems; itemIndex++) {
var matches = false;
for (var metadataIndex = 0, metadataCount = metadataModel
.getMetadataCount(); metadataIndex < metadataCount && !matches; metadataIndex++) {
var vector = metadataModel.get(metadataModel
.getColumnName(metadataIndex));
var value = vector.getValue(itemIndex);
for (var i = 0, length = tokens.length; i < length; i++) {
if (tokens[i] == value) {
matches = true;
break;
}
}
}
if (matches) {
indices[itemIndex] = 1;
}
}
return indices;
};
phantasus.MolarConcentration = function () {
};
/*
*
millimolar mM 10-3 molar 10-0 mol/m3
micromolar uM 10-6 molar 10-3 mol/m3
nanomolar nM 10-9 molar 10-6 mol/m3
picomolar pM 10-12 molar 10-9 mol/m3
femtomolar fM 10-15 molar 10-12 mol/m3
attomolar aM 10-18 molar 10-15 mol/m3
zeptomolar zM 10-21 molar 10-18 mol/m3
yoctomolar yM[3] 10-24 molar 10-27 mol/m3
*/
phantasus.MolarConcentration.getMicroMolarConcentration = function (text) {
/** concentration in molar*/
text = text.toLowerCase();
for (var i = 0; i < phantasus.MolarConcentration.CONCENTRATIONS.length; i++) {
var pair = phantasus.MolarConcentration.CONCENTRATIONS[i];
var key = pair[0];
var factorToMolar = pair[1];
var index = text.indexOf(key);
if (index != -1) {
var value = text.substring(0, index).trim();
var factor = factorToMolar / 10E6;
var conc = parseFloat(value);
return conc / factor;
}
}
};
phantasus.MolarConcentration.CONCENTRATIONS = [
['mm', 10E3],
['um', 10E6],
['\u00B5' + 'm', 10E6],
['nm', 10E9],
['pm', 10E12],
['fm', 10E15],
['am', 10E18],
['zm', 10E21],
['ym', 10E24],
['m', 1]];
phantasus.MaximumMeanProbe = function(probes) {
return phantasus.MaximumUnivariateFunction(probes, phantasus.Mean);
};
phantasus.MaximumMeanProbe.toString = function() {
return "Maximum Mean Probe";
};
phantasus.MaximumMeanProbe.rString = function() {
return "mean";
};
phantasus.MaximumMeanProbe.selectOne = true;
phantasus.MaximumMedianProbe = function(probes) {
return phantasus.MaximumUnivariateFunction(probes, phantasus.Median);
};
phantasus.MaximumMedianProbe.toString = function() {
return "Maximum Median Probe";
};
phantasus.MaximumMedianProbe.rString = function() {
return "fastMedian";
};
phantasus.MaximumMedianProbe.selectOne = true;
phantasus.MaximumUnivariateFunction = function(rowView, fun) {
// console.log("MaximumUnivariateFunction args", rowView, fun);
var curMax = Number.NEGATIVE_INFINITY;
var curIndex = -1;
for (var i = 0; i < rowView.dataset.getRowCount(); i++) {
rowView.setIndex(i);
var mean = fun(rowView);
if (mean > curMax) {
curMax = mean;
curIndex = i;
}
}
return {
value : curMax,
index : curIndex
}
};
phantasus.Positions = function () {
this.spaces = undefined;
this.defaultPositionFunction = function (index) {
return (this.size * index);
};
this.squishedPositionFunction = function (index) {
return this.positions[index];
};
this.positionFunction = this.defaultPositionFunction;
this.squishedIndices = {};
this.isSquished = false;
};
phantasus.Positions.getBottom = function (rect, rowPositions) {
var bottom = rowPositions.getLength();
if (rect != null) {
bottom = 1 + rowPositions.getIndex(rect.y + rect.height, false);
bottom = Math.max(0, bottom);
bottom = Math.min(rowPositions.getLength(), bottom);
}
return bottom;
};
phantasus.Positions.getTop = function (rect, rowPositions) {
var top = 0;
if (rect != null) {
top = rowPositions.getIndex(rect.y, false) - 1;
top = Math.max(0, top);
top = Math.min(rowPositions.getLength(), top);
}
return top;
};
phantasus.Positions.getLeft = function (rect, columnPositions) {
var left = 0;
if (rect != null) {
left = columnPositions.getIndex(rect.x, false) - 1;
left = Math.max(0, left);
left = Math.min(columnPositions.getLength(), left);
}
return left;
};
phantasus.Positions.getRight = function (rect, columnPositions) {
var right = columnPositions.getLength();
if (rect != null) {
right = 1 + columnPositions.getIndex(rect.x + rect.width, false);
right = Math.min(columnPositions.getLength(), right);
}
return right;
};
phantasus.Positions.prototype = {
length: 0,
size: 13,
squishFactor: 0.1,
compress: true,
copy: function () {
var copy = new phantasus.Positions();
if (this.spaces) {
copy.spaces = this.spaces.slice();
}
copy.compress = this.compress;
copy.squishFactor = this.squishFactor;
copy.size = this.size;
copy.length = this.length;
if (this.isSquished) {
copy.positionFunction = copy.squishedPositionFunction;
copy.squishedIndices = _.clone(this.squishedIndices);
copy.isSquished = true;
}
return copy;
},
getIndex: function (position, exact) {
if (this.getLength() === 0) {
return -1;
}
if (exact) {
return this.binaryExactSearch(position);
} else {
return this.binaryInExactSearch(position);
}
},
getLength: function () {
return this.length;
},
getPosition: function (index) {
return this.positionFunction(index)
+ (this.spaces !== undefined ? this.spaces[index] : 0);
},
getItemSize: function (index) {
return this.squishedIndices[index] === true ? this.size
* this.squishFactor : this.size;
},
getSize: function () {
return this.size;
},
setSpaces: function (spaces) {
this.spaces = spaces;
},
setLength: function (length) {
this.length = length;
this.trigger('change', {
source: this,
value: 'length'
});
},
setSize: function (size) {
this.size = size;
if (this.isSquished) {
this.setSquishedIndices(this.squishedIndices);
}
this.trigger('change', {
source: this,
value: 'size'
});
},
setSquishedIndices: function (squishedIndices) {
if (squishedIndices != null) {
var compress = this.compress;
this.squishedIndices = squishedIndices;
var positions = [];
var squishFactor = this.squishFactor;
var size = this.size;
var position = 0;
for (var i = 0, length = this.getLength(); i < length; i++) {
var itemSize;
if (squishedIndices[i] === true) {
positions.push(position);
itemSize = size * squishFactor;
position += itemSize;
} else {
if (!compress) {
position = size * i;
}
positions.push(position);
position += size;
}
}
this.isSquished = true;
this.positions = positions;
this.positionFunction = this.squishedPositionFunction;
} else {
this.squishedIndices = {};
this.isSquished = false;
this.positionFunction = this.defaultPositionFunction;
}
this.trigger('change', {
source: this,
value: 'squishedIndices'
});
},
setSquishFactor: function (f) {
if (this.squishFactor !== f) {
this.squishFactor = f;
if (this.isSquished) {
this.setSquishedIndices(this.squishedIndices);
}
this.trigger('change', {
source: this,
value: 'squishFactor'
});
}
},
getSquishFactor: function () {
return this.squishFactor;
},
binaryExactSearch: function (position) {
var low = 0;
var high = this.length - 1;
while (low <= high) {
var mid = (low + high) >> 1;
var midVal = this.getPosition(mid);
var size = this.getItemSize(mid);
if (midVal <= position && position < (midVal + size)) {
return mid;
}
if (midVal < position) {
low = mid + 1;
} else if (midVal > position) {
high = mid - 1;
} else {
return mid;
// key found
}
}
return -1;
// key not found
},
binaryInExactSearch: function (position) {
var low = 0;
var high = this.getLength() - 1;
var maxIndex = this.getLength() - 1;
if (position <= this.getPosition(0)) {
return 0;
}
while (low <= high) {
var mid = (low + high) >> 1;
var midVal = this.getPosition(mid);
var size = this.getItemSize(mid);
var nextStart = maxIndex === mid ? midVal + size : this
.getPosition(mid + 1);
if (midVal <= position && position < nextStart) {
return mid;
}
if (midVal < position) {
low = mid + 1;
} else if (midVal > position) {
high = mid - 1;
} else {
return mid;
// key found
}
}
return low - 1;
// key not found
}
};
phantasus.Util.extend(phantasus.Positions, phantasus.Events);
/**
*
* @param dataset
* @constructor
*/
phantasus.Project = function (dataset) {
this.originalDataset = dataset;
this.rowIndexMapper = new phantasus.IndexMapper(this, true);
this.columnIndexMapper = new phantasus.IndexMapper(this, false);
this.groupRows = [];
this.groupColumns = [];
this.rowColorModel = new phantasus.VectorColorModel();
this.columnColorModel = new phantasus.VectorColorModel();
this.rowShapeModel = new phantasus.VectorShapeModel();
this.columnShapeModel = new phantasus.VectorShapeModel();
this.rowFontModel = new phantasus.VectorFontModel();
this.columnFontModel = new phantasus.VectorFontModel();
this.hoverColumnIndex = -1;
this.hoverRowIndex = -1;
this.columnSelectionModel = new phantasus.SelectionModel(this, true);
this.rowSelectionModel = new phantasus.SelectionModel(this, false);
this.elementSelectionModel = new phantasus.ElementSelectionModel(this);
this.symmetricProjectListener = null;
phantasus.Project._recomputeCalculatedColumnFields(this.originalDataset, phantasus.VectorKeys.RECOMPUTE_FUNCTION_NEW_HEAT_MAP);
phantasus.Project
._recomputeCalculatedColumnFields(new phantasus.TransposedDatasetView(
this.originalDataset), phantasus.VectorKeys.RECOMPUTE_FUNCTION_NEW_HEAT_MAP);
};
phantasus.Project.Events = {
DATASET_CHANGED: 'datasetChanged',
ROW_GROUP_BY_CHANGED: 'rowGroupByChanged',
COLUMN_GROUP_BY_CHANGED: 'columnGroupByChanged',
ROW_FILTER_CHANGED: 'rowFilterChanged',
COLUMN_FILTER_CHANGED: 'columnFilterChanged',
ROW_SORT_ORDER_CHANGED: 'rowSortOrderChanged',
COLUMN_SORT_ORDER_CHANGED: 'columnSortOrderChanged',
ROW_TRACK_REMOVED: 'rowTrackRemoved',
COLUMN_TRACK_REMOVED: 'columnTrackRemoved'
};
phantasus.Project._recomputeCalculatedColumnFields = function (dataset, key) {
var metadata = dataset.getColumnMetadata();
var view = new phantasus.DatasetColumnView(dataset);
var nfound = 0;
for (var metadataIndex = 0,
count = metadata.getMetadataCount(); metadataIndex < count; metadataIndex++) {
var vector = metadata.get(metadataIndex);
if (vector.getProperties().get(phantasus.VectorKeys.FUNCTION) != null
&& vector.getProperties().get(key)) {
// // copy properties
// var v = metadata.add(name);
// vector.getProperties().forEach(function (val, key) {
// v.getProperties().set(key, val);
// });
// vector = v;
var f = phantasus.VectorUtil.jsonToFunction(vector, phantasus.VectorKeys.FUNCTION);
for (var j = 0, size = vector.size(); j < size; j++) {
view.setIndex(j);
vector.setValue(j, f(view, dataset, j));
}
nfound++;
}
}
return nfound;
};
phantasus.Project.prototype = {
isSymmetric: function () {
return this.symmetricProjectListener != null;
},
setSymmetric: function (heatMap) {
if (heatMap != null) {
if (this.symmetricProjectListener == null) {
this.symmetricProjectListener = new phantasus.SymmetricProjectListener(heatMap.getProject(), heatMap.vscroll, heatMap.hscroll);
}
} else {
if (this.symmetricProjectListener != null) {
this.symmetricProjectListener.dispose();
}
this.symmetricProjectListener = null;
}
},
getHoverColumnIndex: function () {
return this.hoverColumnIndex;
},
setHoverColumnIndex: function (index) {
this.hoverColumnIndex = index;
},
getHoverRowIndex: function () {
return this.hoverRowIndex;
},
setHoverRowIndex: function (index) {
this.hoverRowIndex = index;
},
getRowColorModel: function () {
return this.rowColorModel;
},
getRowShapeModel: function () {
return this.rowShapeModel;
},
getColumnShapeModel: function () {
return this.columnShapeModel;
},
getRowFontModel: function () {
return this.rowFontModel;
},
getColumnFontModel: function () {
return this.columnFontModel;
},
getGroupRows: function () {
return this.groupRows;
},
getGroupColumns: function () {
return this.groupColumns;
},
getFullDataset: function () {
return this.originalDataset;
},
getColumnSelectionModel: function () {
return this.columnSelectionModel;
},
getRowSelectionModel: function () {
return this.rowSelectionModel;
},
getFilteredSortedRowIndices: function () {
return this.rowIndexMapper.convertToView();
},
getFilteredSortedColumnIndices: function () {
return this.columnIndexMapper.convertToView();
},
getElementSelectionModel: function () {
return this.elementSelectionModel;
},
setFullDataset: function (dataset, notify) {
this.originalDataset = dataset;
this.rowIndexMapper.setFilter(this.rowIndexMapper.getFilter());
this.columnIndexMapper.setFilter(this.columnIndexMapper.getFilter());
this.columnSelectionModel.clear();
this.rowSelectionModel.clear();
this.elementSelectionModel.clear();
if (notify) {
this.trigger(phantasus.Project.Events.DATASET_CHANGED);
}
},
setGroupRows: function (keys, notify) {
this.groupRows = keys;
for (var i = 0, nkeys = keys.length; i < nkeys; i++) {
if (keys[i].isColumns() === undefined) {
keys[i].setColumns(false);
}
}
if (notify) {
this.trigger(phantasus.Project.Events.ROW_GROUP_BY_CHANGED);
}
},
setGroupColumns: function (keys, notify) {
this.groupColumns = keys;
for (var i = 0, nkeys = keys.length; i < nkeys; i++) {
if (keys[i].isColumns() === undefined) {
keys[i].setColumns(true);
}
}
if (notify) {
this.trigger(phantasus.Project.Events.COLUMN_GROUP_BY_CHANGED);
}
},
setRowFilter: function (filter, notify) {
this._saveSelection(false);
this.rowIndexMapper.setFilter(filter);
this._restoreSelection(false);
if (notify) {
this.trigger(phantasus.Project.Events.ROW_FILTER_CHANGED);
}
},
getRowFilter: function () {
return this.rowIndexMapper.getFilter();
},
getColumnFilter: function () {
return this.columnIndexMapper.getFilter();
},
setColumnFilter: function (filter, notify) {
this._saveSelection(true);
this.columnIndexMapper.setFilter(filter);
this._restoreSelection(true);
if (notify) {
this.trigger(phantasus.Project.Events.COLUMN_FILTER_CHANGED);
}
},
getColumnColorModel: function () {
return this.columnColorModel;
},
getSortedFilteredDataset: function () {
return phantasus.DatasetUtil.slicedView(this.getFullDataset(),
this.rowIndexMapper.convertToView(), this.columnIndexMapper
.convertToView());
},
getSelectedDataset: function (options) {
options = $.extend({}, {
selectedRows: true,
selectedColumns: true,
emptyToAll: true
}, options);
var dataset = this.getSortedFilteredDataset();
var rows = null;
if (options.selectedRows) {
rows = this.rowSelectionModel.getViewIndices().values().sort(
function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
if (rows.length === 0 && options.emptyToAll) {
rows = null;
}
}
var columns = null;
if (options.selectedColumns) {
columns = this.columnSelectionModel.getViewIndices().values().sort(
function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
if (columns.length === 0 && options.emptyToAll) {
columns = null;
}
}
return rows == null && columns == null ? dataset : new phantasus.SlicedDatasetView(dataset, rows, columns);
},
_saveSelection: function (isColumns) {
this.elementSelectionModel.save();
if (isColumns) {
this.columnSelectionModel.save();
} else {
this.rowSelectionModel.save();
}
},
_restoreSelection: function (isColumns) {
if (isColumns) {
this.columnSelectionModel.restore();
} else {
this.rowSelectionModel.restore();
}
this.elementSelectionModel.restore();
},
setRowSortKeys: function (keys, notify) {
this._saveSelection(false);
for (var i = 0, nkeys = keys.length; i < nkeys; i++) {
if (keys[i].isColumns() === undefined) {
keys[i].setColumns(false);
}
}
this.rowIndexMapper.setSortKeys(keys);
this._restoreSelection(false);
if (notify) {
this.trigger(phantasus.Project.Events.ROW_SORT_ORDER_CHANGED);
}
},
setColumnSortKeys: function (keys, notify) {
this._saveSelection(true);
for (var i = 0, nkeys = keys.length; i < nkeys; i++) {
if (keys[i].isColumns() === undefined) {
keys[i].setColumns(true);
}
}
this.columnIndexMapper.setSortKeys(keys);
this._restoreSelection(true);
if (notify) {
this.trigger(phantasus.Project.Events.COLUMN_SORT_ORDER_CHANGED);
}
},
getRowSortKeys: function () {
return this.rowIndexMapper.sortKeys;
},
getColumnSortKeys: function () {
return this.columnIndexMapper.sortKeys;
},
convertViewColumnIndexToModel: function (viewIndex) {
return this.columnIndexMapper.convertViewIndexToModel(viewIndex);
},
convertViewRowIndexToModel: function (viewIndex) {
return this.rowIndexMapper.convertViewIndexToModel(viewIndex);
},
convertModelRowIndexToView: function (modelIndex) {
return this.rowIndexMapper.convertModelIndexToView(modelIndex);
},
convertModelColumnIndexToView: function (modelIndex) {
return this.columnIndexMapper.convertModelIndexToView(modelIndex);
},
isColumnViewIndexSelected: function (index) {
return this.columnSelectionModel.isViewIndexSelected(index);
},
isRowViewIndexSelected: function (index) {
return this.rowSelectionModel.isViewIndexSelected(index);
}
};
phantasus.Util.extend(phantasus.Project, phantasus.Events);
phantasus.QNorm = function () {
};
/**
* Performs quantile normalization.
*/
phantasus.QNorm.execute = function (data) {
var rows = data.getRowCount();
var cols = data.getColumnCount();
var i, j, ind;
var dimat;
var row_mean = new Float32Array(rows);
var ranks = new Float32Array(rows);
/* # sort original columns */
dimat = phantasus.QNorm.get_di_matrix(data);
for (j = 0; j < cols; j++) {
dimat[j].sort(function (s1, s2) {
if (s1.data < s2.data) {
return -1;
}
if (s1.data > s2.data) {
return 1;
}
return 0;
});
}
/* # calculate means */
for (i = 0; i < rows; i++) {
var sum = 0.0;
var numNonMissing = 0;
for (j = 0; j < cols; j++) {
var f = dimat[j][i].data;
if (!isNaN(f)) {
sum += f;
numNonMissing++;
}
}
row_mean[i] = sum / numNonMissing;
}
/* # unsort mean columns */
for (j = 0; j < cols; j++) {
phantasus.QNorm.get_ranks(ranks, dimat[j], rows);
for (i = 0; i < rows; i++) {
ind = dimat[j][i].rank;
if (ranks[i] - Math.floor(ranks[i]) > 0.4) {
data.setValue(ind, j, 0.5 * (row_mean[Math.floor(ranks[i]) - 1] + row_mean[Math.floor(ranks[i])]));
} else {
data.setValue(ind, j, row_mean[Math.floor(ranks[i]) - 1]);
}
}
}
};
/**
* ************************************************************************
* * * dataitem **get_di_matrix(var *data, var rows, var cols) * * given
* data form a matrix of dataitems, each element of * matrix holds datavalue
* and original index so that * normalized data values can be resorted to
* the original order *
* ************************************************************************
*/
phantasus.QNorm.get_di_matrix = function (data) {
var i, j;
var rows = data.getRowCount();
var cols = data.getColumnCount();
var dimat = [];
for (j = 0; j < cols; j++) {
dimat.push([]);
for (i = 0; i < rows; i++) {
dimat[j][i] = {};
dimat[j][i].data = data.getValue(i, j);
dimat[j][i].rank = i;
}
}
return dimat;
};
/**
* ************************************************************************
* * * var *get_ranks(dataitem *x,var n) * * getParameterValue ranks in
* the same manner as R does. Assume that *x is * already sorted *
* ************************************************************************
*/
phantasus.QNorm.get_ranks = function (rank, x, n) {
var i, j, k;
i = 0;
while (i < n) {
j = i;
while ((j < n - 1) && (x[j].data == x[j + 1].data)) {
j++;
}
if (i != j) {
for (k = i; k <= j; k++) {
rank[k] = (i + j + 2) / 2.0;
}
} else {
rank[i] = i + 1;
}
i = j + 1;
}
};
phantasus.SelectionModel = function (project, isColumns) {
this.viewIndices = new phantasus.Set();
this.project = project;
this.isColumns = isColumns;
};
phantasus.SelectionModel.prototype = {
setViewIndices: function (indices, notify) {
this.viewIndices = indices;
if (notify) {
this.trigger('selectionChanged');
}
},
isViewIndexSelected: function (index) {
return this.viewIndices.has(index);
},
clear: function () {
this.viewIndices = new phantasus.Set();
},
/**
*
* @returns {phantasus.Set}
*/
getViewIndices: function () {
return this.viewIndices;
},
count: function () {
return this.viewIndices.size();
},
toModelIndices: function () {
var project = this.project;
var f = this.isColumns ? project.convertViewColumnIndexToModel
: project.convertViewRowIndexToModel;
f = _.bind(f, project);
var modelIndices = [];
this.viewIndices.forEach(function (index) {
var m = f(index);
modelIndices.push(m);
});
return modelIndices;
},
save: function () {
this.modelIndices = this.toModelIndices();
},
restore: function () {
var project = this.project;
this.viewIndices = new phantasus.Set();
var f = this.isColumns ? project.convertModelColumnIndexToView
: project.convertModelRowIndexToView;
f = _.bind(f, project);
for (var i = 0, length = this.modelIndices.length; i < length; i++) {
var index = f(this.modelIndices[i]);
if (index !== -1) {
this.viewIndices.add(index);
}
}
},
};
phantasus.Util.extend(phantasus.SelectionModel, phantasus.Events);
phantasus.SlicedDatasetView = function (dataset, rowIndices, columnIndices) {
phantasus.DatasetAdapter.call(this, dataset);
this.rowIndices = rowIndices || null;
this.columnIndices = columnIndices || null;
};
phantasus.SlicedDatasetView.prototype = {
getRowCount: function () {
return this.rowIndices !== null ? this.rowIndices.length : this.dataset
.getRowCount();
},
getColumnCount: function () {
return this.columnIndices !== null ? this.columnIndices.length
: this.dataset.getColumnCount();
},
getValue: function (i, j, seriesIndex) {
return this.dataset.getValue(
this.rowIndices !== null ? this.rowIndices[i] : i,
this.columnIndices !== null ? this.columnIndices[j] : j,
seriesIndex);
},
setValue: function (i, j, value, seriesIndex) {
this.dataset.setValue(
this.rowIndices !== null ? this.rowIndices[i] : i,
this.columnIndices !== null ? this.columnIndices[j] : j, value,
seriesIndex);
},
getRowMetadata: function () {
return this.rowIndices !== null ? new phantasus.MetadataModelItemView(
this.dataset.getRowMetadata(), this.rowIndices) : this.dataset
.getRowMetadata();
},
getColumnMetadata: function () {
return this.columnIndices !== null ? new phantasus.MetadataModelItemView(
this.dataset.getColumnMetadata(), this.columnIndices)
: this.dataset.getColumnMetadata();
},
toString: function () {
return this.getName();
}
};
phantasus.Util.extend(phantasus.SlicedDatasetView, phantasus.DatasetAdapter);
phantasus.SlicedVector = function (v, indices) {
phantasus.VectorAdapter.call(this, v);
this.indices = indices;
this.levels = null;
if (v.isFactorized()) {
var oldLevels = v.getFactorLevels();
var newValues = phantasus.VectorUtil.getSet(this).values();
this.levels = _.filter(oldLevels, function (level) {
return _.indexOf(newValues, level) !== -1;
});
}
};
phantasus.SlicedVector.prototype = {
setValue: function (i, value) {
this.v.setValue(this.indices[i], value);
},
getValue: function (i) {
return this.v.getValue(this.indices[i]);
},
size: function () {
return this.indices.length;
},
factorize: function (levels) {
if (!levels || _.size(levels) === 0 || !_.isArray(levels)) {
return this.defactorize();
}
if (this.isFactorized()) {
this.defactorize();
}
var uniqueValuesInVector = _.uniq(phantasus.VectorUtil.getSet(this).values());
var allLevelsArePresent = levels.every(function (value) {
return _.indexOf(uniqueValuesInVector, value) !== -1; // all levels are present in current array
}) && uniqueValuesInVector.every(function (value) {
return _.indexOf(levels, value) !== -1; // all current values present in levels
});
if (!allLevelsArePresent) {
throw Error('Cannot factorize vector. Invalid levels');
}
this.levels = levels;
},
defactorize: function () {
if (!this.isFactorized()) {
return;
}
this.levels = null;
},
isFactorized: function () {
return _.size(this.levels) > 0;
},
getFactorLevels: function () {
return this.levels;
}
};
phantasus.Util.extend(phantasus.SlicedVector, phantasus.VectorAdapter);
phantasus.AbstractSortKey = function (name, columns) {
this.name = name;
this.columns = columns;
};
phantasus.AbstractSortKey.prototype = {
lockOrder: 0,
columns: true,
preservesDendrogram: false,
unlockable: true,
/**
* Indicates whether this key is sorting rows or columns.
* @return {Boolean}
*/
isColumns: function () {
return this.columns;
},
/**
* Sets whether this key is columns (true) or rows (false).
* @param columns {Boolean}
*/
setColumns: function (columns) {
this.columns = columns;
},
isPreservesDendrogram: function () {
return this.preservesDendrogram;
},
setPreservesDendrogram: function (preservesDendrogram) {
this.preservesDendrogram = preservesDendrogram;
},
getLockOrder: function () {
return this.lockOrder;
},
/**
* When lock order is set, indicates whether lock order can be unlocked. For example, bring matches to top sort key can not be unlocked.
*/
isUnlockable: function () {
return this.unlockable;
},
setUnlockable: function (unlockable) {
this.unlockable = unlockable;
},
/**
* Sets the sort key lock order. One is locked to beginning of sort keys, two is locked to end of sort keys. Zero clears lock order.
* Dendrogram sort key is locked to end. Selection on top sort key is locked to beginning.
* @param lockOrder {Number}
*/
setLockOrder: function (lockOrder) {
this.lockOrder = lockOrder;
},
setSortOrder: function (sortOrder) {
this.sortOrder = sortOrder;
},
getSortOrder: function () {
return this.sortOrder;
},
init: function () {
}
};
phantasus.MatchesOnTopSortKey = function (project, modelIndices, name, columns) {
phantasus.AbstractSortKey.call(this, name, columns);
var highlightedModelIndices = {};
var p = project;
var viewIndices = [];
for (var i = 0, j = modelIndices.length, length = modelIndices.length; i < length; i++, j--) {
highlightedModelIndices[modelIndices[i]] = -1; // tie
viewIndices.push(i);
}
this.comparator = function (i1, i2) {
var a = highlightedModelIndices[i1];
if (a === undefined) {
a = 0;
}
var b = highlightedModelIndices[i2];
if (b === undefined) {
b = 0;
}
return (a === b ? 0 : (a < b ? -1 : 1));
};
this.indices = viewIndices;
};
phantasus.MatchesOnTopSortKey.prototype = {
toString: function () {
return this.name;
},
getSortOrder: function () {
return 2;
},
getComparator: function () {
return this.comparator;
},
getValue: function (i) {
return i;
}
};
phantasus.Util.extend(phantasus.MatchesOnTopSortKey, phantasus.AbstractSortKey);
phantasus.SortKey = function (field, sortOrder, columns) {
phantasus.AbstractSortKey.call(this, field, columns);
if (typeof sortOrder === 'string') {
sortOrder = phantasus.SortKey.SortOrder[sortOrder.toUpperCase()];
if (sortOrder === undefined) {
sortOrder = 0;
}
}
this.v = null;
this.c = null;
this.setSortOrder(sortOrder);
};
phantasus.SortKey.prototype = {
toString: function () {
return this.name;
},
init: function (dataset, visibleModelIndices) {
this.v = dataset.getRowMetadata().getByName(this.name);
if (!this.v) {
this.v = {};
this.v.getValue = function () {
return 0;
};
this.c = this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING ? phantasus.SortKey.ASCENDING_COMPARATOR
: phantasus.SortKey.DESCENDING_COMPARATOR;
} else {
if (this.v.isFactorized()) {
var levels = this.v.getFactorLevels();
if (this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING) {
this.c = function (a,b) { return _.indexOf(levels, a) - _.indexOf(levels, b); }
} else {
this.c = function (a,b) { return _.indexOf(levels, b) - _.indexOf(levels, a); }
}
} else {
var dataType = phantasus.VectorUtil.getDataType(this.v);
if (dataType === 'number') {
this.c = this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING ? phantasus.SortKey.NUMBER_ASCENDING_COMPARATOR
: phantasus.SortKey.NUMBER_DESCENDING_COMPARATOR;
} else if (dataType === '[number]') {
var summary = this.v.getProperties().get(
phantasus.VectorKeys.ARRAY_SUMMARY_FUNCTION)
|| phantasus.SortKey.ARRAY_MAX_SUMMARY_FUNCTION;
this.c = this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING ? phantasus.SortKey
.ARRAY_ASCENDING_COMPARATOR(summary)
: phantasus.SortKey.ARRAY_DESCENDING_COMPARATOR(summary);
} else {
this.c = this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING ? phantasus.SortKey.ASCENDING_COMPARATOR
: phantasus.SortKey.DESCENDING_COMPARATOR;
}
if (this.customComparator != null) {
var oldC = this.c;
var customComparator = this.customComparator;
if (this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING) {
this.c = function (a, b) {
var val = customComparator(a, b);
return val === 0 ? oldC(a, b) : val;
};
} else {
this.c = function (a, b) {
var val = customComparator(b, a);
return val === 0 ? oldC(a, b) : val;
};
}
}
}
}
if (this.sortOrder === phantasus.SortKey.SortOrder.TOP_N) {
var pairs = [];
var missingIndices = [];
for (var i = 0, nrows = visibleModelIndices.length; i < nrows; i++) {
var index = visibleModelIndices[i];
var value = this.v.getValue(index);
if (!isNaN(value)) {
pairs.push({
index: index,
value: value
});
} else {
missingIndices.push(index);
}
}
// sort values in descending order
var c = this.c;
this.c = phantasus.SortKey.NUMBER_ASCENDING_COMPARATOR;
pairs
.sort(function (pair1, pair2) {
return c(pair1.value, pair2.value);
});
var modelIndexToValue = [];
var nInGroup = Math.min(pairs.length, 10);
var counter = 0;
var topIndex = 0;
var half = Math.floor(pairs.length / 2);
var topPairs = pairs.slice(0, half);
var bottomPairs = pairs.slice(half);
var bottomIndex = bottomPairs.length - 1;
var ntop = topPairs.length;
var npairs = pairs.length;
while (counter < npairs) {
for (var i = 0; i < nInGroup && topIndex < ntop; i++, topIndex++, counter++) {
modelIndexToValue[topPairs[topIndex].index] = counter;
}
var indexCounterPairs = [];
for (var i = 0; i < nInGroup && bottomIndex >= 0; i++, bottomIndex--, counter++) {
indexCounterPairs.push([
bottomPairs[bottomIndex].index,
counter]);
}
for (var i = indexCounterPairs.length - 1, j = 0; i >= 0; i--, j++) {
var item_i = indexCounterPairs[i];
var item_j = indexCounterPairs[j];
modelIndexToValue[item_i[0]] = item_j[1];
}
}
// add on missing
for (var i = 0, length = missingIndices.length; i < length; i++, counter++) {
modelIndexToValue[missingIndices[i]] = counter;
}
this.modelIndexToValue = modelIndexToValue;
}
else {
delete this.modelIndexToValue;
}
},
getComparator: function () {
return this.c;
},
getValue: function (i) {
return this.modelIndexToValue ? this.modelIndexToValue[i] : this.v.getValue(i);
}
};
phantasus.Util.extend(phantasus.SortKey, phantasus.AbstractSortKey);
/**
* @param modelIndices
* Selected rows or columns
* @param isColumnSort -
* sort columns by selected rows.
*/
phantasus.SortByValuesKey = function (modelIndices, sortOrder, isColumnSort) {
phantasus.AbstractSortKey.call(this, 'values', isColumnSort);
this.bothCount = 10;
this.modelIndices = modelIndices;
this.sortOrder = sortOrder;
this.setSortOrder(sortOrder);
};
phantasus.SortByValuesKey.prototype = {
toString: function () {
return this.name;
},
init: function (dataset, visibleModelIndices) {
// isColumnSort-sort columns by selected rows
// dataset is transposed if !isColumnSort
this.dataset = phantasus.DatasetUtil.slicedView(dataset, null,
this.modelIndices);
this.rowView = new phantasus.DatasetRowView(this.dataset);
this.summaryFunction = this.modelIndices.length > 1 ? phantasus.Median
: function (row) {
return row.getValue(0);
};
if (this.sortOrder === phantasus.SortKey.SortOrder.TOP_N) {
var pairs = [];
var missingIndices = [];
for (var i = 0, nrows = visibleModelIndices.length; i < nrows; i++) {
var index = visibleModelIndices[i];
var value = this.summaryFunction(this.rowView.setIndex(index));
if (!isNaN(value)) {
pairs.push({
index: index,
value: value
});
} else {
missingIndices.push(index);
}
}
// sort values in descending order
pairs
.sort(function (a, b) {
return (a.value < b.value ? 1
: (a.value === b.value ? 0 : -1));
});
var modelIndexToValue = [];
var nInGroup = Math.min(pairs.length, this.bothCount);
var counter = 0;
var topIndex = 0;
var half = Math.floor(pairs.length / 2);
var topPairs = pairs.slice(0, half);
var bottomPairs = pairs.slice(half);
var bottomIndex = bottomPairs.length - 1;
var ntop = topPairs.length;
var npairs = pairs.length;
while (counter < npairs) {
for (var i = 0; i < nInGroup && topIndex < ntop; i++, topIndex++, counter++) {
modelIndexToValue[topPairs[topIndex].index] = counter;
}
var indexCounterPairs = [];
for (var i = 0; i < nInGroup && bottomIndex >= 0; i++, bottomIndex--, counter++) {
indexCounterPairs.push([
bottomPairs[bottomIndex].index,
counter]);
}
for (var i = indexCounterPairs.length - 1, j = 0; i >= 0; i--, j++) {
var item_i = indexCounterPairs[i];
var item_j = indexCounterPairs[j];
modelIndexToValue[item_i[0]] = item_j[1];
}
}
// add on missing
for (var i = 0, length = missingIndices.length; i < length; i++, counter++) {
modelIndexToValue[missingIndices[i]] = counter;
}
this.modelIndexToValue = modelIndexToValue;
} else {
delete this.modelIndexToValue;
}
},
getComparator: function () {
return this.c;
},
getValue: function (i) {
return this.modelIndexToValue ? this.modelIndexToValue[i] : this
.summaryFunction(this.rowView.setIndex(i));
},
setSortOrder: function (sortOrder) {
if (typeof sortOrder === 'string') {
sortOrder = phantasus.SortKey.SortOrder[sortOrder.toUpperCase()];
}
this.sortOrder = sortOrder;
if (this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING) {
this.c = phantasus.SortKey.ELEMENT_ASCENDING_COMPARATOR;
} else if (this.sortOrder === phantasus.SortKey.SortOrder.DESCENDING) {
this.c = phantasus.SortKey.ELEMENT_DESCENDING_COMPARATOR;
} else {
this.c = phantasus.SortKey.NUMBER_ASCENDING_COMPARATOR;
}
}
};
phantasus.Util.extend(phantasus.SortByValuesKey, phantasus.AbstractSortKey);
/**
* @param modelIndices
* Array of model indices
* @param nvisible
* The number of visible indices at the time this sort key was
* created. Used by dendrogram to determine if dendrogram should be
* shown.
* @param name
* This sort key name
* @param columns Whether column sort
*/
phantasus.SpecifiedModelSortOrder = function (modelIndices, nvisible, name, columns) {
phantasus.AbstractSortKey.call(this, name, columns);
this.nvisible = nvisible;
var modelIndexToValue = [];
for (var i = 0, length = modelIndices.length; i < length; i++) {
modelIndexToValue[modelIndices[i]] = i;
}
this.modelIndices = modelIndices;
this.modelIndexToValue = modelIndexToValue;
this.c = phantasus.SortKey.NUMBER_ASCENDING_COMPARATOR;
};
phantasus.SpecifiedModelSortOrder.prototype = {
toString: function () {
return this.name;
},
getComparator: function (a, b) {
return this.c;
},
getValue: function (i) {
return this.modelIndexToValue[i];
},
setSortOrder: function (sortOrder) {
this.sortOrder = sortOrder;
this.c = this.sortOrder === phantasus.SortKey.SortOrder.ASCENDING ? phantasus.SortKey.NUMBER_ASCENDING_COMPARATOR
: phantasus.SortKey.NUMBER_DESCENDING_COMPARATOR;
}
};
phantasus.Util.extend(phantasus.SpecifiedModelSortOrder, phantasus.AbstractSortKey);
/**
* Group by key
*
* @param values
*/
phantasus.SpecifiedGroupByKey = function (clusterIds, columns) {
phantasus.AbstractSortKey.call(this, 'Dendrogram Cut', columns);
this.clusterIds = clusterIds;
this.c = function (a, b) {
return (a === b ? 0 : // Values are equal
(a < b ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
1));
};
};
phantasus.SpecifiedGroupByKey.prototype = {
toString: function () {
return this.name;
},
getComparator: function (a, b) {
return this.c;
},
getValue: function (i) {
return this.clusterIds[i];
}
};
phantasus.Util.extend(phantasus.SpecifiedGroupByKey, phantasus.AbstractSortKey);
phantasus.SortKey.SortOrder = {
ASCENDING: 0,
DESCENDING: 1,
UNSORTED: 2,
CUSTOM: 3,
TOP_N: 4
};
/**
* Comparator to sort ascending using lowercase string comparison
*/
phantasus.SortKey.ASCENDING_COMPARATOR = function (a, b) {
// we want NaNs to end up at the bottom
var aNaN = (a == null);
var bNaN = (b == null);
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
a = ('' + a).toLowerCase();
b = ('' + b).toLowerCase();
return (a === b ? 0 : (a < b ? -1 : 1));
};
/**
* Comparator to sort descending using lowercase string comparison
*/
phantasus.SortKey.DESCENDING_COMPARATOR = function (a, b) {
var aNaN = (a == null);
var bNaN = (b == null);
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
a = ('' + a).toLowerCase();
b = ('' + b).toLowerCase();
return (a === b ? 0 : (a < b ? 1 : -1));
};
phantasus.SortKey.NUMBER_ASCENDING_COMPARATOR = function (a, b) {
// we want NaNs to end up at the bottom
var aNaN = (a == null || isNaN(a));
var bNaN = (b == null || isNaN(b));
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
return (a === b ? 0 : (a < b ? -1 : 1));
};
phantasus.SortKey.NUMBER_DESCENDING_COMPARATOR = function (a, b) {
var aNaN = (a == null || isNaN(a));
var bNaN = (b == null || isNaN(b));
if (aNaN && bNaN) {
return 0;
}
//noinspection JSConstructorReturnsPrimitive
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
return (a === b ? 0 : (a < b ? 1 : -1));
};
phantasus.SortKey.STRING_ASCENDING_COMPARATOR = function (a, b) {
a = (a == null || a.toLowerCase === undefined) ? null : a.toLowerCase();
b = (b == null || b.toLowerCase === undefined) ? null : b.toLowerCase();
return (a === b ? 0 : (a < b ? -1 : 1));
};
phantasus.SortKey.STRING_DESCENDING_COMPARATOR = function (a, b) {
a = (a == null || a.toLowerCase === undefined) ? null : a.toLowerCase();
b = (b == null || b.toLowerCase === undefined) ? null : b.toLowerCase();
return (a === b ? 0 : (a < b ? 1 : -1));
};
phantasus.SortKey.ELEMENT_ASCENDING_COMPARATOR = function (obj1, obj2) {
var a = +obj1;
var b = +obj2;
var aNaN = isNaN(a);
var bNaN = isNaN(b);
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
if (a === b) {
if (obj1 != null && obj1.toObject && obj2 != null && obj2.toObject) {
var a1 = obj1.toObject();
var b1 = obj2.toObject();
for (var name in a1) {
a = a1[name];
b = b1[name];
var c = (a === b ? 0 : (a < b ? -1 : 1));
if (c !== 0) {
return c;
}
}
}
}
return (a === b ? 0 : (a < b ? -1 : 1));
};
phantasus.SortKey.ELEMENT_DESCENDING_COMPARATOR = function (obj1, obj2) {
// we want NaNs to end up at the bottom
var a = +obj1;
var b = +obj2;
var aNaN = isNaN(a);
var bNaN = isNaN(b);
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
if (a === b) {
if (obj1 != null && obj1.toObject && obj2 != null && obj2.toObject) {
var a1 = obj1.toObject();
var b1 = obj2.toObject();
for (var name in a1) {
a = a1[name];
b = b1[name];
var c = (a === b ? 0 : (a < b ? 1 : -1));
if (c !== 0) {
return c;
}
}
}
}
return (a === b ? 0 : (a < b ? 1 : -1));
};
phantasus.SortKey.BOX_PLOT_SUMMARY_FUNCTION = function (array) {
var box = array.box;
if (box == null) {
var v = phantasus.VectorUtil.arrayAsVector(array);
box = phantasus
.BoxPlotItem(this.indices != null ? new phantasus.SlicedVector(
v, this.indices) : v);
array.box = box;
}
return box.q3;
};
phantasus.SortKey.ARRAY_MAX_SUMMARY_FUNCTION = function (array) {
var a = 0;
if (array != null) {
var aPosMax = -Number.MAX_VALUE;
var aNegMax = Number.MAX_VALUE;
for (var i = 0, length = array.length; i < length; i++) {
var value = array[i];
if (!isNaN(value)) {
if (value >= 0) {
aPosMax = value > aPosMax ? value : aPosMax;
} else {
aNegMax = value < aNegMax ? value : aNegMax;
}
}
}
if (aPosMax !== -Number.MAX_VALUE) {
a = aPosMax;
}
if (aNegMax !== Number.MAX_VALUE) {
a = Math.abs(aNegMax) > a ? aNegMax : a;
}
}
return a;
};
phantasus.SortKey.ARRAY_ASCENDING_COMPARATOR = function (summary) {
return function (a, b) {
var aNaN = a == null;
var bNaN = b == null;
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
a = summary(a);
b = summary(b);
aNaN = isNaN(a);
bNaN = isNaN(b);
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
return (a === b ? 0 : (a < b ? -1 : 1));
};
};
phantasus.SortKey.ARRAY_DESCENDING_COMPARATOR = function (summary) {
return function (a, b) {
var aNaN = a == null;
var bNaN = b == null;
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
a = summary(a);
b = summary(b);
aNaN = isNaN(a);
bNaN = isNaN(b);
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
return (a === b ? 0 : (a < b ? 1 : -1));
};
};
phantasus.SortKey.reverseComparator = function (c) {
return function (a, b) {
return c(b, a);
};
};
phantasus.SortKey.keepExistingSortKeys = function (newSortKeys, existingSortKeys) {
for (var i = 0, length = existingSortKeys.length; i < length; i++) {
var key = existingSortKeys[i];
if (key.getLockOrder() > 0) {
// 1 is beginning, 2 is end
// don' add it 2x
var existingIndex = -1;
for (var j = 0; j < newSortKeys.length; j++) {
if (newSortKeys[j] === key) {
existingIndex = j;
break;
}
}
if (existingIndex !== -1) { // remove
newSortKeys.splice(existingIndex, 1);
}
newSortKeys.splice(key.getLockOrder() === 1 ? 0 : newSortKeys.length, 0, key);
}
}
return newSortKeys;
};
phantasus.SortKey.fromJSON = function (project, json) {
var sortKeys = [];
json.forEach(function (key) {
var sortKey = null;
if (key.type === 'annotation') {
sortKey = new phantasus.SortKey(key.field, key.order, key.isColumns);
if (key.customSortOrder != null) {
var customSortOrderMap = new phantasus.Map();
for (var i = 0, size = key.customSortOrder.length; i < size; i++) {
customSortOrderMap.set(key.customSortOrder[i], i);
}
var comparator = function (a, b) {
var v1 = customSortOrderMap.get(a);
var v2 = customSortOrderMap.get(b);
if (v1 === undefined && v2 === undefined) {
return 0;
}
if (v1 === undefined) {
v1 = Infinity;
}
if (v2 === undefined) {
v2 = Infinity;
}
return (v1 < v2 ? -1 : 1);
};
sortKey.customComparator = comparator;
if (key.preservesDendrogram) {
sortKey.nvisible = key.customSortOrder.length;
}
}
} else if (key.type === 'byValues') {
sortKey = new phantasus.SortByValuesKey(key.modelIndices, key.order, key.isColumns);
} else if (key.type === 'specified') {
sortKey = new phantasus.SpecifiedModelSortOrder(key.modelIndices, key.nvisible, key.name, key.isColumns);
} else if (key.type === 'matchesOnTop') {
sortKey = new phantasus.MatchesOnTopSortKey(project, key.modelIndices, key.name, key.isColumns);
} else {
if (key.field != null) {
sortKey = new phantasus.SortKey(key.field, key.order);
} else {
console.log('Unknown key: ' + key);
}
}
if (sortKey != null) {
if (key.preservesDendrogram != null) {
sortKey.setPreservesDendrogram(key.preservesDendrogram);
}
if (key.lockOrder != null && key.lockOrder !== 0) {
sortKey.setLockOrder(key.lockOrder);
sortKey.setUnlockable(key.unlockable);
}
sortKeys.push(sortKey);
}
if (sortKey != null) {
if (key.preservesDendrogram != null) {
sortKey.setPreservesDendrogram(key.preservesDendrogram);
}
if (key.lockOrder != null && key.lockOrder !== 0) {
sortKey.setLockOrder(key.lockOrder);
sortKey.setUnlockable(key.unlockable);
}
sortKeys.push(sortKey);
}
});
return sortKeys;
};
phantasus.SortKey.toJSON = function (sortKeys) {
var json = [];
sortKeys.forEach(function (key) {
var sortKey = null;
if (key instanceof phantasus.SortKey) {
sortKey = {
isColumns: key.isColumns(),
order: key.getSortOrder(),
type: 'annotation',
field: '' + key
};
} else if (key instanceof phantasus.SortByValuesKey) {
sortKey = {
isColumns: key.isColumns(),
order: key.getSortOrder(),
type: 'byValues',
modelIndices: key.modelIndices
};
} else if (key instanceof phantasus.SpecifiedModelSortOrder) {
sortKey = {
isColumns: key.isColumns(),
order: key.getSortOrder(),
type: 'specified',
modelIndices: key.modelIndices,
name: key.name,
nvisible: key.nvisible
};
} else if (key instanceof phantasus.MatchesOnTopSortKey) {
sortKey = {
isColumns: key.isColumns(),
order: key.getSortOrder(),
type: 'matchesOnTop',
modelIndices: key.modelIndices,
name: key.name
};
}
if (sortKey != null) {
sortKey.preservesDendrogram = key.isPreservesDendrogram();
if (key.getLockOrder && key.getLockOrder() !== 0) {
sortKey.lockOrder = key.getLockOrder();
sortKey.unlockable = key.isUnlockable ? key.isUnlockable() : false;
}
json.push(sortKey);
} else {
console.log('Unknown sort key type');
}
});
return json;
};
phantasus.SymmetricProjectListener = function (project, vscroll, hscroll) {
var ignoreEvent = false;
var rowGroupBy;
var columnGroupBy;
var rowFilter;
var columnFilter;
var rowSortOrder;
var columnSortOrder;
var columnSelection;
var rowSelection;
var vscrollFunction;
var hscrollFunction;
project.on(phantasus.Project.Events.ROW_GROUP_BY_CHANGED, rowGroupBy = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.setGroupColumns(project.getGroupRows(), true);
ignoreEvent = false;
});
project.on(phantasus.Project.Events.COLUMN_GROUP_BY_CHANGED, columnGroupBy = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.setGroupRows(project.getGroupColumns(), true);
ignoreEvent = false;
});
project.on(phantasus.Project.Events.ROW_FILTER_CHANGED, rowFilter = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.setColumnFilter(project.getRowFilter(), true);
ignoreEvent = false;
});
project.on(phantasus.Project.Events.COLUMN_FILTER_CHANGED, columnFilter = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.setRowFilter(project.getColumnFilter(), true);
ignoreEvent = false;
});
project.on(phantasus.Project.Events.ROW_SORT_ORDER_CHANGED, rowSortOrder = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.setColumnSortKeys(project.getRowSortKeys(), true);
ignoreEvent = false;
});
project.on(phantasus.Project.Events.COLUMN_SORT_ORDER_CHANGED, columnSortOrder = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.setRowSortKeys(project.getColumnSortKeys(), true);
ignoreEvent = false;
});
project.getColumnSelectionModel().on('selectionChanged', columnSelection = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.getRowSelectionModel().setViewIndices(project.getColumnSelectionModel().getViewIndices(), true);
ignoreEvent = false;
});
project.getRowSelectionModel().on('selectionChanged', rowSelection = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
project.getColumnSelectionModel().setViewIndices(project.getRowSelectionModel().getViewIndices(), true);
ignoreEvent = false;
});
vscroll.on('scroll', vscrollFunction = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
var f = vscroll.getMaxValue() === 0 ? 0 : vscroll.getValue() / vscroll.getMaxValue();
hscroll.setValue(f * hscroll.getMaxValue(), true);
ignoreEvent = false;
});
hscroll.on('scroll', hscrollFunction = function () {
if (ignoreEvent) {
return;
}
ignoreEvent = true;
var f = hscroll.getMaxValue() === 0 ? 0 : hscroll.getValue() / hscroll.getMaxValue();
vscroll.setValue(f * vscroll.getMaxValue(), true);
ignoreEvent = false;
});
this.dispose = function () {
project.off(phantasus.Project.Events.ROW_GROUP_BY_CHANGED, rowGroupBy);
project.off(phantasus.Project.Events.COLUMN_GROUP_BY_CHANGED, columnGroupBy);
project.off(phantasus.Project.Events.ROW_FILTER_CHANGED, rowFilter);
project.off(phantasus.Project.Events.COLUMN_FILTER_CHANGED, columnFilter);
project.off(phantasus.Project.Events.ROW_SORT_ORDER_CHANGED, rowSortOrder);
project.off(phantasus.Project.Events.COLUMN_SORT_ORDER_CHANGED, columnSortOrder);
project.getColumnSelectionModel().off('selectionChanged', columnSelection);
project.getRowSelectionModel().off('selectionChanged', rowSelection);
vscroll.off('scroll', vscrollFunction);
hscroll.off('scroll', hscrollFunction);
};
};
phantasus.TransposedDatasetView = function (dataset) {
phantasus.DatasetAdapter.call(this, dataset);
};
phantasus.TransposedDatasetView.prototype = {
getRowCount: function () {
return this.dataset.getColumnCount();
},
getColumnCount: function () {
return this.dataset.getRowCount();
},
getValue: function (i, j, seriesIndex) {
return this.dataset.getValue(j, i, seriesIndex);
},
setValue: function (i, j, value, seriesIndex) {
this.dataset.setValue(j, i, value, seriesIndex);
},
getRowMetadata: function () {
return this.dataset.getColumnMetadata();
},
getColumnMetadata: function () {
return this.dataset.getRowMetadata();
}
};
phantasus.Util.extend(phantasus.TransposedDatasetView, phantasus.DatasetAdapter);
/**
* Provides percentile computation.
* <p>
* There are several commonly used methods for estimating percentiles (a.k.a.
* quantiles) based on sample data. For large samples, the different methods
* agree closely, but when sample sizes are small, different methods will give
* significantly different results. The algorithm implemented here works as
* follows:
* <ol>
* <li>Let <code>n</code> be the length of the (sorted) array and
* <code>0 < p <= 100</code> be the desired percentile.</li>
* <li>If <code> n = 1 </code> return the unique array element (regardless of
* the value of <code>p</code>); otherwise</li>
* <li>Compute the estimated percentile position
* <code> pos = p * (n + 1) / 100</code> and the difference, <code>d</code>
* between <code>pos</code> and <code>floor(pos)</code> (i.e. the fractional
* part of <code>pos</code>). If <code>pos >= n</code> return the largest
* element in the array; otherwise</li>
* <li>Let <code>lower</code> be the element in position
* <code>floor(pos)</code> in the array and let <code>upper</code> be the
* next element in the array. Return <code>lower + d * (upper - lower)</code></li>
* </ol>
*
* @param p Percentile between 0 and 100
*/
phantasus.Percentile = function (vector, p, isSorted) {
return phantasus.ArrayPercentile(phantasus.RemoveNaN(vector), p, isSorted);
};
phantasus.Percentile.toString = function () {
return 'Percentile';
};
/**
* @private
* @ignore
*/
phantasus.RemoveNaN = function (values) {
var array = [];
for (var i = 0, size = values.size(); i < size; i++) {
var value = values.getValue(i);
if (!isNaN(value)) {
array.push(value);
}
}
return array;
};
phantasus.Median = function (vector) {
return phantasus.ArrayPercentile(phantasus.RemoveNaN(vector), 50, false);
};
phantasus.Median.toString = function () {
return 'Median';
};
/**
* @return {string}
*/
phantasus.Median.rString = function () {
return 'fastMedian';
};
/**
* @ignore
*/
phantasus.ArrayPercentile = function (values, p, isSorted) {
if (!isSorted) {
values.sort(function (a, b) {
return (a < b ? -1 : (a === b ? 0 : 1));
});
}
return d3.quantile(values, p / 100);
};
/**
* @ignore
*/
phantasus.MaxPercentiles = function (percentiles) {
var f = function (vector) {
var values = [];
for (var i = 0, size = vector.size(); i < size; i++) {
var value = vector.getValue(i);
if (!isNaN(value)) {
values.push(value);
}
}
if (values.length === 0) {
return NaN;
}
values.sort(function (a, b) {
return (a < b ? -1 : (a === b ? 0 : 1));
});
var max = 0;
for (var i = 0; i < percentiles.length; i++) {
var p = phantasus.ArrayPercentile(values, percentiles[i], true);
if (Math.abs(p) > Math.abs(max)) {
max = p;
}
}
return max;
};
f.toString = function () {
var s = ['Maximum of '];
for (var i = 0, length = percentiles.length; i < length; i++) {
if (i > 0 && length > 2) {
s.push(', ');
}
if (i === length - 1) {
s.push(length == 2 ? ' and ' : 'and ');
}
s.push(percentiles[i]);
}
s.push(' percentiles');
return s.join('');
};
return f;
};
phantasus.CountIf = function (vector, criteria) {
if (!/[<>=!]/.test(criteria)) {
criteria = '=="' + criteria + '"';
}
var matches = 0;
for (var i = 0, size = vector.size(); i < size; i++) {
var value = vector.getValue(i);
if (eval(value + criteria)) {
matches++;
}
}
return matches;
};
phantasus.Mean = function (vector) {
var sum = 0;
var count = 0;
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
if (!isNaN(val)) {
sum += val;
count++;
}
}
return count === 0 ? NaN : sum / count;
};
phantasus.Mean.toString = function () {
return 'Mean';
};
phantasus.Mean.rString = function () {
return 'mean.default';
};
phantasus.Sum = function (vector) {
var sum = 0;
var found = false;
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
if (!isNaN(val)) {
found = true;
sum += val;
}
}
return !found ? NaN : sum;
};
phantasus.Sum.toString = function () {
return 'Sum';
};
phantasus.Sum.rString = function () {
return 'sum';
};
phantasus.CountNonNaN = function (vector) {
var count = 0;
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
if (!isNaN(val)) {
count++;
}
}
return count;
};
phantasus.CountNonNaN.toString = function () {
return 'Count non-NaN';
};
phantasus.Max = function (vector) {
var max = -Number.MAX_VALUE;
var found = false;
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
if (!isNaN(val)) {
found = true;
max = Math.max(max, val);
}
}
return !found ? NaN : max;
};
phantasus.Max.toString = function () {
return 'Max';
};
phantasus.Max.rString = function () {
return 'max';
};
phantasus.Min = function (vector) {
var min = Number.MAX_VALUE;
var found = false;
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
if (!isNaN(val)) {
found = true;
min = Math.min(min, val);
}
}
return !found ? NaN : min;
};
phantasus.Min.toString = function () {
return 'Min';
};
phantasus.Min.rString = function () {
return 'min';
};
phantasus.Variance = function (list, mean) {
if (mean == undefined) {
mean = phantasus.Mean(list);
}
var sum = 0;
var n = 0;
for (var j = 0, size = list.size(); j < size; j++) {
var x = list.getValue(j);
if (!isNaN(x)) {
var diff = x - mean;
diff = diff * diff;
sum += diff;
n++;
}
}
if (n <= 1) {
return NaN;
}
n = n - 1;
if (n < 1) {
n = 1;
}
var variance = sum / n;
return variance;
};
phantasus.Variance.toString = function () {
return 'Variance';
};
phantasus.StandardDeviation = function (list, mean) {
return Math.sqrt(phantasus.Variance(list, mean));
};
phantasus.StandardDeviation.toString = function () {
return 'Standard deviation';
};
var LOG_10 = Math.log(10);
phantasus.Log10 = function (x) {
return x <= 0 ? 0 : Math.log(x) / LOG_10;
};
var LOG_2 = Math.log(2);
phantasus.Log2 = function (x) {
return x <= 0 ? 0 : Math.log(x) / LOG_2;
};
/**
* Computes the False Discovery Rate using the BH procedure.
*
* @param nominalPValues
* Array of nominal p-values.
*/
phantasus.FDR_BH = function (nominalPValues) {
var size = nominalPValues.length;
var fdr = [];
var pValueIndices = phantasus.Util.indexSort(nominalPValues, true);
var ranks = phantasus.Util.rankIndexArray(pValueIndices);
// check for ties
for (var i = pValueIndices.length - 1; i > 0; i--) {
var bigPValue = nominalPValues[pValueIndices[i]];
var smallPValue = nominalPValues[pValueIndices[i - 1]];
if (bigPValue == smallPValue) {
ranks[pValueIndices[i - 1]] = ranks[pValueIndices[i]];
}
}
for (var i = 0; i < size; i++) {
var rank = ranks[i];
var p = nominalPValues[i];
fdr[i] = (p * size) / rank;
}
// ensure fdr is monotonically decreasing
var pIndices = phantasus.Util.indexSort(nominalPValues, false);
for (var i = 0; i < pIndices.length - 1; i++) {
var highIndex = pIndices[i];
var lowIndex = pIndices[i + 1];
fdr[lowIndex] = Math.min(fdr[lowIndex], fdr[highIndex]);
}
for (var i = 0; i < size; i++) {
fdr[i] = Math.min(fdr[i], 1);
}
return fdr;
};
phantasus.FDR_BH.tString = function () {
return 'FDR(BH)';
};
phantasus.MAD = function (list, median) {
if (median == null) {
median = phantasus.Percentile(list, 50);
}
var temp = [];
for (var j = 0, size = list.size(); j < size; j++) {
var value = list.getValue(j);
if (!isNaN(value)) {
temp.push(Math.abs(value - median));
}
}
var r = phantasus.Percentile(new phantasus.Vector('', temp.length)
.setArray(temp), 50);
return 1.4826 * r;
};
phantasus.MAD.toString = function () {
return 'Median absolute deviation';
};
phantasus.CV = function (list) {
var mean = phantasus.Mean(list);
var stdev = Math.sqrt(phantasus.Variance(list, mean));
return stdev / mean;
};
phantasus.CV.toString = function () {
return 'Coefficient of variation';
};
phantasus.BoxPlotItem = function (list) {
var values = phantasus.RemoveNaN(list);
values.sort(function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
if (values.length === 0) {
return {
median: NaN,
q1: NaN,
q3: NaN,
lowerAdjacentValue: NaN,
upperAdjacentValue: NaN
};
}
return phantasus.BoxPlotArrayItem(values);
};
phantasus.BoxPlotArrayItem = function (values) {
var median = phantasus.ArrayPercentile(values, 50, true);
var q1 = phantasus.ArrayPercentile(values, 25, true);
var q3 = phantasus.ArrayPercentile(values, 75, true);
var w = 1.5;
var upperAdjacentValue = -Number.MAX_VALUE;
var lowerAdjacentValue = Number.MAX_VALUE;
// The upper adjacent value (UAV) is the largest observation that is
// less than or equal to
// the upper inner fence (UIF), which is the third quartile plus
// 1.5*IQR.
//
// The lower adjacent value (LAV) is the smallest observation that is
// greater than or equal
// to the lower inner fence (LIF), which is the first quartile minus
// 1.5*IQR.
var upperOutlier = q3 + w * (q3 - q1);
var lowerOutlier = q1 - w * (q3 - q1);
var sum = 0;
for (var i = 0, length = values.length; i < length; i++) {
var value = values[i];
if (value <= upperOutlier) {
upperAdjacentValue = Math.max(upperAdjacentValue, value);
}
if (value >= lowerOutlier) {
lowerAdjacentValue = Math.min(lowerAdjacentValue, value);
}
sum += value;
// if (value > upperOutlier) {
// upperOutliers.add(new Outlier(i, j, value));
// }
// if (value < lowerOutlier) {
// lowerOutliers.add(new Outlier(i, j, value));
// }
}
var mean = sum / values.length;
if (lowerAdjacentValue > q1) {
lowerAdjacentValue = q1;
}
if (upperAdjacentValue < q3) {
upperAdjacentValue = q3;
}
return {
mean: mean,
median: median,
q1: q1, // Lower Quartile
q3: q3, // Upper Quartile
lowerAdjacentValue: lowerAdjacentValue, // Lower Whisker
upperAdjacentValue: upperAdjacentValue
// Upper Whisker
};
};
phantasus.VectorColorModel = function () {
this.vectorNameToColorMap = new phantasus.Map();
this.vectorNameToColorScheme = new phantasus.Map();
this.colors = phantasus.VectorColorModel.TWENTY_COLORS;
};
phantasus.VectorColorModel.YES_COLOR = '#d8b365';
phantasus.VectorColorModel.FEMALE = '#ff99ff';
phantasus.VectorColorModel.MALE = '#66ccff';
// tableau 20-same as d3 category20
phantasus.VectorColorModel.TWENTY_COLORS = [
'#1f77b4', '#aec7e8', '#ff7f0e',
'#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd',
'#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f',
'#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'];
phantasus.VectorColorModel.CATEGORY_20A = phantasus.VectorColorModel.TWENTY_COLORS;
phantasus.VectorColorModel.CATEGORY_20B = [
'#393b79', '#5254a3', '#6b6ecf',
'#9c9ede', '#637939', '#8ca252', '#b5cf6b', '#cedb9c', '#8c6d31',
'#bd9e39', '#e7ba52', '#e7cb94', '#843c39', '#ad494a', '#d6616b',
'#e7969c', '#7b4173', '#a55194', '#ce6dbd', '#de9ed6'];
phantasus.VectorColorModel.CATEGORY_20C = [
'#3182bd', '#6baed6', '#9ecae1',
'#c6dbef', '#e6550d', '#fd8d3c', '#fdae6b', '#fdd0a2', '#31a354',
'#74c476', '#a1d99b', '#c7e9c0', '#756bb1', '#9e9ac8', '#bcbddc',
'#dadaeb', '#636363', '#969696', '#bdbdbd', '#d9d9d9'];
phantasus.VectorColorModel.CATEGORY_ALL = [].concat(
phantasus.VectorColorModel.CATEGORY_20A,
phantasus.VectorColorModel.CATEGORY_20B,
phantasus.VectorColorModel.CATEGORY_20C);
phantasus.VectorColorModel.TABLEAU10 = [
'#1f77b4', '#ff7f0e', '#2ca02c',
'#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22',
'#17becf'];
phantasus.VectorColorModel.STANDARD_COLORS = {
'na': '#c0c0c0',
'nan': '#c0c0c0',
'': '#c0c0c0',
'wt': '#c0c0c0',
'n': '#c0c0c0',
'0': '#c0c0c0',
'y': phantasus.VectorColorModel.YES_COLOR,
'1': phantasus.VectorColorModel.YES_COLOR,
'male': phantasus.VectorColorModel.MALE,
'm': phantasus.VectorColorModel.MALE,
'female': phantasus.VectorColorModel.FEMALE,
'f': phantasus.VectorColorModel.FEMALE,
'kd': '#C675A8',
'oe': '#56b4e9',
'cp': '#FF9933',
'pcl': '#003B4A',
'trt_sh.cgs': '#C675A8',
'trt_oe': '#56b4e9',
'trt_cp': '#FF9933',
'a375': '#1490C1',
'a549': '#AAC8E9',
'hcc515': '#1C9C2A',
'hepg2': '#94DC89',
'ht29': '#946DBE',
'mcf7': '#C5B2D5',
'pc3': '#38C697',
'asc': '#FF8000',
'cd34': '#FFBB75',
'ha1e': '#FB4124',
'neu': '#FF9A94',
'npc': '#E57AC6',
'cancer': '#1490C1',
'immortalized normal': '#FF8000'
};
phantasus.VectorColorModel.getStandardColor = function (value) {
if (value == null) {
return '#c0c0c0';
}
var stringValue = value.toString().toLowerCase();
return phantasus.VectorColorModel.STANDARD_COLORS[stringValue];
};
phantasus.VectorColorModel.getColorMapForNumber = function (length) {
var colors;
if (length < 3) {
colors = colorbrewer.Set1[3];
} else {
colors = colorbrewer.Paired[length];
}
return colors ? colors : phantasus.VectorColorModel.TWENTY_COLORS;
};
phantasus.VectorColorModel.prototype = {
toJSON: function (tracks) {
var _this = this;
var json = {};
tracks.forEach(function (track) {
if (track.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE)) {
var colorMap = _this.vectorNameToColorMap.get(track.getName());
if (colorMap != null) {
json[track.getName()] = colorMap;
}
} else {
// colorScheme is instanceof phantasus.HeatMapColorScheme
var colorScheme = _this.vectorNameToColorScheme.get(track.getName());
if (colorScheme != null && typeof colorScheme.getCurrentColorSupplier !== 'undefined') {
var colorSchemeJSON = phantasus.AbstractColorSupplier.toJSON(colorScheme.getCurrentColorSupplier());
json[track.getName()] = colorSchemeJSON;
}
}
});
return json;
},
fromJSON: function (json) {
for (var name in json) {
var obj = json[name];
if (obj.colors) {
obj.scalingMode = 'fixed';
this.vectorNameToColorScheme.set(name, phantasus.AbstractColorSupplier.fromJSON(obj));
} else {
this.vectorNameToColorMap.set(name, phantasus.Map.fromJSON(obj));
}
}
},
clear: function (vector) {
this.vectorNameToColorMap.remove(vector.getName());
this.vectorNameToColorScheme.remove(vector.getName());
},
copy: function () {
var c = new phantasus.VectorColorModel();
c.colors = this.colors.slice(0);
this.vectorNameToColorMap.forEach(function (colorMap, name) {
var newColorMap = new phantasus.Map();
newColorMap.setAll(colorMap); // copy existing values
c.vectorNameToColorMap.set(name, newColorMap);
});
this.vectorNameToColorScheme.forEach(function (colorScheme, name) {
c.vectorNameToColorScheme.set(name, colorScheme
.copy(new phantasus.Project(new phantasus.Dataset({
name: '',
rows: 1,
columns: 1
}))));
});
return c;
},
clearAll: function () {
this.vectorNameToColorMap = new phantasus.Map();
this.vectorNameToColorScheme = new phantasus.Map();
},
containsDiscreteColor: function (vector, value) {
var metadataValueToColorMap = this.vectorNameToColorMap.get(vector
.getName());
if (metadataValueToColorMap === undefined) {
return false;
}
var c = metadataValueToColorMap.get(value);
return c != null;
},
setDiscreteColorMap: function (colors) {
this.colors = colors;
},
getContinuousColorScheme: function (vector) {
return this.vectorNameToColorScheme.get(vector.getName());
},
isContinuous: function (vector) {
return this.vectorNameToColorScheme.has(vector.getName());
},
getDiscreteColorScheme: function (vector) {
return this.vectorNameToColorMap.get(vector.getName());
},
createContinuousColorMap: function (vector) {
var minMax = phantasus.VectorUtil.getMinMax(vector);
var min = minMax.min;
var max = minMax.max;
var cs = new phantasus.HeatMapColorScheme(new phantasus.Project(
new phantasus.Dataset({
name: '',
rows: 1,
columns: 1
})), {
type: 'fixed',
map: [
{
value: min,
color: colorbrewer.Greens[3][0]
}, {
value: max,
color: colorbrewer.Greens[3][2]
}]
});
this.vectorNameToColorScheme.set(vector.getName(), cs);
return cs;
},
_getColorForValue: function (value) {
var color = phantasus.VectorColorModel.getStandardColor(value);
if (color == null) { // try to reuse existing color map
var existingMetadataValueToColorMap = this.vectorNameToColorMap
.values();
for (var i = 0, length = existingMetadataValueToColorMap.length; i < length; i++) {
color = existingMetadataValueToColorMap[i].get(value);
if (color !== undefined) {
return color;
}
}
}
return color;
},
getContinuousMappedValue: function (vector, value) {
var cs = this.vectorNameToColorScheme.get(vector.getName());
if (cs === undefined) {
cs = this.createContinuousColorMap(vector);
}
return cs.getColor(0, 0, value);
},
getMappedValue: function (vector, value) {
//// console.log("getMappedValue", vector, value);
var metadataValueToColorMap = this.vectorNameToColorMap.get(vector
.getName());
if (metadataValueToColorMap === undefined) {
metadataValueToColorMap = new phantasus.Map();
this.vectorNameToColorMap.set(vector.getName(),
metadataValueToColorMap);
// set all possible colors
var values = phantasus.VectorUtil.getValues(vector);
var ncolors = 0;
var colors = null;
if (values.length < 3) {
colors = colorbrewer.Dark2[3];
} else {
colors = colorbrewer.Paired[values.length];
}
//// console.log("getMappedValue", colors);
if (!colors) {
if (values.length <= 20) {
colors = d3.scale.category20().range();
} else {
colors = phantasus.VectorColorModel.CATEGORY_ALL;
}
}
//// console.log("getMappedValue", colors);
if (colors) {
var ncolors = colors.length;
for (var i = 0, nvalues = values.length; i < nvalues; i++) {
var color = this._getColorForValue(values[i]);
//// console.log(i, color, values[i], colors[i % ncolors]);
if (color == null) {
color = colors[i % ncolors];
}
metadataValueToColorMap.set(values[i], color);
}
} else {
var _this = this;
_.each(values, function (val) {
_this.getMappedValue(vector, val);
});
}
//// console.log(metadataValueToColorMap);
}
var color = metadataValueToColorMap.get(value);
if (color == null) {
color = this._getColorForValue(value);
if (color == null) {
var index = metadataValueToColorMap.size();
color = this.colors[index % this.colors.length];
}
metadataValueToColorMap.set(value, color);
}
return color;
},
setMappedValue: function (vector, value, color) {
var metadataValueToColorMap = this.vectorNameToColorMap.get(vector
.getName());
if (metadataValueToColorMap === undefined) {
metadataValueToColorMap = new phantasus.Map();
this.vectorNameToColorMap.set(vector.getName(),
metadataValueToColorMap);
}
metadataValueToColorMap.set(value, color);
}
};
phantasus.VectorFontModel = function () {
this.vectorNameToMappedValue = new phantasus.Map();
this.fonts = phantasus.VectorFontModel.FONTS;
};
phantasus.VectorFontModel.FONTS = [{weight: 400}, {weight: 700}, {weight: 900}];
// 400 (normal), 700 (bold), 900 (bolder)
phantasus.VectorFontModel.prototype = {
toJSON: function (tracks) {
var _this = this;
var json = {};
tracks.forEach(function (track) {
if (track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT) && track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT)) {
var map = _this.vectorNameToMappedValue.get(track.getName());
if (map != null) {
json[track.getName()] = map;
}
}
});
return json;
},
fromJSON: function (json) {
for (var name in json) {
var obj = json[name];
this.vectorNameToMappedValue.set(name, phantasus.Map.fromJSON(obj));
}
},
clear: function (vector) {
this.vectorNameToMappedValue.remove(vector.getName());
},
copy: function () {
var c = new phantasus.VectorFontModel();
c.fonts = this.fonts.slice(0);
this.vectorNameToMappedValue.forEach(function (fontMap, name) {
var newFontMap = new phantasus.Map();
newFontMap.setAll(fontMap); // copy existing values
c.vectorNameToMappedValue.set(name, newFontMap);
});
return c;
},
clearAll: function () {
this.vectorNameToMappedValue = new phantasus.Map();
},
_getFontForValue: function (value) {
if (value == null) {
return phantasus.VectorFontModel.FONTS[0];
}
// try to reuse existing map
var existingMetadataValueToFontMap = this.vectorNameToMappedValue
.values();
for (var i = 0, length = existingMetadataValueToFontMap.length; i < length; i++) {
var font = existingMetadataValueToFontMap[i].get(value);
if (font !== undefined) {
return font;
}
}
},
getMap: function (name) {
return this.vectorNameToMappedValue.get(name);
},
getMappedValue: function (vector, value) {
var metadataValueToFontMap = this.vectorNameToMappedValue.get(vector
.getName());
if (metadataValueToFontMap === undefined) {
metadataValueToFontMap = new phantasus.Map();
this.vectorNameToMappedValue.set(vector.getName(),
metadataValueToFontMap);
// set all possible values
var values = phantasus.VectorUtil.getValues(vector);
for (var i = 0, nvalues = values.length; i < nvalues; i++) {
var font = this._getFontForValue(values[i]);
if (font == null) {
font = this.fonts[0]; // default is normal
}
metadataValueToFontMap.set(values[i], font);
}
}
var font = metadataValueToFontMap.get(value);
if (font == null) {
font = this._getFontForValue(value);
if (font == null) {
font = this.fonts[0]; // default is normal
}
metadataValueToFontMap.set(value, font);
}
return font;
},
setMappedValue: function (vector, value, font) {
var metadataValueToFontMap = this.vectorNameToMappedValue.get(vector
.getName());
if (metadataValueToFontMap === undefined) {
metadataValueToFontMap = new phantasus.Map();
this.vectorNameToMappedValue.set(vector.getName(),
metadataValueToFontMap);
}
metadataValueToFontMap.set(value, font);
}
};
/**
* An interface for an ordered collection of values.
*
* @interface phantasus.VectorInterface
*/
/**
* Returns the value at the specified index.
*
* @function
* @name phantasus.VectorInterface#getValue
* @param index the index
* @return the value
*/
/**
* Gets the key-value pairs associated with this vector.
*
* @function
* @name phantasus.VectorInterface#getProperties
* @return {phantasus.Map}
*/
/**
* Returns the number of elements in this vector.
*
* @function
* @name phantasus.VectorInterface#size
* @return {number} the size.
*/
/**
* Returns the name of this vector.
*
* @function
* @name phantasus.VectorInterface#getName
* @return {string} the name
*/
phantasus.VectorKeys = {};
/** [string] of field names in array */
phantasus.VectorKeys.FIELDS = 'phantasus.fields';
phantasus.VectorKeys.VALUE_TO_INDICES = 'phantasus.valueToIndices';
/** [int] of visible field indices in phantasus.VectorKeys.FIELDS */
phantasus.VectorKeys.VISIBLE_FIELDS = 'phantasus.visibleFields';
phantasus.VectorKeys.DATA_TYPE = 'phantasus.dataType';
/** Function to map an array to a single value for sorting */
phantasus.VectorKeys.ARRAY_SUMMARY_FUNCTION = 'phantasus.arraySummaryFunct';
/** Key for object (e.g. box plot) that summarizes data values */
phantasus.VectorKeys.HEADER_SUMMARY = 'phantasus.headerSummary';
/** Key indicating to show header summary */
phantasus.VectorKeys.SHOW_HEADER_SUMMARY = 'phantasus.showHeaderSummary';
phantasus.VectorKeys.TITLE = 'phantasus.title';
/** Function to compute vector value */
phantasus.VectorKeys.FUNCTION = 'phantasus.funct';
/** Indicates that vector values are dynamically computed based on selection */
phantasus.VectorKeys.SELECTION = 'phantasus.selection';
/** Whether to recompute a function when creating a new heat map (true or false) */
phantasus.VectorKeys.RECOMPUTE_FUNCTION_NEW_HEAT_MAP = 'phantasus.recompute.funct.new.heat.map';
/** Whether to recompute a function when filter is updated (true or false) */
phantasus.VectorKeys.RECOMPUTE_FUNCTION_FILTER = 'phantasus.recompute.funct.filter';
/** Boolean, whether to recompute a function when heat map selection changes */
phantasus.VectorKeys.RECOMPUTE_FUNCTION_SELECTION = 'phantasus.recompute.funct.selection';
/**Number format spec/function */
phantasus.VectorKeys.FORMATTER = 'phantasus.formatter';
/* Indicates that a "fake" vector to show row/column number */
phantasus.VectorKeys.IS_INDEX = 'phantasus.isIndex';
/** Whether vector values should be treated discretely or continuously */
phantasus.VectorKeys.DISCRETE = 'phantasus.discrete';
phantasus.VectorKeys.COPY_IGNORE = new phantasus.Set();
phantasus.VectorKeys.COPY_IGNORE.add(phantasus.VectorKeys.HEADER_SUMMARY);
phantasus.VectorKeys.COPY_IGNORE.add(phantasus.VectorKeys.DATA_TYPE);
phantasus.VectorKeys.COPY_IGNORE.add(phantasus.VectorKeys.VALUE_TO_INDICES);
phantasus.VectorKeys.JSON_WHITELIST = new phantasus.Set();
phantasus.VectorKeys.JSON_WHITELIST.add(phantasus.VectorKeys.FIELDS);
phantasus.VectorKeys.JSON_WHITELIST.add(phantasus.VectorKeys.FORMATTER);
phantasus.VectorKeys.JSON_WHITELIST.add(phantasus.VectorKeys.DATA_TYPE);
phantasus.VectorKeys.JSON_WHITELIST.add(phantasus.VectorKeys.TITLE);
phantasus.VectorKeys.LEVELS = 'phantasus.levels';
phantasus.VectorShapeModel = function () {
this.shapes = phantasus.VectorShapeModel.SHAPES;
this.vectorNameToMappedValue = new phantasus.Map();
};
phantasus.VectorShapeModel.SHAPES = [
'circle', 'square', 'plus', 'x',
'asterisk', 'diamond', 'triangle-up', 'triangle-down', 'triangle-left',
'triangle-right', 'circle-minus'];
phantasus.VectorShapeModel.FILLED_SHAPES = [
'circle', 'square', 'diamond', 'triangle-up', 'triangle-down', 'triangle-left',
'triangle-right'];
phantasus.VectorShapeModel.prototype = {
toJSON: function (tracks) {
var _this = this;
var json = {};
tracks.forEach(function (track) {
if (track.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)) {
var map = _this.vectorNameToMappedValue.get(track.getName());
if (map != null) {
json[track.getName()] = map;
}
}
});
return json;
},
fromJSON: function (json) {
for (var name in json) {
var obj = json[name];
this.vectorNameToMappedValue.set(name, phantasus.Map.fromJSON(obj));
}
},
clear: function (vector) {
this.vectorNameToMappedValue.remove(vector.getName());
},
copy: function () {
var c = new phantasus.VectorShapeModel();
c.shapes = this.shapes.slice(0);
this.vectorNameToMappedValue.forEach(function (shapeMap, name) {
var newShapeMap = new phantasus.Map();
newShapeMap.setAll(shapeMap); // copy existing values
c.vectorNameToMappedValue.set(name, newShapeMap);
});
return c;
},
clearAll: function () {
this.vectorNameToMappedValue = new phantasus.Map();
},
_getShapeForValue: function (value) {
if (value == null) {
return 'none';
}
// try to reuse existing map
var existingMetadataValueToShapeMap = this.vectorNameToMappedValue
.values();
for (var i = 0, length = existingMetadataValueToShapeMap.length; i < length; i++) {
var shape = existingMetadataValueToShapeMap[i].get(value);
if (shape !== undefined) {
return shape;
}
}
},
getMap: function (name) {
return this.vectorNameToMappedValue.get(name);
},
getMappedValue: function (vector, value) {
var metadataValueToShapeMap = this.vectorNameToMappedValue.get(vector
.getName());
if (metadataValueToShapeMap === undefined) {
metadataValueToShapeMap = new phantasus.Map();
this.vectorNameToMappedValue.set(vector.getName(),
metadataValueToShapeMap);
// set all possible shapes
var values = phantasus.VectorUtil.getValues(vector);
for (var i = 0, nvalues = values.length; i < nvalues; i++) {
var shape = this._getShapeForValue(values[i]);
if (shape == null) {
shape = this.shapes[i % this.shapes.length];
}
metadataValueToShapeMap.set(values[i], shape);
}
}
var shape = metadataValueToShapeMap.get(value);
if (shape == null) {
shape = this._getShapeForValue(value);
if (shape == null) {
var index = metadataValueToShapeMap.size();
shape = this.shapes[index % this.shapes.length];
}
metadataValueToShapeMap.set(value, shape);
}
return shape;
},
setMappedValue: function (vector, value, shape) {
var metadataValueToShapeMap = this.vectorNameToMappedValue.get(vector
.getName());
if (metadataValueToShapeMap === undefined) {
metadataValueToShapeMap = new phantasus.Map();
this.vectorNameToMappedValue.set(vector.getName(),
metadataValueToShapeMap);
}
metadataValueToShapeMap.set(value, shape);
}
};
phantasus.VectorUtil = function () {
};
phantasus.VectorUtil.jsonToFunction = function (vector, key) {
var f = vector.getProperties().get(key);
if (typeof f === 'object') {
// TODO encode other functions
var binSize = f.binSize;
var min = f.domain[0];
var max = f.domain[1];
var numberOfBins = Math.ceil((max - min) / binSize);
var percent = f.percent;
var cumulative = f.cumulative;
var histogramFunction = function (view, selectedDataset, columnIndex) {
var total = 0;
var binNumberToOccurences = new Uint32Array(numberOfBins);
for (var i = 0, nrows = selectedDataset.getRowCount(); i < nrows; i++) {
var value = selectedDataset.getValue(i, columnIndex);
if (!isNaN(value)) {
if (value >= min && value <= max) {
var bin = Math.floor(((value - min) / binSize));
if (bin < 0) {
bin = 0;
} else if (bin >= numberOfBins) {
bin = numberOfBins - 1;
}
binNumberToOccurences[bin]++;
}
total++;
}
}
if (cumulative) {
for (var i = numberOfBins - 2; i >= 0; i--) {
binNumberToOccurences[i] += binNumberToOccurences[i + 1];
}
}
if (percent) {
var percents = new Float32Array(numberOfBins);
for (var i = 0; i < numberOfBins; i++) {
percents[i] = 100 * (binNumberToOccurences[i] / total);
}
return percents;
}
return binNumberToOccurences;
};
vector.getProperties().set(key, histogramFunction);
var jsonSpec = f;
f = histogramFunction;
f.toJSON = function () {
return jsonSpec;
};
}
return f;
};
phantasus.VectorUtil.createValueToIndexMap = function (vector, splitArrayValues) {
var map = new phantasus.Map();
var isArray = splitArrayValues && phantasus.VectorUtil.getDataType(vector)[0] === '[';
for (var j = 0, size = vector.size(); j < size; j++) {
var val = vector.getValue(j);
if (isArray) {
if (val != null) {
for (var k = 0; k < val.length; k++) {
map.set(val[k], j);
}
}
} else {
map.set(val, j);
}
}
return map;
};
phantasus.VectorUtil.createValueToIndicesMap = function (vector, splitArrayValues) {
if (!vector) {
throw 'vector is null';
}
var isArray = splitArrayValues && phantasus.VectorUtil.getDataType(vector)[0] === '[';
var map = new phantasus.Map();
for (var j = 0, size = vector.size(); j < size; j++) {
var val = vector.getValue(j);
if (isArray) {
if (val != null) {
for (var k = 0; k < val.length; k++) {
var list = map.get(val[k]);
if (list === undefined) {
list = [];
map.set(val[k], list);
}
list.push(j);
}
}
} else {
var list = map.get(val);
if (list === undefined) {
list = [];
map.set(val, list);
}
list.push(j);
}
}
return map;
};
phantasus.VectorUtil.createValueToCountMap = function (vector) {
if (!vector) {
throw 'vector is null';
}
var map = new phantasus.Map();
var dataType = phantasus.VectorUtil.getDataType(vector);
var isArray = dataType[0] === '[';
for (var j = 0, size = vector.size(); j < size; j++) {
var val = vector.getValue(j);
if (val != null) {
if (isArray) {
for (var k = 0; k < val.length; k++) {
var count = map.get(val[k]) || 0;
map.set(val[k], count + 1);
}
} else {
var count = map.get(val) || 0;
map.set(val, count + 1);
}
}
}
return map;
};
phantasus.VectorUtil.createValuesToIndicesMap = function (vectors) {
var map = new phantasus.Map();
var nvectors = vectors.length;
if (vectors[0] == null) {
throw 'no vectors found';
}
for (var i = 0, nitems = vectors[0].size(); i < nitems; i++) {
var array = [];
for (var j = 0; j < nvectors; j++) {
var vector = vectors[j];
var val = vector.getValue(i);
array.push(val);
}
var key = new phantasus.Identifier(array);
var list = map.get(key);
if (list === undefined) {
list = [];
map.set(key, list);
}
list.push(i);
}
return map;
};
phantasus.VectorUtil.createValuesToIndexMap = function (vectors) {
var map = new phantasus.Map();
var nvectors = vectors.length;
if (vectors[0] == null) {
throw 'no vectors found';
}
for (var i = 0, nitems = vectors[0].size(); i < nitems; i++) {
var array = [];
for (var j = 0; j < nvectors; j++) {
var vector = vectors[j];
var val = vector.getValue(i);
array.push(val);
}
var key = new phantasus.Identifier(array);
map.set(key, i);
}
return map;
};
phantasus.VectorUtil.createValuesToCountMap = function (vectors) {
var map = new phantasus.Map();
var nvectors = vectors.length;
if (vectors[0] == null) {
throw 'no vectors found';
}
for (var i = 0, nitems = vectors[0].size(); i < nitems; i++) {
var array = [];
for (var j = 0; j < nvectors; j++) {
var vector = vectors[j];
var val = vector.getValue(i);
array.push(val);
}
var key = new phantasus.Identifier(array);
var count = map.get(key) || 0;
map.set(key, count + 1);
}
return map;
};
/**
*
* @param vector
* @param excludeNull
* @returns A sorted array of unique values contained in the vector. Note that array values are
* not split.
*/
phantasus.VectorUtil.getValues = function (vector, excludeNull) {
var set = new phantasus.Set();
for (var j = 0, size = vector.size(); j < size; j++) {
var val = vector.getValue(j);
if (excludeNull && val == null) {
continue;
}
set.add(val);
}
var array = set.values();
array.sort(phantasus.SortKey.ASCENDING_COMPARATOR);
return array;
};
phantasus.VectorUtil.getSet = function (vector, splitArrayValues) {
var set = new phantasus.Set();
var isArray = splitArrayValues && phantasus.VectorUtil.getDataType(vector)[0] === '[';
for (var j = 0, size = vector.size(); j < size; j++) {
var value = vector.getValue(j);
if (isArray) {
if (value != null) {
for (var k = 0, nvalues = value.length; k < nvalues; k++) {
set.add(value[k]);
}
}
} else {
set.add(value);
}
}
return set;
};
phantasus.VectorUtil.maybeConvertToStringArray = function (vector, delim) {
var newValues = [];
var regex = new RegExp(delim);
var found = false;
for (var i = 0, nrows = vector.size(); i < nrows; i++) {
var s = vector.getValue(i);
if (s != null) {
if (!s.split) {
return false;
}
var tokens = s.split(regex);
newValues.push(tokens);
if (!found && tokens.length > 1) {
found = true;
}
}
}
if (found) {
for (var i = 0, nrows = newValues.length; i < nrows; i++) {
vector.setValue(i, newValues[i]);
}
vector.getProperties().set(phantasus.VectorKeys.DATA_TYPE, '[string]');
}
return found;
};
phantasus.VectorUtil.maybeConvertStringToNumber = function (vector) {
var newValues = [];
var found = false;
for (var i = 0, nrows = vector.size(); i < nrows; i++) {
var s = vector.getValue(i);
var tmp = parseFloat(s);
if (!isNaN(tmp) && isFinite(tmp)) {
newValues.push(tmp);
found = true;
} else {
return false;
}
}
if (!found) {
return false;
}
for (var i = 0, nrows = newValues.length; i < nrows; i++) {
vector.setValue(i, newValues[i]);
}
vector.getProperties().set(phantasus.VectorKeys.DATA_TYPE, 'number');
return true;
};
phantasus.VectorUtil.containsMoreThanOneValue = function (vector) {
return phantasus.VectorUtil.containsMoreThanNValues(vector, 1);
};
phantasus.VectorUtil.containsMoreThanNValues = function (vector, n) {
var s = new phantasus.Set();
for (var j = 0, size = vector.size(); j < size; j++) {
var val = vector.getValue(j);
s.add(val);
if (s.size() > n) {
return true;
}
}
return false;
};
phantasus.VectorUtil.createSpanMap = function (vector) {
var previous = vector.getValue(0);
// find 1st row with different value
var startIndexToEndIndex = new phantasus.Map();
var start = 0;
for (var i = 1, nrows = vector.size(); i < nrows; i++) {
var val = vector.getValue(i);
if (previous !== val) {
previous = val;
// start inclusive, end exclusive
startIndexToEndIndex.set(start, i);
start = i;
}
}
startIndexToEndIndex.set(start, vector.size());
return startIndexToEndIndex;
};
phantasus.VectorUtil.toArray = function (vector) {
var array = [];
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
array.push(val);
}
return array;
};
phantasus.VectorUtil.arrayAsVector = function (array, name) {
var v = new phantasus.Vector(name, array.length);
v.array = array;
return v;
};
phantasus.VectorUtil.toString = function (vector) {
var array = [];
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
array.push(val);
}
return array.join(', ');
};
phantasus.VectorUtil.getDataType = function (vector) {
var dataType = vector.getProperties().get(phantasus.VectorKeys.DATA_TYPE);
if (dataType === undefined) {
var firstNonNull = phantasus.VectorUtil.getFirstNonNull(vector);
dataType = phantasus.Util.getDataType(firstNonNull);
vector.getProperties().set(phantasus.VectorKeys.DATA_TYPE, dataType);
}
return dataType;
};
phantasus.VectorUtil.getMinMax = function (vector) {
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
var fields = vector.getProperties().get(phantasus.VectorKeys.FIELDS);
var isArray = phantasus.VectorUtil.getDataType(vector)[0] === '[';
if (fields != null) {
var nvalues = fields.length;
for (var i = 0, size = vector.size(); i < size; i++) {
var array = vector.getValue(i);
if (array) {
for (var j = 0; j < nvalues; j++) {
var value = array[j];
if (!isNaN(value)) {
min = value < min ? value : min;
max = value > max ? value : max;
}
}
}
}
} else if (isArray) {
for (var i = 0, size = vector.size(); i < size; i++) {
var array = vector.getValue(i);
if (array != null) {
for (var j = 0, nvalues = array.length; j < nvalues; j++) {
var value = array[j];
if (!isNaN(value)) {
min = value < min ? value : min;
max = value > max ? value : max;
}
}
}
}
} else {
for (var i = 0, size = vector.size(); i < size; i++) {
var value = vector.getValue(i);
if (!isNaN(value)) {
min = value < min ? value : min;
max = value > max ? value : max;
}
}
}
return {
min: min,
max: max
};
}
;
phantasus.VectorUtil.getFirstNonNull = function (vector) {
for (var i = 0, length = vector.size(); i < length; i++) {
var val = vector.getValue(i);
if (val != null) {
return val;
}
}
return null;
};
phantasus.VectorUtil.isNumber = function (vector) {
return phantasus.VectorUtil.getDataType(vector) === 'number';
};
/**
* An ordered collection of values.
*
* Creates a new vector with the given name and size.
*
* @param name
* the vector name
* @param size
* the number of elements in this vector
* @constructor
*/
phantasus.Vector = function (name, size) {
this.array = [];
this.levels = null;
phantasus.AbstractVector.call(this, name, size);
};
/**
* @static
*/
phantasus.Vector.fromArray = function (name, array) {
var v = new phantasus.Vector(name, array.length);
v.array = array;
return v;
};
phantasus.Vector.prototype = {
/**
* @ignore
* @param value
*/
push: function (value) {
this.array.push(value);
},
/**
* Sets the value at the specified index.
*
* @param index
* the index
* @param value
* the value
*/
setValue: function (index, value) {
this.defactorize();
this.array[index] = value;
},
getValue: function (index) {
return this.array[index];
},
/**
* @ignore
* @param name
*/
setName: function (name) {
this.name = name;
},
/**
* @ignore
* @param array
* @returns {phantasus.Vector}
*/
setArray: function (array) {
this.defactorize();
this.array = array;
return this;
},
factorize: function (levels) {
if (!levels || _.size(levels) === 0 || !_.isArray(levels)) {
return this.defactorize();
}
if (this.isFactorized()) {
this.defactorize();
}
var uniqueValuesInVector = _.uniq(this.array);
var allLevelsArePresent = levels.every(function (value) {
return _.indexOf(uniqueValuesInVector, value) !== -1; // all levels are present in current array
}) && uniqueValuesInVector.every(function (value) {
return _.indexOf(levels, value) !== -1; // all current values present in levels
});
if (!allLevelsArePresent) {
throw Error('Cannot factorize vector. Invalid levels');
}
this.levels = levels;
},
defactorize: function () {
if (!this.isFactorized()) {
return;
}
this.levels = null;
},
isFactorized: function () {
return _.size(this.levels) > 0;
},
getFactorLevels: function () {
return this.levels;
}
};
phantasus.Util.extend(phantasus.Vector, phantasus.AbstractVector);
/**
*
* @param pageOptions.el
* @param pageOptions.tabManager
* @constructor
*/
phantasus.LandingPage = function (pageOptions) {
pageOptions = $.extend({}, {
el: $('#vis'),
autoInit: true
}, pageOptions);
this.pageOptions = pageOptions;
var _this = this;
var $el = $('<div class="container" style="display: none;"></div>');
this.$el = $el;
var html = [];
phantasus.Util.createPhantasusHeader().appendTo($el);
html.push('<div data-name="help" class="pull-right"></div>');
html.push('<h4>Open your own file</h4>');
html.push('<div data-name="formRow" class="center-block"></div>');
html.push('<div data-name="historyRow" class="center-block"></div>');
html.push('<div style="display: none;" data-name="preloadedDataset"><h4>Or select a preloaded' +
' dataset</h4></div>');
html.push('</div>');
var $html = $(html.join(''));
$html.appendTo($el);
new phantasus.HelpMenu().$el.appendTo($el.find('[data-name=help]'));
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'file',
showLabel: false,
value: '',
type: 'file',
required: true,
help: phantasus.DatasetUtil.DATASET_FILE_FORMATS
});
formBuilder.$form.appendTo($el.find('[data-name=formRow]'));
this.formBuilder = formBuilder;
this.$sampleDatasetsEl = $el.find('[data-name=preloadedDataset]');
this.tabManager = new phantasus.TabManager({landingPage: this});
this.tabManager.on('change rename add remove', function (e) {
var title = _this.tabManager.getTabText(_this.tabManager.getActiveTabId());
if (title == null || title === '') {
title = 'phantasus';
}
document.title = title;
});
if (pageOptions.tabManager) {
this.tabManager = pageOptions.tabManager;
} else {
this.tabManager = new phantasus.TabManager({landingPage: this});
this.tabManager.on('change rename add remove', function (e) {
var title = _this.tabManager.getTabText(_this.tabManager.getActiveTabId());
if (title == null || title === '') {
title = 'phantasus';
}
document.title = title;
});
this.tabManager.$nav.appendTo($(this.pageOptions.el));
this.tabManager.$tabContent.appendTo($(this.pageOptions.el));
}
this.$historyDatsetsEl = $el.find('[data-name=historyRow]');
phantasus.datasetHistory.on('open', function (evt) {
_this.open({dataset: evt});
});
phantasus.datasetHistory.on('changed', function () {
phantasus.datasetHistory.render(_this.$historyDatsetsEl);
});
phantasus.datasetHistory.render(this.$historyDatsetsEl);
if (this.pageOptions.autoInit) {
var searchString = window.location.search;
if (searchString.length === 0) {
searchString = window.location.hash;
}
this.$el.prependTo($(document.body));
if (searchString.length === 0) {
this.show();
} else {
searchString = searchString.substring(1);
var keyValuePairs = searchString.split('&');
var params = {};
for (var i = 0; i < keyValuePairs.length; i++) {
var pair = keyValuePairs[i].split('=');
params[pair[0]] = decodeURIComponent(pair[1]);
}
// console.log(params);
if (params.json) {
var options = JSON.parse(decodeURIComponent(params.json));
_this.open(options);
} else if (params.url) { // url to config
var $loading = phantasus.Util.createLoadingEl();
$loading.appendTo($('#vis'));
phantasus.Util.getText(params.url).done(function (text) {
var options = JSON.parse(text);
_this.open(options);
}).fail(function (err) {
console.log('Unable to get config file');
_this.show();
}).always(function () {
$loading.remove();
});
} else if (params.geo) {
var options = {
dataset: {
file: params.geo.toUpperCase(),
options: {
interactive: true,
isGEO: true
}
}
};
this.open(options);
} else if (params.session) {
var options = {
dataset: {
file: params.session,
options: {
interactive: true,
session: true
}
}
};
_this.open(options);
} else if (params.preloaded) {
var options = {
dataset: {
file: params.preloaded,
options: {
interactive: true,
preloaded: true
}
}
};
_this.open(options);
} else {
this.show();
}
}
}
};
phantasus.LandingPage.prototype = {
open: function (openOptions) {
this.dispose();
var _this = this;
var createGEOHeatMap = function(options) {
var req = ocpu.call('checkGPLs/print', { name : options.dataset.file }, function (session) {
// session.getMessages(function(success) {
// console.log('checkGPLs messages', '::', success);
// });
var filenames = JSON.parse(session.txt);
// console.log("filenames", filenames, filenames.length);
if (!filenames.length) {
_this.show();
throw new Error("Dataset" + " " + options.dataset.file + " does not exist");
}
if (filenames.length === 1) {
new phantasus.HeatMap(options);
}
else {
for (var j = 0; j < filenames.length; j++) {
var specificOptions = options;
specificOptions.dataset.file = filenames[j];
new phantasus.HeatMap(specificOptions);
}
}
});
req.fail(function () {
_this.show();
throw new Error("Checking GPLs call to OpenCPU failed" + req.responseText);
});
};
var createPreloadedHeatMap = function(options) {
options.dataset.options.exactName = options.dataset.file;
new phantasus.HeatMap(options);
};
var createSessionHeatMap = function (options) {
//http://localhost:3000/?session=x06c106048e7cb1
var req = ocpu.call('sessionExists/print', { sessionName : options.dataset.file }, function(session) {
var result = JSON.parse(session.txt);
if (!result.result) {
_this.show();
throw new Error("Dataset" + " " + options.dataset.file + " does not exist");
}
var heatmapReq = ocpu.call('heatmapSettings/print', { sessionName: options.dataset.file }, function (session) {
var data = JSON.parse(session.txt);
if (!data.result) {
console.log('Unavailable heatmap json settings');
return new phantasus.HeatMap(options);
}
options.inheritFromParent = false;
var newOptions = $.extend({}, data.result, options);
new phantasus.HeatMap(newOptions);
});
heatmapReq.fail(function () {
console.warn('Could not load heatmap json settings');
new phantasus.HeatMap(options);
});
});
req.fail(function () {
_this.show();
throw new Error("Failed to check if the session exists:" + req.responseText);
});
};
var optionsArray = _.isArray(openOptions) ? openOptions : [openOptions];
// console.log(optionsArray);
for (var i = 0; i < optionsArray.length; i++) {
var originalOptions = _.clone(optionsArray[i]);
var options = optionsArray[i];
options.tabManager = _this.tabManager;
options.focus = i === 0;
options.standalone = true;
options.landingPage = _this;
if (options.dataset.options && options.dataset.options.isGEO) {
createGEOHeatMap(options);
} else if (options.dataset.options && options.dataset.options.preloaded) {
createPreloadedHeatMap(options);
} else if (options.dataset.options && options.dataset.options.session) {
createSessionHeatMap(options);
}
else {
// console.log("before loading heatmap from landing_page", options);
new phantasus.HeatMap(options);
}
}
},
dispose: function () {
this.formBuilder.setValue('file', '');
this.$el.hide();
$(window)
.off(
'paste.phantasus drop.phantasus dragover.phantasus dragenter.phantasus');
this.formBuilder.off('change');
},
show: function () {
var _this = this;
this.$el.show();
this.formBuilder.on('change', function (e) {
var value = e.value;
if (value !== '' && value != null) {
_this.openFile(value);
}
});
$(window).on('beforeunload.phantasus', function () {
if (_this.tabManager.getTabCount() > 0) {
return 'Are you sure you want to close phantasus?';
}
});
$(window).on('paste.phantasus', function (e) {
var tagName = e.target.tagName;
if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') {
return;
}
var text = e.originalEvent.clipboardData.getData('text/plain');
if (text != null && text.length > 0) {
e.preventDefault();
e.stopPropagation();
var url;
if (text.indexOf('http') === 0) {
url = text;
} else {
var blob = new Blob([text]);
url = window.URL.createObjectURL(blob);
}
_this.openFile(url);
}
}).on('dragover.phantasus dragenter.phantasus', function (e) {
e.preventDefault();
e.stopPropagation();
}).on(
'drop.phantasus',
function (e) {
if (e.originalEvent.dataTransfer
&& e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
var files = e.originalEvent.dataTransfer.files;
_this.openFile(files[0]);
} else if (e.originalEvent.dataTransfer) {
var url = e.originalEvent.dataTransfer.getData('URL');
e.preventDefault();
e.stopPropagation();
_this.openFile(url);
}
});
if (navigator.onLine && !this.sampleDatasets) {
this.sampleDatasets = new phantasus.SampleDatasets({
$el: this.$sampleDatasetsEl,
show: true,
callback: function (heatMapOptions) {
_this.open(heatMapOptions);
}
});
}
},
openFile: function (value) {
var _this = this;
var isGEO;
var preloaded;
if (value.name && (value.isGEO || value.preloaded)) {
isGEO = value.isGEO;
preloaded = value.preloaded;
value = value.name;
}
var fileName = phantasus.Util.getFileName(value);
if (fileName.toLowerCase().endsWith('.json')) {
phantasus.Util.getText(value).done(function (text) {
_this.open(JSON.parse(text));
}).fail(function (err) {
phantasus.FormBuilder.showMessageModal({
title: 'Error',
message: 'Unable to load session'
});
});
} else {
var options = {
dataset: {
file: value,
options: {
interactive: true,
isGEO: isGEO,
preloaded: preloaded
}
}
};
phantasus.OpenDatasetTool.fileExtensionPrompt(fileName, function (readOptions) {
// console.log("fileExtensionPrompt", readOptions);
if (readOptions) {
for (var key in readOptions) {
options.dataset.options[key] = readOptions[key];
}
}
_this.open(options);
});
}
}
};
phantasus.SampleDatasets = function (options) {
if (!options.openText) {
options.openText = 'Open';
}
var _this = this;
var $el = options.$el;
this.callback = options.callback;
$el.on('click', '[name=ccle]', function (e) { // button click
var $this = $(this);
var obj = {};
$this.parents('tr').find('input:checked').each(function (i, c) {
obj[$(c).data('type')] = true;
});
_this.openCCLE(obj);
e.preventDefault();
});
$el.on('click', '[name=tcgaLink]', function (e) {
e.preventDefault();
var $this = $(this);
var type = $this.data('disease-type');
var obj = {};
$this.parents('tr').find('input:checked').each(function (i, c) {
obj[$(c).data('type')] = true;
});
var disease;
for (var i = 0; i < _this.diseases.length; i++) {
if (_this.diseases[i].type === type) {
disease = _this.diseases[i];
break;
}
}
obj.type = type;
obj.name = disease.name;
_this.openTcga(obj);
});
$el.on(
'click',
'[data-toggle=dataTypeToggle]',
function (e) {
var $this = $(this);
var $button = $this.parents('tr').find('button');
var isDisabled = $this.parents('tr').find(
'input:checked').length === 0;
$button.prop('disabled', isDisabled);
});
fetch('https://genome.ifmo.ru/files/software/phantasus/tcga/tcga_index.txt')
.then(function (response) {
if (response.ok) {
return response.text();
}
})
.then(function (text) {
var exampleHtml = [];
/*exampleHtml.push('<table class="table table-condensed table-bordered">');
exampleHtml.push('<thead><tr><th>Name</th><th>Gene' +
' Expression</th><th>Copy Number By Gene</th><th>Mutations</th><th>Gene' +
' Essentiality</th><th></th></tr></thead>');
exampleHtml.push('<tbody>');
exampleHtml.push('<tr>');
exampleHtml
.push('<td>Cancer Cell Line Encyclopedia (CCLE), Project Achilles</td>');
exampleHtml
.push('<td><input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="mrna"> </td>');
exampleHtml
.push('<td><input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="cn"> </td>');
exampleHtml
.push('<td><input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="sig_genes"> </td>');
exampleHtml
.push('<td><input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="ach"> </td>');
exampleHtml
.push('<td><button disabled type="button" class="btn btn-link" name="ccle">'
+ options.openText + '</button></td>');
exampleHtml.push('</tr></tbody></table>');*/
exampleHtml.push(
'<div>TCGA data <a target="_blank" href="https://confluence.broadinstitute.org/display/GDAC/Dashboard-Stddata">(Broad GDAC 1/28/2016)</a></div><span>Please adhere to the' +
' <a target="_blank"' +
' href="http://cancergenome.nih.gov/abouttcga/policies/publicationguidelines">TCGA' +
' publication guidelines</a></u> when using TCGA data in your publications.</span>');
exampleHtml.push('<div data-name="tcga"></div>');
$(exampleHtml.join('')).appendTo($el);
if (options.show) {
$el.show();
}
var lines = text.split('\n');
var diseases = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line === '') {
continue;
}
var tokens = line.split('\t');
var type = tokens[0];
var dataTypes = tokens[1].split(',');
var name = phantasus.TcgaUtil.DISEASE_STUDIES[type];
var disease = {
mrna: dataTypes
.indexOf('mRNAseq_RSEM_normalized_log2.txt') !== -1,
sig_genes: dataTypes.indexOf('sig_genes.txt') !== -1,
gistic: dataTypes
.indexOf('all_lesions.conf_99.txt') !== -1,
sample_info: dataTypes.indexOf('All_CDEs.txt') !== -1,
mutation: dataTypes
.indexOf('mutations_merged.maf.txt') !== -1,
rppa: dataTypes.indexOf('rppa.txt') !== -1,
methylation: dataTypes
.indexOf('meth.by_mean.data.txt') !== -1,
name: name,
type: type,
dataTypes: dataTypes
};
if (disease.mrna || disease.gistic
|| disease.sig_genes || disease.rppa
|| disease.methylation) {
diseases.push(disease);
}
}
diseases.sort(function (a, b) {
a = a.name.toLowerCase();
b = b.name.toLowerCase();
return (a === b ? 0 : (a < b ? -1 : 1));
});
var tcga = [];
_this.diseases = diseases;
tcga.push('<table class="table table-condensed table-bordered">');
tcga.push('<thead><tr>');
tcga.push('<th>Disease</th>');
tcga.push('<th>Gene Expression</th>');
tcga.push('<th>GISTIC Copy Number</th>');
tcga.push('<th>Copy Number By Gene</th>');
tcga.push('<th>Mutations</th>');
tcga.push('<th>Proteomics</th>');
tcga.push('<th>Methylation</th>');
tcga.push('<th></th>');
tcga.push('</tr></thead>');
tcga.push('<tbody>');
for (var i = 0; i < diseases.length; i++) {
var disease = diseases[i];
tcga.push('<tr>');
tcga.push('<td>' + disease.name + '</td>');
tcga.push('<td>');
if (disease.mrna) {
tcga
.push('<input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="mrna"> ');
}
tcga.push('</td>');
tcga.push('<td>');
if (disease.gistic) {
tcga
.push('<input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="gistic"> ');
}
tcga.push('</td>');
tcga.push('<td>');
if (disease.gistic) {
tcga
.push('<input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="gisticGene"> ');
}
tcga.push('</td>');
tcga.push('<td>');
if (disease.sig_genes) {
tcga
.push('<input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="sig_genes"> ');
}
tcga.push('</td>');
tcga.push('<td>');
if (disease.rppa) {
tcga
.push('<input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="rppa"> ');
}
tcga.push('</td>');
tcga.push('<td>');
if (disease.methylation) {
tcga
.push('<input type="checkbox" style="margin-left:4px;" data-toggle="dataTypeToggle" data-type="methylation"> ');
}
tcga.push('</td>');
tcga
.push('<td><button disabled type="button" class="btn btn-link" name="tcgaLink" data-disease-type="'
+ disease.type
+ '">'
+ options.openText
+ '</button></td>');
tcga.push('</tr>');
}
tcga.push('</tbody>');
tcga.push('</table>');
$(tcga.join('')).appendTo($el.find('[data-name=tcga]'));
}).catch(function (error) {
console.log(error);
});
};
phantasus.SampleDatasets.getTcgaDataset = function (options) {
var baseUrl = 'https://genome.ifmo.ru/files/software/phantasus/tcga/'
+ options.type + '/';
var datasetOptions = {};
if (options.mrna) {
datasetOptions.mrna = baseUrl + 'mRNAseq_RSEM_normalized_log2.txt';
}
if (options.methylation) {
datasetOptions.methylation = baseUrl + 'meth.by_mean.data.txt';
}
if (options.sig_genes) {
datasetOptions.mutation = baseUrl + 'mutations_merged.maf.txt';
datasetOptions.sigGenes = baseUrl + 'sig_genes.txt';
}
// datasetOptions.seg = baseUrl + 'snp.seg.txt';
if (options.rppa) {
datasetOptions.rppa = baseUrl + 'rppa.txt';
}
if (options.gistic) {
datasetOptions.gistic = baseUrl + 'all_lesions.conf_99.txt';
}
if (options.gisticGene) {
datasetOptions.gisticGene = baseUrl + 'all_data_by_genes.txt';
}
datasetOptions.mrnaClust = baseUrl + 'bestclus.txt';
datasetOptions.columnAnnotations = [
{
file: baseUrl + 'All_CDEs.txt',
datasetField: 'participant_id',
fileField: 'patient_id', // e.g. tcga-5l-aat0
transposed: false
}];
return phantasus.TcgaUtil.getDataset(datasetOptions);
};
phantasus.SampleDatasets.getCCLEDataset = function (options) {
var datasets = [];
if (options.sig_genes) {
datasets.push(
'https://software.broadinstitute.org/morpheus/preloaded-datasets/ccle2maf_081117.maf.txt');
}
if (options.cn) {
datasets.push('https://software.broadinstitute.org/morpheus/preloaded-datasets/CCLE_copynumber_byGene_2013-12-03.gct');
}
if (options.mrna) {
datasets.push('https://software.broadinstitute.org/morpheus/preloaded-datasets/CCLE_expression_081117.rpkm.gct');
}
if (options.ach) {
datasets.push('https://software.broadinstitute.org/morpheus/preloaded-datasets/Achilles_v2.20.2_GeneSolutions.gct');
}
var d = $.Deferred();
var datasetPromise = phantasus.DatasetUtil.readDatasetArray(datasets);
datasetPromise.done(function (dataset) {
var idVector = dataset.getColumnMetadata().get(0);
var siteVector = dataset.getColumnMetadata().add('site');
for (var j = 0, ncols = siteVector.size(); j < ncols; j++) {
var id = idVector.getValue(j);
var index = id.indexOf('_');
if (index !== -1) {
idVector.setValue(j, id.substring(0, index));
siteVector.setValue(j, id.substring(index + 1));
}
}
d.resolve(dataset);
}).fail(function (err) {
d.reject(err);
});
return d;
};
phantasus.SampleDatasets.prototype = {
openTcga: function (options) {
console.log("openTcga", options);
this.callback({
name: options.name,
renderReady: function (heatMap) {
var whitelist = [
'age_at_initial_pathologic_diagnosis',
'breast_carcinoma_estrogen_receptor_status',
'breast_carcinoma_progesterone_receptor_status',
'lab_proc_her2_neu_immunohistochemistry_receptor_status',
'days_to_death', 'ethnicity', 'gender',
'histological_type', 'pathologic_stage'];
var columnMetadata = heatMap.getProject().getFullDataset().getColumnMetadata();
for (var i = 0; i < whitelist.length; i++) {
if (columnMetadata.getByName(whitelist[i])) {
heatMap.addTrack(whitelist[i], true, 'color');
}
}
// view in space of mutation sample ids only
if (options.sig_genes) {
if (heatMap.getTrackIndex('q_value', false) === -1) {
heatMap.addTrack('q_value', false, 'text');
}
}
},
columns: [
{
field: 'participant_id',
display: 'text'
}, {
field: 'sample_type',
display: 'color'
}, {
field: 'mutation_summary',
display: 'stacked_bar'
}, {
field: 'mutation_summary_selection',
display: 'stacked_bar'
}, {
field: 'mRNAseq_cluster',
display: 'color, highlight'
}],
dataset: phantasus.SampleDatasets.getTcgaDataset(options)
});
},
openCCLE: function (options) {
var name = [];
if (options.sig_genes) {
name.push('Mut');
}
if (options.cn) {
name.push('CN');
}
if (options.mrna) {
name.push('Exp');
}
if (options.ach) {
name.push('Ach');
}
this.callback({
rows: [
{
field: 'id',
display: 'text'
}, {
field: 'Description',
display: 'text, tooltip'
}, {
field: 'mutation_summary',
display: 'stacked_bar'
}, {
field: 'Source',
display: 'color'
}],
columns: [
{
field: 'id',
display: 'text,tooltip'
}, {
field: 'mutation_summary',
display: 'stacked_bar'
}, {
field: 'site',
display: 'color, highlight'
}],
dataset: phantasus.SampleDatasets.getCCLEDataset(options),
name: 'CCLE - ' + name.join(', ')
});
}
};
phantasus.SampleDatasets.TCGA_DISEASE_TYPES_INFO = [
{
id: 'mrna',
name: 'GENE EXPRESSION',
type: 'mrna'
}, {
id: 'gistic',
name: 'GISTIC COPY NUMBER',
type: 'gistic'
}, {
id: 'gistic',
name: 'COPY NUMBER BY GENE',
type: 'gisticGene'
}, {
id: 'sig_genes',
name: 'MUTATION',
type: 'sig_genes'
}, {
id: 'rppa',
name: 'PROTEOMICS',
type: 'rppa'
}, {
id: 'methylation',
name: 'METHYLATION',
type: 'methylation'
}];
phantasus.AdjustDataTool = function () {
};
phantasus.AdjustDataTool.prototype = {
toString: function () {
return 'Adjust';
},
init: function (project, form) {
var dataset = project.getFullDataset();
var _this = this;
var filterNumeric = function (metadata, currentName) {
var meta = metadata.getByName(currentName);
var type = phantasus.VectorUtil.getDataType(meta);
return type === 'number' || type === '[number]'
};
var numericRows = phantasus.MetadataUtil.getMetadataNames(dataset.getRowMetadata()).filter(filterNumeric.bind(null,dataset.getRowMetadata()));
var numericColumns = phantasus.MetadataUtil.getMetadataNames(dataset.getColumnMetadata()).filter(filterNumeric.bind(null,dataset.getColumnMetadata()));
var rows = ['(None)'].concat(numericRows);
var columns = ['(None)'].concat(numericColumns);
this.sweepRowColumnSelect = form.$form.find('[name=sweep-row-column]');
this.sweepAction = form.$form.find('[name=sweep-action]');
this.sweepTarget = form.$form.find('[name=sweep-target]');
this.sweepTarget.on('change', function (e) {
var mode = e.currentTarget.value;
_this.sweepRowColumnSelect.empty();
$.each(mode === 'row' ? rows : columns, function(key,value) {
_this.sweepRowColumnSelect.append($("<option></option>")
.attr("value", value).text(value));
});
});
this.sweepAction.on('change', function (e) {
var action = e.currentTarget.value;
form.$form.find('#Sweep-first-divider').text(
action === 'Divide' ? 'each' : 'from each'
);
form.$form.find('#Sweep-second-divider').text(
action === 'Divide' ? 'by field:' : 'field:'
);
});
form.$form.find('[name=scale_column_sum]').on('change', function (e) {
form.setVisible('column_sum', form.getValue('scale_column_sum'));
});
form.setVisible('column_sum', false);
this.sweepTarget.trigger('change');
},
gui: function () {
// z-score, robust z-score, log2, inverse
return [{
name: 'warning',
showLabel: false,
type: 'custom',
value: 'Operations are performed in order listed'
}, {
name: 'scale_column_sum',
type: 'checkbox',
help: 'Whether to scale each column sum to a specified value'
}, {
name: 'column_sum',
type: 'text',
style: 'max-width:150px;'
}, {
name: 'log_2',
type: 'checkbox'
}, {
name: 'one_plus_log_2',
type: 'checkbox',
help: 'Take log2(1 + x)'
}, {
name: 'inverse_log_2',
type: 'checkbox'
}, {
name: 'quantile_normalize',
type: 'checkbox'
}, {
name: 'z-score',
type: 'checkbox',
help: 'Subtract mean, divide by standard deviation'
}, {
name: 'robust_z-score',
type: 'checkbox',
help: 'Subtract median, divide by median absolute deviation'
}, {
name: 'Sweep',
type: 'triple-select',
firstName: 'sweep-action',
firstOptions: ['Divide', 'Subtract'],
firstDivider: 'each',
secondName: 'sweep-target',
secondOptions: ['row', 'column'],
secondDivider: 'by field:',
thirdName: 'sweep-row-column',
thirdOptions: [],
comboboxStyle: 'display: inline-block; width: auto; padding: 0; margin-left: 2px; margin-right: 2px; height: 25px; max-width: 120px;',
value: '',
showLabel: false
}];
},
execute: function (options) {
var project = options.project;
var heatMap = options.heatMap;
var sweepBy = (_.size(this.sweepRowColumnSelect) > 0) ? this.sweepRowColumnSelect[0].value : '(None)';
if (!options.input.log_2 &&
!options.input.inverse_log_2 &&
!options.input['z-score'] &&
!options.input['robust_z-score'] &&
!options.input.quantile_normalize &&
!options.input.scale_column_sum &&
!options.input.one_plus_log_2 &&
sweepBy === '(None)') {
// No action selected;
return;
}
// clone the values 1st
var sortedFilteredDataset = phantasus.DatasetUtil.copy(project
.getSortedFilteredDataset());
var rowIndices = project
.getRowSelectionModel()
.getViewIndices()
.values().sort(
function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
if (rowIndices.length === 0) {
rowIndices = null;
}
var columnIndices = project
.getColumnSelectionModel()
.getViewIndices()
.values()
.sort(
function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
if (columnIndices.length === 0) {
columnIndices = null;
}
var dataset = sortedFilteredDataset;
var rowView = new phantasus.DatasetRowView(dataset);
var functions = {};
if (options.input.scale_column_sum) {
var scaleToValue = parseFloat(options.input.column_sum);
functions.scaleColumnSum = scaleToValue;
if (!isNaN(scaleToValue)) {
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var sum = 0;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
var value = dataset.getValue(i, j);
if (!isNaN(value)) {
sum += value;
}
}
var ratio = scaleToValue / sum;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
var value = dataset.getValue(i, j);
dataset.setValue(i, j, value * ratio);
}
}
}
}
if (options.input.log_2) {
functions.log2 = true;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
dataset.setValue(i, j, phantasus.Log2(dataset.getValue(
i, j)));
}
}
}
if (options.input.one_plus_log_2) {
functions.onePlusLog2 = true;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
dataset.setValue(i, j, phantasus.Log2(dataset.getValue(
i, j) + 1));
}
}
}
if (options.input.inverse_log_2) {
functions.inverseLog2 = true;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
dataset.setValue(i, j, Math.pow(2, dataset.getValue(i, j)));
}
}
}
if (options.input.quantile_normalize) {
functions.quantileNormalize = true;
phantasus.QNorm.execute(dataset);
}
if (options.input['z-score']) {
functions.zScore = true;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
rowView.setIndex(i);
var mean = phantasus.Mean(rowView);
var stdev = Math.sqrt(phantasus.Variance(rowView));
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
dataset.setValue(i, j, (dataset.getValue(i, j) - mean)
/ stdev);
}
}
}
if (options.input['robust_z-score']) {
functions.robustZScore = true;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
rowView.setIndex(i);
var median = phantasus.Median(rowView);
var mad = phantasus.MAD(rowView, median);
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
dataset.setValue(i, j,
(dataset.getValue(i, j) - median) / mad);
}
}
}
if (sweepBy !== '(None)') {
functions.sweep = {};
var op = this.sweepAction[0].value === 'Subtract' ?
function (a,b) {return a - b; } :
function (a,b) {return a / b; } ;
var mode = this.sweepTarget[0].value;
var sweepVector = mode === 'row' ?
dataset.getRowMetadata().getByName(sweepBy) :
dataset.getColumnMetadata().getByName(sweepBy);
functions.sweep.mode = mode;
functions.sweep.name = sweepBy;
functions.sweep.op = this.sweepAction[0].value === 'Subtract' ? '-':'/';
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
var value = dataset.getValue(i, j);
if (!isNaN(value)) {
var operand = sweepVector.getValue(mode === 'row' ? i : j);
dataset.setValue(i, j, op(value, operand));
}
}
}
}
var currentSessionPromise = dataset.getESSession();
if (currentSessionPromise) {
dataset.setESSession(new Promise(function (resolve, reject) {
currentSessionPromise.then(function (essession) {
functions.es = essession;
var req = ocpu.call("adjustDataset", functions, function (newSession) {
resolve(newSession);
}, false, "::es");
req.fail(function () {
reject();
throw new Error("adjustDataset call to OpenCPU failed" + req.responseText);
});
});
}));
}
if (options.rawDataset) {
return dataset;
}
return new phantasus.HeatMap({
name: heatMap.getName(),
dataset: dataset,
parent: heatMap,
symmetric: project.isSymmetric() && dataset.getColumnCount() === dataset.getRowCount()
});
}
};
phantasus.AnnotateDatasetTool = function (options) {
this.options = options || {target: 'Rows'};
};
phantasus.AnnotateDatasetTool.prototype = {
toString: function () {
return 'Annotate ' + this.options.target.toString();
},
gui: function () {
var array = [];
array.push({
name: 'file',
showLabel: false,
placeholder: 'Open your own file',
value: '',
type: 'file',
required: true,
allowedInputs: {
computer: true,
url: true
}
});
array.options = {
ok: this.options.file != null,
size: 'modal-lg'
};
return array;
},
init: function (project, form) {
var _this = this;
form.on('change', function (e) {
var value = e.value;
if (value !== '' && value != null) {
form.setValue('file', value);
_this.options.file = value;
_this.ok();
}
});
},
execute: function (options) {
var _this = this;
var isInteractive = this.options.file == null;
var heatMap = options.heatMap;
if (!isInteractive) {
options.input.file = this.options.file;
}
if (options.input.file.isGEO) {
options.input.isGEO = options.input.file.isGEO;
options.input.file = options.input.file.name;
}
if (options.input.file.preloaded) {
options.input.preloaded = options.input.file.preloaded;
options.input.file = options.input.file.name;
}
var project = options.project;
var d = $.Deferred();
var isAnnotateColumns = this.options.target !== 'Rows';
var fileOrUrl = options.input.file;
var dataset = project.getFullDataset();
var fileName = phantasus.Util.getFileName(fileOrUrl);
if (phantasus.Util.endsWith(fileName, '.cls')) {
var result = phantasus.Util.readLines(fileOrUrl);
result.always(function () {
d.resolve();
});
result.done(function (lines) {
_this.annotateCls(heatMap, dataset, fileName,
isAnnotateColumns, lines);
});
} else if (phantasus.Util.endsWith(fileName, '.gmt')) {
phantasus.ArrayBufferReader.getArrayBuffer(fileOrUrl, function (
err,
buf) {
d.resolve();
if (err) {
throw new Error('Unable to read ' + fileOrUrl);
}
var sets = new phantasus.GmtReader().read(
new phantasus.ArrayBufferReader(new Uint8Array(
buf)));
_this.promptSets(dataset, heatMap, isAnnotateColumns,
sets, phantasus.Util.getBaseFileName(
phantasus.Util.getFileName(fileOrUrl)));
});
} else {
var result = phantasus.Util.readLines(fileOrUrl);
result.done(function (lines) {
_this.prompt(lines, dataset, heatMap, isAnnotateColumns);
}).always(function () {
d.resolve();
});
return d;
}
},
annotateCls: function (heatMap, dataset, fileName, isColumns, lines) {
if (isColumns) {
dataset = phantasus.DatasetUtil.transposedView(dataset);
}
var assignments = new phantasus.ClsReader().read(lines);
if (assignments.length !== dataset.getRowCount()) {
throw new Error(
'Number of samples in cls file does not match dataset.');
}
var vector = dataset.getRowMetadata().add(
phantasus.Util.getBaseFileName(fileName));
for (var i = 0; i < assignments.length; i++) {
vector.setValue(i, assignments[i]);
}
if (heatMap) {
heatMap.getProject().trigger('trackChanged', {
vectors: [vector],
display: ['color'],
columns: isColumns
});
}
},
annotateSets: function (dataset, isColumns, sets,
datasetMetadataName, setSourceFileName) {
if (isColumns) {
dataset = phantasus.DatasetUtil.transposedView(dataset);
}
var vector = dataset.getRowMetadata().getByName(datasetMetadataName);
var idToIndices = phantasus.VectorUtil.createValueToIndicesMap(vector);
var setVector = dataset.getRowMetadata().add(setSourceFileName);
sets.forEach(function (set) {
var name = set.name;
var members = set.ids;
members.forEach(function (id) {
var indices = idToIndices.get(id);
if (indices !== undefined) {
for (var i = 0, nIndices = indices.length; i < nIndices; i++) {
var array = setVector.getValue(indices[i]);
if (array === undefined) {
array = [];
}
array.push(name);
setVector.setValue(indices[i], array);
}
}
});
});
return setVector;
},
/**
*
* @param lines
* Lines of text in annotation file or null if a gmt file
* @param dataset
* Current dataset
* @param isColumns
* Whether annotating columns
* @param sets
* Sets if a gmt file or null
* @param metadataName
* The dataset metadata name to match on
* @param fileColumnName
* The metadata file name to match on
* @param fileColumnNamesToInclude
* An array of column names to include from the metadata file or
* null to include all
* @param tranposed For text/Excel files only. If <code>true</code>, different annotations are on each row.
*/
annotate: function (lines, dataset, isColumns, sets, metadataName,
fileColumnName, fileColumnNamesToInclude, transposed) {
if (isColumns) {
dataset = phantasus.DatasetUtil.transposedView(dataset);
}
var vector = dataset.getRowMetadata().getByName(metadataName);
if (!vector) {
throw new Error('vector ' + metadataName + ' not found.');
}
var fileColumnNamesToIncludeSet = null;
if (fileColumnNamesToInclude) {
fileColumnNamesToIncludeSet = new phantasus.Set();
fileColumnNamesToInclude.forEach(function (name) {
fileColumnNamesToIncludeSet.add(name);
});
}
var vectors = [];
var idToIndices = phantasus.VectorUtil.createValueToIndicesMap(vector);
if (!lines) {
_.each(
sets,
function (set) {
var name = set.name;
var members = set.ids;
var v = dataset.getRowMetadata().add(name);
vectors.push(v);
_.each(
members,
function (id) {
var indices = idToIndices.get(id);
if (indices !== undefined) {
for (var i = 0, nIndices = indices.length; i < nIndices; i++) {
v.setValue(
indices[i],
name);
}
}
});
});
} else {
var tab = /\t/;
if (!transposed) {
var header = lines[0].split(tab);
var fileMatchOnColumnIndex = _.indexOf(header, fileColumnName);
if (fileMatchOnColumnIndex === -1) {
throw new Error(fileColumnName + ' not found in header:'
+ header);
}
var columnIndices = [];
var nheaders = header.length;
for (var j = 0; j < nheaders; j++) {
var name = header[j];
if (j === fileMatchOnColumnIndex) {
continue;
}
if (fileColumnNamesToIncludeSet
&& !fileColumnNamesToIncludeSet.has(name)) {
continue;
}
var v = dataset.getRowMetadata().getByName(name);
if (!v) {
v = dataset.getRowMetadata().add(name);
}
columnIndices.push(j);
vectors.push(v);
}
var nheaders = columnIndices.length;
for (var i = 1, nrows = lines.length; i < nrows; i++) {
var line = lines[i].split(tab);
var id = line[fileMatchOnColumnIndex];
var indices = idToIndices.get(id);
if (indices !== undefined) {
var nIndices = indices.length;
for (var j = 0; j < nheaders; j++) {
var token = line[columnIndices[j]];
var v = vectors[j];
for (var r = 0; r < nIndices; r++) {
v.setValue(indices[r], token);
}
}
}
}
}
else {
// transposed
var splitLines = [];
var matchOnLine;
for (var i = 0, nrows = lines.length; i < nrows; i++) {
var line = lines[i].split(tab);
var name = line[0];
if (fileColumnName === name) {
matchOnLine = line;
} else {
if (fileColumnNamesToIncludeSet
&& !fileColumnNamesToIncludeSet.has(name)) {
continue;
}
splitLines.push(line);
var v = dataset.getRowMetadata().getByName(name);
if (!v) {
v = dataset.getRowMetadata().add(name);
}
vectors.push(v);
}
}
if (matchOnLine == null) {
throw new Error(fileColumnName + ' not found in header.');
}
for (var fileColumnIndex = 1, ncols = matchOnLine.length; fileColumnIndex < ncols; fileColumnIndex++) {
var id = matchOnLine[fileColumnIndex];
var indices = idToIndices.get(id);
if (indices !== undefined) {
var nIndices = indices.length;
for (var j = 0; j < splitLines.length; j++) {
var token = splitLines[j][fileColumnIndex];
var v = vectors[j];
for (var r = 0; r < nIndices; r++) {
v.setValue(indices[r], token);
}
}
}
}
}
}
for (var i = 0; i < vectors.length; i++) {
phantasus.VectorUtil.maybeConvertStringToNumber(vectors[i]);
}
return vectors;
},
// prompt for metadata field name in dataset
promptSets: function (dataset, heatMap, isColumns, sets, setSourceFileName) {
var promptTool = {};
var _this = this;
promptTool.execute = function (options) {
var metadataName = options.input.dataset_field_name;
var vector = _this.annotateSets(dataset, isColumns, sets,
metadataName, setSourceFileName);
heatMap.getProject().trigger('trackChanged', {
vectors: [vector],
display: ['text'],
columns: isColumns
});
};
promptTool.toString = function () {
return 'Select Fields To Match On';
};
promptTool.gui = function () {
return [
{
name: 'dataset_field_name',
options: phantasus.MetadataUtil.getMetadataNames(
isColumns ? dataset.getColumnMetadata() : dataset.getRowMetadata()),
type: 'select',
value: 'id',
required: true
}];
};
phantasus.HeatMap.showTool(promptTool, heatMap);
},
prompt: function (lines, dataset, heatMap, isColumns) {
var promptTool = {};
var _this = this;
var header = lines != null ? lines[0].split('\t') : null;
promptTool.execute = function (options) {
var metadataName = options.input.dataset_field_name;
var fileColumnName = options.input.file_field_name;
var vectors = _this.annotate(lines, dataset, isColumns, null,
metadataName, fileColumnName);
var nameToIndex = new phantasus.Map();
var display = [];
for (var i = 0; i < vectors.length; i++) {
display.push(isColumns ? 'color' : 'text');
nameToIndex.set(vectors[i].getName(), i);
}
if (lines.colors) {
var colorModel = isColumns
? heatMap.getProject().getColumnColorModel()
: heatMap.getProject().getRowColorModel();
lines.colors.forEach(function (item) {
var index = nameToIndex.get(item.header);
var vector = vectors[index];
display[index] = 'color';
colorModel.setMappedValue(vector, item.value, item.color);
});
}
heatMap.getProject().trigger('trackChanged', {
vectors: vectors,
display: display,
columns: isColumns
});
};
promptTool.toString = function () {
return 'Select Fields To Match On';
};
promptTool.gui = function () {
var items = [
{
name: 'dataset_field_name',
options: phantasus.MetadataUtil.getMetadataNames(
isColumns ? dataset.getColumnMetadata() : dataset.getRowMetadata()),
type: 'select',
required: true
}];
if (lines) {
items.push({
name: 'file_field_name',
type: 'select',
options: _.map(header, function (item) {
return {
name: item,
value: item
};
}),
required: true
});
}
return items;
};
phantasus.HeatMap.showTool(promptTool, heatMap);
}
};
phantasus.AnnotateDendrogramTool = function (isColumns) {
this._isColumns = isColumns;
};
phantasus.AnnotateDendrogramTool.prototype = {
toString: function () {
return 'Annotate Dendrogram';
},
gui: function () {
return [{
name: 'file',
value: '',
type: 'file',
required: true,
help: 'an xlsx file or a tab-delimitted text file'
}];
},
execute: function (options) {
var fileOrUrl = options.input.file;
var isColumns = this._isColumns;
var heatMap = options.heatMap;
var result = phantasus.Util.readLines(fileOrUrl);
var fileName = phantasus.Util.getFileName(fileOrUrl);
var dendrogram = isColumns ? heatMap.columnDendrogram
: heatMap.rowDendrogram;
var nameToNode = new phantasus.Map();
phantasus.DendrogramUtil.dfs(dendrogram.tree.rootNode,
function (node) {
nameToNode.set(node.name, node);
return true;
});
var tab = /\t/;
result.done(function (lines) {
var header = lines[0].split(tab);
var promptTool = {};
// node.info = {foo:['a', 'b'], bar:[3]}
promptTool.execute = function (options) {
var nodeIdField = options.input.node_id_field;
var nodeIdIndex = _.indexOf(header, nodeIdField);
var numberOfMatchingNodes = 0;
for (var i = 1; i < lines.length; i++) {
var array = lines[i].split(tab);
var nodeName = array[nodeIdIndex];
var node = nameToNode.get(nodeName);
if (node !== undefined) {
numberOfMatchingNodes++;
var info = node.info || (node.info = {});
for (var j = 0; j < array.length; j++) {
if (j === nodeIdIndex) {
continue;
}
var vals = info[header[j]];
if (vals === undefined) {
vals = [];
info[header[j]] = vals;
}
vals.push(array[j]);
}
}
}
heatMap.trigger('dendrogramAnnotated', {
isColumns: isColumns
});
dendrogram.setInvalid(true);
dendrogram.repaint();
};
promptTool.toString = function () {
return 'Select Node Id Field';
};
promptTool.gui = function () {
return [{
name: 'node_id_field',
type: 'select',
options: _.map(header, function (item) {
return {
name: item,
value: item
};
}),
required: true
}];
};
phantasus.HeatMap.showTool(promptTool, heatMap);
});
}
};
phantasus.annotationDBMeta = {
init: false,
dbs: {}
};
phantasus.initAnnotationConvertTool = function (options) {
if (!phantasus.annotationDBMeta.init) {
var $el = $('<div style="background:white;" title="Init"><h5>Loading AnnotationDB meta information</h5></div>');
phantasus.Util.createLoadingEl().appendTo($el);
$el.dialog({
resizable: false,
height: 150,
width: 300
});
var req = ocpu.call("queryAnnotationDBMeta/print", {}, function (newSession) {
var result = JSON.parse(newSession.txt);
phantasus.annotationDBMeta.init = true;
phantasus.annotationDBMeta.dbs = result;
$el.dialog('destroy').remove();
new phantasus.AnnotationConvertTool(options.heatMap);
});
req.fail(function () {
$el.dialog('destroy').remove();
throw new Error("Couldn't load Annotation DB meta information. Please try again in a moment. Error:" + req.responseText);
});
} else {
new phantasus.AnnotationConvertTool(options.heatMap);
}
};
phantasus.AnnotationConvertTool = function (heatMap) {
var self = this;
var project = heatMap.getProject();
if (phantasus.annotationDBMeta.init && !_.size(phantasus.annotationDBMeta.dbs)) {
throw new Error('There is no AnnotationDB on server. Ask administrator to put AnnotationDB sqlite databases in cacheDir/annotationdb folder');
}
var names = _.map(phantasus.annotationDBMeta.dbs, function (value, dbName) {
return dbName + ' - ' + value.species.toString();
});
var rowMetadata = project.getFullDataset().getRowMetadata();
var featureColumns = phantasus
.MetadataUtil
.getMetadataNames(rowMetadata);
if (!_.size(featureColumns)) {
throw new Error('There is no columns in feature data');
}
var firstDBName = _.first(names).split(' - ')[0];
var $dialog = $('<div style="background:white;" title="' + this.toString() + '"></div>');
var form = new phantasus.FormBuilder({
formStyle: 'vertical'
});
[{
name: 'specimen_DB',
type: 'select',
options: names,
value: _.first(names)
}, {
name: 'source_column',
type: 'select',
options: featureColumns,
value: _.first(featureColumns)
}, {
name: 'source_column_type',
type: 'select',
options: phantasus.annotationDBMeta.dbs[firstDBName].columns,
value: _.first(phantasus.annotationDBMeta.dbs[firstDBName].columns)
}, {
name: 'result_column_type',
type: 'select',
options: phantasus.annotationDBMeta.dbs[firstDBName].columns,
value: _.first(phantasus.annotationDBMeta.dbs[firstDBName].columns)
}].forEach(function (a) {
form.append(a);
});
form.$form.appendTo($dialog);
form.$form.find('[name=specimen_DB]').on('change', function (e) {
var newVal = $(this).val();
var newDb = newVal.split(' - ')[0];
var newColumns = phantasus.annotationDBMeta.dbs[newDb].columns;
form.setOptions('source_column_type', newColumns, true);
form.setOptions('result_column_type', newColumns, true);
});
$dialog.dialog({
close: function (event, ui) {
$dialog.dialog('destroy').remove();
},
resizable: true,
height: 450,
width: 600,
buttons: [
{
text: "Cancel",
click: function () {
$(this).dialog("destroy").remove();
}
},
{
text: "Submit",
click: function () {
var $dialogContent = $('<div><span>' + self.toString() + '...</span><button class="btn' +
' btn-xs btn-default" style="margin-left:6px;display: none;">Cancel</button></div>');
var $dialog = phantasus.FormBuilder.showInDraggableDiv({
$content: $dialogContent,
appendTo: heatMap.getContentEl(),
width: 'auto'
});
self.execute({
project: project,
form: form
}).always(function () {
$dialog.remove();
});
$(this).dialog("destroy").remove();
}
}
]
});
this.$dialog = $dialog;
};
phantasus.AnnotationConvertTool.prototype = {
toString: function () {
return "Annotate from AnnotationDB";
},
execute: function (options) {
var project = options.project;
var dataset = project.getFullDataset();
var promise = $.Deferred();
var selectedFeatureName = options.form.getValue('source_column');
var selectedDB = options.form.getValue('specimen_DB').split(' - ')[0];
var columnType = options.form.getValue('source_column_type').split(' - ')[0];
var keyType = options.form.getValue('result_column_type').split(' - ')[0];
if (columnType === keyType) {
throw new Error('Converting column from ' + columnType + ' to ' + keyType + ' is invalid');
}
dataset.getESSession().then(function (essession) {
var args = {
es: essession,
dbName: selectedDB,
columnName: selectedFeatureName,
columnType: columnType,
keyType: keyType
};
var req = ocpu.call("convertByAnnotationDB/print", args, function (newSession) {
var result = JSON.parse(newSession.txt);
var v = dataset.getRowMetadata().add(keyType);
for (var i = 0; i < dataset.getRowCount(); i++) {
v.setValue(i, (result[i] || 'NA').toString());
}
v.getProperties().set("phantasus.dataType", "string");
dataset.setESSession(Promise.resolve(newSession));
project.trigger("trackChanged", {
vectors: [v],
display: []
});
promise.resolve();
}, false, "::es");
req.fail(function () {
promise.reject();
throw new Error("Could not annotate dataset. Please double check your parameters or contact administrator. Error: " + req.responseText);
});
});
return promise;
}
};
/**
* @param chartOptions.project
* phantasus.Project
* @param chartOptions.getVisibleTrackNames
* {Function}
*/
phantasus.ChartTool = function (chartOptions) {
var _this = this;
this.getVisibleTrackNames = chartOptions.getVisibleTrackNames;
this.project = chartOptions.project;
var project = this.project;
this.$el = $('<div class="container-fluid">'
+ '<div class="row" style="height: 100%">'
+ '<div data-name="configPane" class="col-xs-2"></div>'
+ '<div class="col-xs-10" style="height: 90%"><div style="position:relative; height: 100%" data-name="chartDiv"></div></div>'
+ '</div></div>');
var formBuilder = new phantasus.FormBuilder({
formStyle: 'vertical'
});
this.formBuilder = formBuilder;
formBuilder.append({
name: 'chart_type',
type: 'bootstrap-select',
options: ["row profile", "column profile", 'boxplot']
});
var rowOptions = [];
var columnOptions = [];
var numericRowOptions = [];
var numericColumnOptions = [];
var unitedColumnsRowsOptions = [];
var options = [];
var numericOptions = [];
var updateOptions = function () {
var dataset = project.getFullDataset();
rowOptions = [{
name: '(None)',
value: ''
}];
columnOptions = [{
name: '(None)',
value: ''
}];
numericRowOptions = [{
name: '(None)',
value: ''
}];
numericColumnOptions = [{
name: '(None)',
value: ''
}];
options = [{
name: '(None)',
value: ''
}];
numericOptions = [{
name: '(None)',
value: ''
}];
phantasus.MetadataUtil.getMetadataNames(dataset.getRowMetadata())
.forEach(
function (name) {
var dataType = phantasus.VectorUtil
.getDataType(dataset.getRowMetadata()
.getByName(name));
if (dataType === 'number'
|| dataType === '[number]') {
numericRowOptions.push({
name: name + ' (row)',
value: name + '_r'
});
}
rowOptions.push({
name: name + ' (row)',
value: name + '_r'
});
});
phantasus.MetadataUtil.getMetadataNames(dataset.getColumnMetadata())
.forEach(
function (name) {
var dataType = phantasus.VectorUtil
.getDataType(dataset.getColumnMetadata()
.getByName(name));
if (dataType === 'number'
|| dataType === '[number]') {
numericColumnOptions.push({
name: name + ' (column)',
value: name + '_c'
});
}
columnOptions.push({
name: name + ' (column)',
value: name + '_c'
});
});
options = options.concat(rowOptions.slice(1));
options = options.concat(columnOptions.slice(1));
numericOptions = numericOptions.concat(numericRowOptions.slice(1));
numericOptions = numericOptions.concat(numericColumnOptions.slice(1));
unitedColumnsRowsOptions = columnOptions.concat(rowOptions.slice(1));
};
updateOptions();
formBuilder.append({
name: 'group_by',
type: 'bootstrap-select',
options: unitedColumnsRowsOptions
});
formBuilder.append({
name: 'box_show_points',
type: 'bootstrap-select',
options: [{name: '(None)', value: ""}, 'all', 'outliers'],
title: 'Show points'
});
formBuilder.append({
name: 'axis_label',
type: 'bootstrap-select',
options: rowOptions
});
formBuilder.append({
name: 'show_points',
type: 'checkbox',
value: true
});
formBuilder.append({
name: 'show_lines',
type: 'checkbox',
value: true
});
formBuilder.append({
name: 'add_profile',
type: 'bootstrap-select',
options: [{
name: "(None)",
value: ""
},{
name: "mean",
value: "mean"
}, {
name: "median",
value: "median"
}]
});
formBuilder.append({
name: 'color',
type: 'bootstrap-select',
options: unitedColumnsRowsOptions
});
formBuilder.append({
name: 'tooltip',
type: 'bootstrap-select',
multiple: true,
search: true,
options: options.slice(1)
});
formBuilder.append({
name: "adjust_data",
title: "Adjust Data",
type: 'collapsed-checkboxes',
showLabel: false,
checkboxes: [{
name: 'log2'
}, {
name: 'z-score',
title: 'Z-Score'
}]
});
formBuilder.append({
name: 'export_to_SVG',
type: 'button'
});
function setVisibility() {
var chartType = formBuilder.getValue('chart_type');
formBuilder.setVisible('axis_label', chartType !== 'boxplot');
formBuilder.setVisible('color', chartType !== 'boxplot');
formBuilder.setVisible('tooltip', chartType !== 'boxplot');
formBuilder.setVisible('add_profile', chartType !== 'boxplot');
formBuilder.setVisible('show_points', chartType !== 'boxplot');
formBuilder.setVisible('show_lines', chartType !== 'boxplot');
formBuilder.setVisible('group_by', chartType === 'boxplot');
formBuilder.setVisible('box_show_points', chartType === 'boxplot');
if (chartType === 'column profile' || chartType === 'row profile') {
formBuilder.setOptions('axis_label', chartType === 'column profile' ? rowOptions : columnOptions,
true);
}
}
this.tooltip = [];
var draw = _.debounce(this.draw.bind(this), 100);
formBuilder.$form.on('change', 'select', function (e) {
if ($(this).attr('name') === 'tooltip') {
var tooltipVal = _this.formBuilder.getValue('tooltip');
_this.tooltip.length = 0; // clear array
if (tooltipVal != null) {
tooltipVal.forEach(function (tip) {
_this.tooltip.push(phantasus.ChartTool.getVectorInfo(tip));
});
}
} else {
setVisibility();
draw();
}
});
formBuilder.$form.on('click', 'input[type=checkbox]', function (e) {
draw();
});
setVisibility();
var selectionChanged = function () {
var selected = project.getRowSelectionModel().count();
if (selected >= 100) {
if (!phantasus.ChartTool.prototype.warningShown) {
phantasus.FormBuilder.showInModal({
title: 'Warning',
html: 'Selected 100 or more genes in dataset. Lines and points were automatically disabled due to performance concerns. You can enable them by yourself. Process with caution.',
z: 10000
});
phantasus.ChartTool.prototype.warningShown = true;
}
formBuilder.setValue('show_points', false);
formBuilder.setValue('show_lines', false);
}
draw();
};
var trackChanged = function () {
updateOptions();
setVisibility();
selectionChanged();
formBuilder.setOptions('group_columns_by', options, true);
formBuilder.setOptions('group_rows_by', options, true);
};
project.getColumnSelectionModel().on('selectionChanged.chart', selectionChanged);
project.getRowSelectionModel().on('selectionChanged.chart', selectionChanged);
project.on('trackChanged.chart', trackChanged);
var $dialog = $('<div style="background:white;" title="Chart"></div>');
var $configPane = this.$el.find('[data-name=configPane]');
this.$chart = this.$el.find('[data-name=chartDiv]');
var $chart = $('<div></div>');
$chart.appendTo(this.$chart);
formBuilder.$form.appendTo($configPane);
this.$el.appendTo($dialog);
this.exportButton = this.$el.find('button[name=export_to_SVG]');
this.exportButton.on('click', function () {
var svgs = _this.$el.find("svg");
if (svgs.length < 1) {
throw Error('Chart is not ready. Cannot export')
}
var svgx = svgs[0].cloneNode(true);
svgs[1].childNodes.forEach(function (x) {
svgx.appendChild(x.cloneNode(true));
});
$(svgx).find('.drag').remove();
phantasus.Util.saveAsSVG(svgx, "chart.svg");
});
$dialog.dialog({
dialogClass: 'phantasus',
close: function (event, ui) {
event.stopPropagation();
$(this).dialog('destroy');
project.off('trackChanged.chart', trackChanged);
project.getRowSelectionModel().off('selectionChanged.chart', draw);
project.getColumnSelectionModel().off('selectionChanged.chart',
draw);
},
resizable: true,
height: 580,
width: 900
});
this.$dialog = $dialog;
this.draw();
};
phantasus.ChartTool.BUTTONS_TO_REMOVE_FOR_STATIC_CHART = ['select2d', 'lasso2d']; // ['zoom2d', 'pan2d', 'select2d', 'lasso2d', 'autoScale2d', 'resetScale2d'];
phantasus.ChartTool.getPlotlyDefaults = function () {
var layout = {
hovermode: 'closest',
autosize: true,
// paper_bgcolor: 'rgb(255,255,255)',
// plot_bgcolor: 'rgb(229,229,229)',
showlegend: false,
margin: {
b: 40,
l: 60,
t: 25,
r: 10,
autoexpand: true
},
titlefont: {
size: 12
},
xaxis: {
zeroline: false,
titlefont: {
size: 12
},
// gridcolor: 'rgb(255,255,255)',
showgrid: true,
// showline: true,
showticklabels: true,
tickcolor: 'rgb(127,127,127)',
ticks: 'outside'
},
yaxis: {
zeroline: false,
titlefont: {
size: 12
},
// gridcolor: 'rgb(255,255,255)',
showgrid: true,
// showline: true,
showticklabels: true,
tickcolor: 'rgb(127,127,127)',
ticks: 'outside'
}
};
var config = {
modeBarButtonsToAdd: [],
showLink: false,
displayModeBar: true, // always show modebar
displaylogo: false,
staticPlot: false,
showHints: true,
doubleClick: "reset",
modeBarButtonsToRemove: ['sendDataToCloud', 'zoomIn2d', 'zoomOut2d', 'hoverCompareCartesian', 'hoverClosestCartesian', 'autoScale2d']
};
return {
layout: layout,
config: config
};
};
phantasus.ChartTool.getVectorInfo = function (value) {
var field = value.substring(0, value.length - 2);
var isColumns = value.substring(value.length - 2) === '_c';
return {
field: field,
isColumns: isColumns
};
};
phantasus.ChartTool.specialProfilers = {
mean: function (rowView) {
return phantasus.Mean(rowView);
},
median: function (rowView) {
return phantasus.Percentile(rowView, 50);
}
};
phantasus.ChartTool.prototype = {
warningShown: false,
_createProfile: function (options) {
var dataset = options.dataset;
var _this = this;
// only allow coloring by row
var colorByVector = options.colorByVector;
var colorModel = options.colorModel;
var axisLabelVector = options.axisLabelVector;
var addProfile = options.addProfile;
var isColumnChart = options.isColumnChart;
var colorByInfo = options.colorByInfo;
var traceMode = [
options.showLines ? 'lines' : '',
options.showPoints ? 'markers' : ''
] .filter(function (val) { return val.length; })
.join('+') || 'none';
var heatmap = this.heatmap;
var uniqColors = {};
var myPlot = options.myPlot;
var xmin = 0,
xmax = 0,
ymin = 0,
ymax = 0;
var traces = [];
var ticktext = [];
var tickvals = [];
var color = undefined;
if (traceMode !== 'none') {
if (colorByVector) {
_.each(phantasus.VectorUtil.toArray(colorByVector), function (value) {
if (!uniqColors[value]) {
if (colorModel.containsDiscreteColor(colorByVector, value)
&& colorByVector.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
uniqColors[value] = colorModel.getMappedValue(colorByVector, value);
} else if (colorModel.isContinuous(colorByVector)) {
uniqColors[value] = colorModel.getContinuousMappedValue(colorByVector, value);
} else {
uniqColors[value] = phantasus.VectorColorModel.CATEGORY_ALL[_.size(uniqColors) % 60];
}
}
return uniqColors[value]
});
_.each(uniqColors, function (color, categoryName) {
categoryName = phantasus.Util.chunk(categoryName, 10).join('<br>');
traces.push({
x: [1000000], y: [1000000],
marker: {
fillcolor: color,
color: color,
size: 10
},
name: categoryName,
legendgroup: 'colors',
mode: "markers",
type: "scatter",
showlegend: true,
invisiblePoint: true
});
});
}
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
// each row is a new trace
var x = [];
var y = [];
var text = [];
var size = 6;
if (colorByVector) {
if (colorByInfo.isColumns === isColumnChart) {
color = uniqColors[colorByVector.getValue(i)];
} else {
color = [];
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
color.push(uniqColors[colorByVector.getValue(j)])
}
}
}
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
x.push(j);
y.push(dataset.getValue(i, j));
var obj = {
i: i,
j: j
};
obj.toString = function () {
var s = [];
for (var tipIndex = 0; tipIndex < _this.tooltip.length; tipIndex++) {
var tip = _this.tooltip[tipIndex];
if (tip.isColumns) {
phantasus.HeatMapTooltipProvider.vectorToString(dataset.getColumnMetadata().getByName(tip.field),
this.j, s, '<br>');
} else {
phantasus.HeatMapTooltipProvider.vectorToString(dataset.getRowMetadata().getByName(tip.field),
this.i, s, '<br>');
}
}
return s.join('');
};
text.push(obj);
}
var trace = {
x: x,
y: y,
name: colorByVector ? colorByVector.getValue(i) : '',
tickmode: 'array',
marker: {
size: size,
symbol: 'circle',
color: color
},
text: text,
mode: traceMode,
type: 'scatter',
showlegend: false
};
if (colorByVector && colorByInfo.isColumns !== isColumnChart) {
trace.marker.size = 10;
trace.line = {
color: 'rgba(125,125,125,0.35)',
}
}
traces.push(trace);
}
}
if (addProfile) {
var moddedX = [];
var moddedY = [];
var rowView = new phantasus.DatasetColumnView(dataset);
for (var idx = 0, size = dataset.getColumnCount(); idx < size; idx++) {
rowView.setIndex(idx);
var val = phantasus.ChartTool.specialProfilers[addProfile](rowView);
moddedY.push(val);
moddedX.push(idx);
}
_.each(traces, function (trace) {
if (trace.showlegend) return;
trace.opacity = 0.3;
trace.line = trace.line || {};
trace.line.color = colorByInfo.isColumns !== isColumnChart ? 'rgb(125,125,125);' : trace.line.color;
});
traces.push({
x: moddedX,
y: moddedY,
name: addProfile,
tickmode: 'array',
marker: {
color: _.isArray(color) && _.size(color) > 1 ? color : 'rgb(100,100,100)',
shape: 'cross',
size: 10
},
line: {
color: 'rgb(100,100,100)',
width: 4
},
mode: 'lines',
type: 'scatter',
showlegend: true,
legendgroup: 'added_profile'
});
}
_.each(traces, function (trace) {
if (trace.invisiblePoint) return;
xmin = Math.min(xmin, _.min(trace.x));
xmax = Math.max(xmax, _.max(trace.x));
ymin = Math.min(ymin, _.min(trace.y));
ymax = Math.max(ymax, _.max(trace.y));
});
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
ticktext.push(axisLabelVector != null ? axisLabelVector.getValue(j) : '' + j);
tickvals.push(j);
}
options.layout.xaxis.tickvals = tickvals;
options.layout.xaxis.ticktext = ticktext;
options.layout.xaxis.range = [xmin - (xmax - xmin) * 0.15, xmax + (xmax - xmin) * 0.15];
options.layout.xaxis.tickmode = 'array';
options.layout.yaxis.range = [ymin - (ymax - ymin) * 0.15, ymax + (ymax - ymin) * 0.15];
var $parent = $(myPlot).parent();
options.layout.width = $parent.width();
options.layout.height = this.$dialog.height() - 30;
phantasus.ChartTool.newPlot(myPlot, traces, options.layout, options.config);
// myPlot.on('plotly_selected', function (eventData) {
// selection = eventData;
// });
myPlot.on('plotly_legendclick', function () {
return false;
});
myPlot.on('plotly_legenddoubleclick', function () {
return false;
});
var resize = _.debounce(function () {
var width = $parent.width();
var height = _this.$dialog.height() - 30;
Plotly.relayout(myPlot, {
width: width,
height: height
});
}, 500);
this.$dialog.on('dialogresize', resize);
$(myPlot).on('remove', function () {
_this.$dialog.off('dialogresize');
});
},
_createBoxPlot: function (options) {
var _this = this;
var showPoints = options.showPoints;
var myPlot = options.myPlot;
var datasets = options.datasets;
var colors = options.colors;
var ids = options.ids;
var boxData = [];
for (var k = 0, ndatasets = datasets.length; k < ndatasets; k++) {
var dataset = datasets[k];
var id = ids[k];
var values = new Float32Array(dataset.getRowCount() * dataset.getColumnCount());
var counter = 0;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var value = dataset.getValue(i, j);
if (!isNaN(value)) {
values[counter] = value;
counter++;
}
}
}
if (counter !== values.length) {
values = values.slice(0, counter);
}
values.sort();
boxData.push(values);
}
var $parent = $(myPlot).parent();
options.layout.width = $parent.width();
options.layout.height = this.$dialog.height() - 30;
var traces = boxData.map(function (box, index) {
var trace = {
y: box,
type: 'box',
hoverinfo: 'y+text',
boxpoints: showPoints,
name: ids[index],
marker: {
color: colors[index]
}
};
if (showPoints === 'all') {
trace.pointpos = -1.8;
trace.jitter = 0.3;
}
return trace;
});
phantasus.ChartTool.newPlot(myPlot, traces, options.layout, options.config);
var resize = _.debounce(function () {
var width = $parent.width();
var height = _this.$dialog.height() - 30;
Plotly.relayout(myPlot, {
width: width,
height: height
});
}, 500);
this.$dialog.on('dialogresize', resize);
$(myPlot).on('remove', function () {
_this.$dialog.off('dialogresize');
});
},
draw: function () {
var _this = this;
this.$chart.empty();
var myPlot = this.$chart[0];
var plotlyDefaults = phantasus.ChartTool.getPlotlyDefaults();
var layout = plotlyDefaults.layout;
var config = plotlyDefaults.config;
var boxShowPoints = this.formBuilder.getValue('box_show_points');
var showPoints = this.formBuilder.getValue('show_points');
var showLines = this.formBuilder.getValue('show_lines');
var showOutliers = this.formBuilder.getValue('show_outliers');
var addProfile = this.formBuilder.getValue('add_profile');
var adjustData = this.formBuilder.getValue('adjust_data');
var axisLabel = this.formBuilder.getValue('axis_label');
var colorBy = this.formBuilder.getValue('color');
var chartType = this.formBuilder.getValue('chart_type');
var dataset;
if (_.size(adjustData)) {
var log2 = adjustData.indexOf('log2') !== -1;
var zScore = adjustData.indexOf('z-score') !== -1;
var tempheatmap = new phantasus.HeatMap({
dummy: true,
dataset: this.project.getSelectedDataset({
emptyToAll: false
})
});
dataset = new phantasus.AdjustDataTool().execute({
heatMap: tempheatmap,
project: tempheatmap.getProject(),
rawDataset: true,
input: {
log_2: log2,
'z-score': zScore
}
});
} else {
dataset = this.project.getSelectedDataset({
emptyToAll: false
});
}
this.dataset = dataset;
if (dataset.getRowCount() === 0 && dataset.getColumnCount() === 0) {
$('<h4>Please select rows and columns in the heat map.</h4>')
.appendTo(this.$chart);
return;
} else if (dataset.getRowCount() === 0) {
$('<h4>Please select rows in the heat map.</h4>')
.appendTo(this.$chart);
return;
}
if (dataset.getColumnCount() === 0) {
$('<h4>Please select columns in the heat map.</h4>')
.appendTo(this.$chart);
return;
}
var groupBy = this.formBuilder.getValue('group_by');
var colorByInfo = phantasus.ChartTool.getVectorInfo(colorBy);
var colorModel = !colorByInfo.isColumns ?
this.project.getRowColorModel() :
this.project.getColumnColorModel();
var axisLabelInfo = phantasus.ChartTool.getVectorInfo(axisLabel);
var axisLabelVector = axisLabelInfo.isColumns ?
dataset.getColumnMetadata().getByName(axisLabelInfo.field) :
dataset.getRowMetadata().getByName(axisLabelInfo.field);
var colorByVector = colorByInfo.isColumns ?
dataset.getColumnMetadata().getByName(colorByInfo.field) :
dataset.getRowMetadata().getByName(colorByInfo.field);
if (chartType === 'row profile' || chartType === 'column profile') {
showPoints = showPoints && (dataset.getRowCount() * dataset.getColumnCount()) <= 100000;
if (chartType === 'column profile') {
dataset = new phantasus.TransposedDatasetView(dataset);
}
this
._createProfile({
showPoints: showPoints,
showLines: showLines,
isColumnChart: chartType === 'column profile',
axisLabelVector: axisLabelVector,
colorByVector: colorByVector,
colorByInfo: colorByInfo,
colorModel: colorModel,
addProfile: addProfile,
myPlot: myPlot,
dataset: dataset,
config: config,
layout: $
.extend(
true,
{},
layout,
{
showlegend: true,
margin: {
b: 80
},
yaxis: {},
xaxis: {}
})
});
} else {
if (boxShowPoints === '') boxShowPoints = false;
var datasets = [];//1-d array of datasets
var ids = []; // 1-d array of grouping values
var colors = [undefined];
if (groupBy) {
var groupByInfo = phantasus.ChartTool.getVectorInfo(groupBy);
var vector = groupByInfo.isColumns ?
dataset.getColumnMetadata().getByName(groupByInfo.field) :
dataset.getRowMetadata().getByName(groupByInfo.field);
var boxColorModel = !groupByInfo.isColumns ?
this.project.getRowColorModel() :
this.project.getColumnColorModel();
var uniqColors = {};
colors = [];
_.each(phantasus.VectorUtil.toArray(vector), function (value) {
if (!uniqColors[value]) {
if (boxColorModel.containsDiscreteColor(vector, value)
&& vector.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
uniqColors[value] = boxColorModel.getMappedValue(vector, value);
} else if (boxColorModel.isContinuous(vector)) {
uniqColors[value] = boxColorModel.getContinuousMappedValue(vector, value);
} else {
uniqColors[value] = phantasus.VectorColorModel.CATEGORY_ALL[_.size(uniqColors) % 60];
}
}
return uniqColors[value]
});
var valueToIndices = phantasus.VectorUtil.createValueToIndicesMap(vector, true);
valueToIndices.forEach(function (indices, value) {
datasets.push(new phantasus.SlicedDatasetView(dataset,
groupByInfo.isColumns ? null : indices,
groupByInfo.isColumns ? indices : null)
);
ids.push(value);
colors.push(uniqColors[value]);
});
} else {
datasets.push(dataset);
ids.push('');
}
this._createBoxPlot({
showPoints: boxShowPoints,
myPlot: myPlot,
datasets: datasets,
colors: colors,
ids: ids,
layout: layout,
config: config
});
}
}
};
phantasus.ChartTool.newPlot = function (myPlot, traces, layout, config) {
return Plotly.newPlot(myPlot, traces, layout, config);
};
phantasus.CollapseDatasetTool = function () {
};
phantasus.CollapseDatasetTool.Functions = [
phantasus.Mean, phantasus.Median, phantasus.Min,
phantasus.Max, phantasus.Sum, phantasus.MaximumMeanProbe,
phantasus.MaximumMedianProbe
];
phantasus.CollapseDatasetTool.Functions.fromString = function (s) {
for (var i = 0; i < phantasus.CollapseDatasetTool.Functions.length; i++) {
if (phantasus.CollapseDatasetTool.Functions[i].toString() === s) {
return phantasus.CollapseDatasetTool.Functions[i];
}
}
throw new Error(s + ' not found');
};
phantasus.CollapseDatasetTool.prototype = {
toString: function () {
return 'Collapse';
},
init: function (project, form) {
var setValue = function (val) {
var isRows = val === 'Rows';
var names = phantasus
.MetadataUtil
.getMetadataNames(
isRows ?
project.getFullDataset().getRowMetadata() :
project.getFullDataset().getColumnMetadata());
form.setOptions('collapse_to_fields', names);
};
form.$form.find('[name=collapse]').on('change', function (e) {
setValue($(this).val());
});
form.$form.find('[name=collapse_method]').on('change', function (e) {
form.setVisible('percentile', $(this).val() === phantasus.Percentile.toString());
var collapsableColumns = !phantasus.CollapseDatasetTool.Functions.fromString($(this).val()).selectOne;
form.setVisible('collapse', collapsableColumns);
if (!collapsableColumns) {
setValue('Rows');
}
});
setValue('Rows');
},
gui: function () {
return [{
name: 'collapse_method',
options: phantasus.CollapseDatasetTool.Functions,
value: phantasus.CollapseDatasetTool.Functions[1],
type: 'select'
}, {
name: 'collapse',
options: ['Columns', 'Rows'],
value: 'Rows',
type: 'radio'
}, {
name: 'collapse_to_fields',
options: [],
type: 'select',
multiple: true
}, {
name: 'omit_unannotated',
type: 'checkbox',
value: true
}];
},
execute: function (options) {
var project = options.project;
var heatMap = options.heatMap;
var f = phantasus
.CollapseDatasetTool
.Functions
.fromString(options.input.collapse_method);
var collapseToFields = options.input.collapse_to_fields;
var omitUnannotated = options.input.omit_unannotated;
if (!collapseToFields || collapseToFields.length === 0) {
throw new Error('Please select one or more fields to collapse to');
}
var dataset = project.getFullDataset();
var rows = options.input.collapse === 'Rows';
if (!rows) {
dataset = new phantasus.TransposedDatasetView(dataset);
}
if (omitUnannotated) {
var omitCheck = function (x) {
return !x || x.toString() === '' || x.toString() === 'NA'
};
var fieldMeta = dataset.getRowMetadata();
var aoa = collapseToFields
.map(function (field) {
return phantasus.VectorUtil.toArray(fieldMeta.getByName(field));
})
var keepIndexes = _.zip
.apply(null, aoa)
.reduce(function (acc, value, index) {
if (value.some(omitCheck)) {
return acc;
}
acc.push(index);
return acc;
}, []);
dataset = new phantasus.SlicedDatasetView(dataset, keepIndexes);
}
var collapseMethod = f.selectOne ? phantasus.SelectRow : phantasus.CollapseDataset;
dataset = collapseMethod(dataset, collapseToFields, f, true);
if (!rows) {
dataset = phantasus.DatasetUtil.copy(new phantasus.TransposedDatasetView(dataset));
}
var oldDataset = project.getFullDataset();
var oldSession = oldDataset.getESSession();
if (oldSession) {
dataset.setESSession(new Promise(function (resolve, reject) {
oldSession.then(function (esSession) {
var args = {
es: esSession,
selectOne: Boolean(f.selectOne),
isRows: rows,
fn: f.rString(),
fields: collapseToFields,
removeEmpty: omitUnannotated
};
ocpu
.call("collapseDataset", args, function (newSession) {
resolve(newSession);
}, false, "::es")
.fail(function () {
reject();
throw new Error("Collapse dataset failed. See console");
});
});
}));
}
return new phantasus.HeatMap({
name: heatMap.getName(),
dataset: dataset,
parent: heatMap,
symmetric: false
});
}
};
phantasus.CreateAnnotation = function () {
};
phantasus.CreateAnnotation.prototype = {
toString: function () {
return 'Create Calculated Annotation';
},
gui: function () {
this.operationDict = {
'Mean': 'MEAN()',
'MAD': 'MAD()',
'Median': 'MEDIAN()',
'Max': 'MAX()',
'Min': 'MIN()',
'Sum': 'SUM()',
};
return [ {
name: 'annotate',
options: ['Columns', 'Rows'],
value: 'Rows',
type: 'radio'
},{
name: 'operation',
value: _.first(Object.keys(this.operationDict)),
type: 'select',
options: Object.keys(this.operationDict)
}, {
name: 'annotation_name',
value: '',
type: 'text',
help: 'Optional annotation name. If not specified, the operation name will be used.',
autocomplete: 'off'
}, {
name: 'use_selected_rows_and_columns_only',
type: 'checkbox'
}];
},
execute: function (options) {
var project = options.project;
var opName = options.input.operation;
var colName = options.input.annotation_name || opName;
var operation = this.operationDict[opName];
var selectedOnly = options.input.use_selected_rows_and_columns_only;
var isColumns = options.input.annotate === 'Columns';
var args = {
operation: opName,
isColumns: isColumns,
name: colName
};
var dataset = selectedOnly
? project.getSelectedDataset({
selectedRows: true,
selectedColumns: true,
})
: project.getFullDataset();
if (selectedOnly) {
var indices = phantasus.Util.getTrueIndices(dataset);
args.columns = indices.columns;
args.rows = indices.rows;
}
if (isColumns) {
dataset = phantasus.DatasetUtil.transposedView(dataset);
}
var fullDataset = project.getFullDataset();
var session = fullDataset.getESSession();
fullDataset.setESSession(new Promise(function (resolve, reject) {
session.then(function (esSession) {
args.es = esSession;
ocpu
.call("calculatedAnnotation", args, function (newSession) {
resolve(newSession);
}, false, "::es")
.fail(function () {
reject();
throw new Error("Calculated annotation failed. See console");
});
});
}));
var rowView = new phantasus.DatasetRowView(dataset);
var vector = dataset.getRowMetadata().add(colName);
var MAD = function () {
return phantasus.MAD(rowView);
};
var MAX = function () {
return phantasus.Max(rowView);
};
var MEAN = function () {
return phantasus.Mean(rowView);
};
var MEDIAN = function (p) {
return phantasus.Percentile(rowView, 50);
};
var MIN = function () {
return phantasus.Min(rowView);
};
var SUM = function () {
return phantasus.Sum(rowView);
};
var idx = 0;
for (var size = dataset.getRowCount(); idx < size; idx++) {
rowView.setIndex(idx);
var val = eval(operation);
vector.setValue(idx, val.valueOf());
}
phantasus.VectorUtil.maybeConvertStringToNumber(vector);
project.trigger('trackChanged', {
vectors: [vector],
display: ['text'],
columns: isColumns
});
}
};
phantasus.DendrogramEnrichmentTool = function (isColumns) {
this.isColumns = isColumns;
};
phantasus.DendrogramEnrichmentTool.prototype = {
toString: function () {
return 'Dendrogram Enrichment';
},
gui: function (project) {
var dataset = project.getSortedFilteredDataset();
var fields = phantasus.MetadataUtil
.getMetadataNames(this.isColumns ? dataset.getColumnMetadata()
: dataset.getRowMetadata());
return [{
name: 'field',
options: fields,
type: 'bootstrap-select',
multiple: false
}, {
name: 'min_p-value_for_enrichment',
type: 'text',
value: '0.05'
}, {
name: 'minimum_number_of_total_members_in_group',
type: 'text',
value: '5'
}, {
name: 'minimum_number_of_members_in_group',
type: 'text',
value: '3'
}];
},
execute: function (options) {
var project = options.project;
var heatMap = options.heatMap;
var pValue = options.input['min_p-value_for_enrichment'];
var minTotalGroupSize = options.input.minimum_number_of_total_members_in_group;
var minGroupSize = options.input.minimum_number_of_members_in_group;
var dataset = project.getSortedFilteredDataset();
var dendrogram = this.isColumns ? heatMap.columnDendrogram
: heatMap.rowDendrogram;
var vector = this.isColumns ? dataset.getColumnMetadata().getByName(
options.input.field) : dataset.getRowMetadata().getByName(
options.input.field);
var valueToIndices = phantasus.VectorUtil
.createValueToIndicesMap(vector);
var valueToGlobalCount = new phantasus.Map();
var values = [];
valueToIndices.forEach(function (indices, value) {
valueToGlobalCount.set(value, indices.length);
values.push(value);
});
var nvalues = values.length;
var N = vector.size();
phantasus.DendrogramUtil.dfs(dendrogram.tree.rootNode,
function (node) {
delete node.info;
var valueToCount = new phantasus.Map();
for (var i = 0; i < nvalues; i++) {
valueToCount.set(values[i], 0);
}
var min = node.minIndex;
var max = node.maxIndex;
var n = max - min + 1;
if (n > 1 && n >= minTotalGroupSize) {
for (var i = min; i <= max; i++) {
var value = vector.getValue(i);
valueToCount
.set(value, valueToCount.get(value) + 1);
}
for (var i = 0; i < nvalues; i++) {
var K = valueToGlobalCount.get(values[i]);
var k = valueToCount.get(values[i]);
if (k >= minGroupSize) {
var a = k;
var b = K - k;
var c = n - k;
var d = N + k - n - K;
var p = phantasus.FisherExact.fisherTest(a, b,
c, d);
if (p <= pValue) {
if (!node.info) {
node.info = {};
}
node.info[values[i]] = p;
}
}
}
}
return true;
});
dendrogram.setInvalid(true);
dendrogram.repaint();
}
};
phantasus.DESeqTool = function () {
};
phantasus.DESeqTool.prototype = {
toString: function () {
return "DESeq2 (experimental)";
},
init: function (project, form) {
var _this = this;
var updateAB = function (fieldNames) {
var ids = [];
if (fieldNames != null) {
var vectors = phantasus.MetadataUtil.getVectors(project
.getFullDataset().getColumnMetadata(), fieldNames);
var idToIndices = phantasus.VectorUtil
.createValuesToIndicesMap(vectors);
idToIndices.forEach(function (indices, id) {
ids.push(id);
});
}
ids.sort();
form.setOptions("class_a", ids);
form.setOptions("class_b", ids);
};
var $field = form.$form.find("[name=field]");
$field.on("change", function (e) {
updateAB($(this).val());
});
if ($field[0].options.length > 0) {
$field.val($field[0].options[0].value);
}
updateAB($field.val());
},
gui: function (project) {
var dataset = project.getFullDataset();
if (_.size(project.getRowFilter().enabledFilters) > 0 || _.size(project.getColumnFilter().enabledFilters) > 0) {
phantasus.FormBuilder.showInModal({
title: 'Warning',
html: 'Your dataset is filtered.<br/>' + this.toString() + ' will apply to unfiltered dataset. Consider using New Heat Map tool.',
z: 10000
});
}
var fields = phantasus.MetadataUtil.getMetadataNames(dataset
.getColumnMetadata());
return [{
name: "field",
options: fields,
type: "select",
multiple: true
}, {
name: "class_a",
title: "Class A",
options: [],
value: "",
type: "checkbox-list",
multiple: true
}, {
name: "class_b",
title: "Class B",
options: [],
value: "",
type: "checkbox-list",
multiple: true
}];
},
execute: function (options) {
var project = options.project;
var field = options.input.field;
var classA = options.input.class_a;
var classB = options.input.class_b;
var dataset = project.getFullDataset();
var promise = $.Deferred();
if (classA.length == 0 || classB.length == 0) {
throw new Error("You must choose at least one option in each class");
}
// console.log(dataset);
var v = dataset.getColumnMetadata().getByName("Comparison");
if (v == null) {
v = dataset.getColumnMetadata().add("Comparison");
}
var vs = [];
field.forEach(function (name) {
vs.push(dataset.getColumnMetadata().getByName(name));
});
var checkCortege = function (vectors, curClass, curColumn) {
var columnInClass = false;
var isEqual = true;
for (j = 0; j < curClass.length; j += 1) {
isEqual = true;
for (var k = 0; k < vectors.length; k += 1) {
isEqual &= vectors[k].getValue(curColumn) == curClass[j].array[k];
}
columnInClass |= isEqual;
}
return columnInClass;
};
for (var i = 0; i < dataset.getColumnCount(); i++) {
var columnInA = checkCortege(vs, classA, i);
var columnInB = checkCortege(vs, classB, i);
if (columnInA && columnInB) {
var warning = "Chosen classes have intersection in column " + i;
promise.reject();
throw new Error(warning);
}
v.setValue(i, columnInA ? "A" : (columnInB ? "B" : ""));
}
var vecArr = phantasus.VectorUtil.toArray(v);
var count = _.countBy(vecArr);
if (count['A'] === 1 || count['B'] === 1) {
promise.reject();
throw new Error('Chosen classes have only single sample');
}
project.trigger("trackChanged", {
vectors: [v],
display: ["color"],
columns: true
});
var values = Array.apply(null, Array(project.getFullDataset().getColumnCount()))
.map(String.prototype.valueOf, "");
for (var j = 0; j < dataset.getColumnCount(); j++) {
values[j] = v.getValue(j);
}
dataset.getESSession().then(function (essession) {
var args = {
es: essession,
fieldValues: values
};
var req = ocpu.call("deseqAnalysis/print", args, function (session) {
var r = new FileReader();
var filePath = phantasus.Util.getFilePath(session, JSON.parse(session.txt)[0]);
r.onload = function (e) {
var contents = e.target.result;
var ProtoBuf = dcodeIO.ProtoBuf;
ProtoBuf.protoFromFile("./message.proto", function (error, success) {
if (error) {
alert(error);
}
var builder = success,
rexp = builder.build("rexp"),
REXP = rexp.REXP,
rclass = REXP.RClass;
var res = REXP.decode(contents);
var data = phantasus.Util.getRexpData(res, rclass);
var names = phantasus.Util.getFieldNames(res, rclass);
var vs = [];
names.forEach(function (name) {
if (name !== "symbol") {
var v = dataset.getRowMetadata().add(name);
for (var i = 0; i < dataset.getRowCount(); i++) {
v.setValue(i, data[name].values[i]);
}
vs.push(v);
}
});
dataset.setESSession(Promise.resolve(session));
project.trigger("trackChanged", {
vectors: vs,
display: []
});
promise.resolve();
})
};
phantasus.BlobFromPath.getFileObject(filePath, function (file) {
r.readAsArrayBuffer(file);
});
}, false, "::es");
req.fail(function () {
promise.reject();
throw new Error("Deseq call failed" + req.responseText);
});
});
return promise;
}
};
var ENRICHR_URL = 'https://amp.pharm.mssm.edu/Enrichr/addList';
var ENRICHR_SUBMIT_LIMIT = 10000;
phantasus.enrichrTool = function (project) {
var self = this;
var dataset = project.getSortedFilteredDataset();
var rows = phantasus.MetadataUtil.getMetadataNames(dataset
.getRowMetadata(), true);
var $dialog = $('<div style="background:white;" title="Submit to Enrichr"></div>');
var form = new phantasus.FormBuilder({
formStyle: 'vertical'
});
form.appendContent('<h4>Please select rows.</h4>');
[{
name: 'column_with_gene_symbols',
options: rows,
value: _.first(rows),
type: 'select'
}, {
name: 'ambiguous_genes_strategy',
type: 'select',
tooltipHelp: 'Sometimes gene symbol cell contains multiple values separated by \'///\'.',
options: [{
name: 'omit',
value: 'omit'
}, {
name: 'split',
value: 'split'
}]
}, {
name: 'preview_data',
type: 'select',
style: 'height: auto; max-height: 600px; overflow-x: auto; overflow-y: hidden;',
multiple: true,
disabled: true, //READ-ONLY
}].forEach(function (a) {
form.append(a);
});
form.$form.appendTo($dialog);
var columnSelect = form.$form.find("[name=column_with_gene_symbols]");
var strategySelect = form.$form.find("[name=ambiguous_genes_strategy]");
var previewData = form.$form.find('[name=preview_data]');
var onSelect = function () {
var data = phantasus.Dataset.toJSON(project.getSelectedDataset());
var newOptions = [];
if (data.rows < ENRICHR_SUBMIT_LIMIT) {
var parsedData = parseData(data, $(columnSelect).val(), $(strategySelect).val());
newOptions = _.size(parsedData) === 0 ? ['[No rows selected]'] : [parsedData.join(' ')];
} else {
newOptions = ['[Invalid amount of rows selected (0 < n < 10000)]'];
}
form.setOptions('preview_data', newOptions);
//previewData[0].size = Math.max(newOptions.length, 5);
previewData[0].size = 2;
};
columnSelect.on('change', onSelect);
strategySelect.on('change', onSelect);
onSelect();
form.$form.find('[data-toggle="tooltip"]').tooltip();
form.appendContent('Result will open in a new window automatically.');
project.getRowSelectionModel().on("selectionChanged.chart", onSelect);
$dialog.dialog({
close: function (event, ui) {
project.getRowSelectionModel().off("selectionChanged.chart", onSelect);
$dialog.dialog('destroy').remove();
},
resizable: true,
height: 450,
width: 600,
buttons: [
{
text: "Cancel",
click: function () {
$(this).dialog("close");
}
},
{
text: "Submit",
click: function () {
self.execute({
project: project,
form: form
});
$(this).dialog("close");
}
}
]
});
this.$dialog = $dialog;
};
function parseData(data, column_with_gene_symbols, strategy) {
var targetData = (_.findWhere(data.rowMetadataModel.vectors, {name: column_with_gene_symbols}) || {array: []}).array;
return _.reduce(targetData, function (acc, geneRow) {
if (!_.isString(geneRow)) { // rows are not strings???
acc.push(geneRow);
return acc;
}
var isAmbiguousGene = geneRow.indexOf('///') !== -1;
if (isAmbiguousGene && strategy === 'split') {
acc = acc.concat(geneRow.split('///'));
} else if (!isAmbiguousGene) {
acc.push(geneRow);
}
return acc;
}, []);
}
phantasus.enrichrTool.prototype = {
toString: function () {
return 'Submit to Enrichr';
},
execute: function (options) {
var data = phantasus.Dataset.toJSON(options.project.getSelectedDataset());
if (data.rows >= ENRICHR_SUBMIT_LIMIT) {
throw new Error('Invalid amount of rows are selected (0 <= n <= 10000)');
}
var parsedData = parseData(data, options.form.getValue('column_with_gene_symbols'),
options.form.getValue('ambiguous_genes_strategy'))
.join('\n');
if (_.size(parsedData) === 0 || _.size(parsedData) >= ENRICHR_SUBMIT_LIMIT) {
throw new Error('Invalid amount of rows are selected (0 <= n <= 10000). Currently selected: ' + _.size(parsedData) + ' genes');
}
var promise = $.Deferred();
var formData = new FormData();
formData.append('list', parsedData);
formData.append('description', options.project.getSelectedDataset().getName());
$.ajax({
type: "POST",
url: ENRICHR_URL,
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
data = JSON.parse(data);
window.open('https://amp.pharm.mssm.edu/Enrichr/enrich?dataset=' + data.shortId, '_blank');
promise.resolve();
},
error: function (_, __, error) {
promise.reject();
throw new Error(error);
}
});
return promise;
}
};
phantasus.initFGSEATool = function (options) {
if (!phantasus.fgseaTool.init) {
var $el = $('<div style="background:white;" title="Init"><h5>Loading FGSEA meta information</h5></div>');
phantasus.Util.createLoadingEl().appendTo($el);
$el.dialog({
resizable: false,
height: 150,
width: 300
});
var req = ocpu.call("availableFGSEADatabases/print", {}, function (newSession) {
var result = JSON.parse(newSession.txt);
phantasus.fgseaTool.init = true;
phantasus.fgseaTool.dbs = result;
$el.dialog('destroy').remove();
new phantasus.fgseaTool(options.heatMap);
});
req.fail(function () {
$el.dialog('destroy').remove();
throw new Error("Couldn't load FGSEA meta information. Please try again in a moment. Error:" + req.responseText);
});
} else {
new phantasus.fgseaTool(options.heatMap);
}
};
phantasus.fgseaTool = function (heatMap) {
var self = this;
var project = heatMap.getProject();
var fullDataset = project.getFullDataset();
var numberFields = phantasus.MetadataUtil.getMetadataSignedNumericFields(fullDataset
.getRowMetadata());
if (_.size(phantasus.fgseaTool.dbs) === 0) {
throw new Error('There is no installed pathway databases.');
}
if (numberFields.length === 0) {
throw new Error('No fields in row annotation appropriate for ranking.');
}
var rankRows = numberFields.map(function (field) {
return field.getName();
});
var rows = phantasus.MetadataUtil.getMetadataNames(fullDataset
.getRowMetadata(), true);
this.$dialog = $('<div style="background:white;" title="' + phantasus.fgseaTool.prototype.toString() + '"></div>');
this.formBuilder = new phantasus.FormBuilder({
formStyle: 'vertical'
});
var dbOptions = phantasus.fgseaTool.dbs.map(function (dbObj) {
return {
name: dbObj.HINT,
value: dbObj.FILE
};
});
[{
name: 'pathway_database',
options: dbOptions,
value: _.first(dbOptions).value,
type: 'select'
},{
name: 'rank_by',
options: rankRows,
value: _.first(rankRows),
type: 'select'
}, {
name: 'column_with_gene_ID',
options: rows,
value: _.first(rows),
type: 'select'
}, {
name: 'omit_ambigious_genes',
type: 'checkbox',
tooltipHelp: 'Current column contains cells with multiple values separated by \'///\'.',
value: true
}].forEach(function (a) {
self.formBuilder.append(a);
});
this.formBuilder.$form.appendTo(this.$dialog);
this.formBuilder.$form.find('[data-toggle="tooltip"]').tooltip();
var columnSelect = this.formBuilder.$form.find("[name=column_with_gene_ID]");
var onSelect = function () {
var geneIDColumn = self.formBuilder.getValue('column_with_gene_ID');
var values = phantasus.VectorUtil.toArray(fullDataset.getRowMetadata().getByName(geneIDColumn));
var show = values.some(function (value) {
return _.isString(value) && value.indexOf('///') !== -1;
});
self.formBuilder.setVisible('omit_ambigious_genes', show);
};
columnSelect.on('change', onSelect);
onSelect();
this.$dialog.dialog({
open: function (event, ui) {
},
close: function (event, ui) {
self.$dialog.dialog('destroy').remove();
event.stopPropagation();
},
buttons: {
"Submit": function () {
var promise = self.execute(heatMap);
self.$dialog.dialog('close');
phantasus.Util.customToolWaiter(promise, phantasus.fgseaTool.prototype.toString(), heatMap);
},
"Cancel": function () {
self.$dialog.dialog('close');
}
},
resizable: true,
height: 400,
width: 600
});
};
phantasus.fgseaTool.prototype = {
init: false,
dbs: [],
precision: {
pval: 3,
padj: 3,
log2err: 3,
ES: 3,
NES: 3,
size: undefined
},
toString: function () {
return "Perform FGSEA"
},
execute: function (heatMap) {
var self = this;
var promise = $.Deferred();
this.parentHeatmap = heatMap;
var dataset = heatMap.getProject().getFullDataset();
var rankBy = this.formBuilder.getValue('rank_by');
var geneIDColumn = this.formBuilder.getValue('column_with_gene_ID');
var omitAmbigious = this.formBuilder.getValue('omit_ambigious_genes');
var genes = phantasus.VectorUtil.toArray(dataset.getRowMetadata().getByName(geneIDColumn));
var ranks = phantasus.VectorUtil.toArray(dataset.getRowMetadata().getByName(rankBy));
if ((new Set(genes)).size !== genes.length) {
promise.reject();
throw new Error('FGSEA requires Gene ID column to be unique. Please use collapse tool.');
}
if (omitAmbigious) {
var omitIndexes = [];
genes = genes.reduce(function (acc, gene, index) {
if (!_.isString(gene)) { // rows are not strings???
acc.push(gene);
return acc;
}
var isAmbiguousGene = gene.indexOf('///') !== -1;
if (isAmbiguousGene) {
omitIndexes.push(index);
} else if (!isAmbiguousGene) {
acc.push(gene);
}
return acc;
}, []);
omitIndexes
.sort(function (a,b) {return b - a;})
.forEach(function (index) {
ranks.splice(index,1);
});
}
var request = {
dbName: this.formBuilder.getValue('pathway_database'),
ranks: {
genes: genes,
ranks: ranks
}
};
this.dbName = request.dbName;
var req = ocpu.call('performFGSEA/print', request, function (session) {
self.session = session;
self.fgsea = JSON.parse(session.txt);
if (_.size(self.fgsea) === 0) {
promise.reject();
throw new Error("FGSEA returned 0 rows. Nothing to show");
}
promise.resolve();
self.openTab();
}, false, "::es")
.fail(function () {
promise.reject();
throw new Error("Failed to perform FGSEA. Error:" + req.responseText);
});
return promise;
},
openTab: function () {
var self = this;
var template = [
'<div class="container-fluid">',
'<div class="row">',
'<div class="col-sm-12">',
'<label class="control-label">Actions:</label>',
'<div><button class="btn btn-default">Save as TSV</button></div>',
'</div>',
'</div>',
'<div class="row">',
'<div class="col-sm-9">',
'<label class="control-label">FGSEA:</label>',
'<div>' + this.generateTableHTML() + '</div>',
'</div>',
'<div class="col-sm-3">',
'<label class="control-label">Pathway detail:</label>',
'<div id="pathway-detail" style="word-break: break-all">Hint: Click on pathway name to get list of genes in it</div>',
'</div>',
'</div>',
'</div>'
].join('');
this.$el = $(template);
this.$saveButton = this.$el.find('button');
this.$saveButton.on('click', function () {
var blob = new Blob([self.generateTSV()], {type: "text/plain;charset=utf-8"});
saveAs(blob, self.parentHeatmap.getName() + "_FGSEA.tsv");
});
this.$pathwayDetail = this.$el.find('#pathway-detail');
this.$table = this.$el.find('table');
this.$table.on('click', 'tbody tr', function (e) {
var pathway = $(e.currentTarget).children().first().text();
var request = {
dbName: self.dbName,
pathwayName: pathway
};
self.$pathwayDetail.empty();
phantasus.Util.createLoadingEl().appendTo(self.$pathwayDetail);
var req = ocpu.call('queryPathway/print', request, function (session) {
var pathwayGenes = JSON.parse(session.txt);
self.$pathwayDetail.empty();
var leadingEdge = _.find(self.fgsea, {pathway: pathway}).leadingEdge;
var pathwayDetail = $([
'<div>',
'<strong>Pathway name:</strong>' + pathway + '<br>',
'<strong>Pathway genes (ID):</strong>' + pathwayGenes.geneID.join(' ') + '<br>',
'<strong>Pathway genes (Symbols):</strong>' + pathwayGenes.geneSymbol.join(' ') + '<br>',
'<strong>Leading edge:</strong>' + leadingEdge.join(' ') + '<br>',
'</div>'
].join(''));
pathwayDetail.appendTo(self.$pathwayDetail);
});
req.fail(function () {
throw new Error('Failed to get pathway details: ' + req.responseText);
});
});
$(this.$table).DataTable({
paging: false,
searching: false,
order: [[1, 'asc']]
});
this.tab = this.parentHeatmap.tabManager.add({
$el: this.$el,
closeable: true,
rename: false,
title: this.parentHeatmap.getName() + "_FGSEA",
object: this,
focus: true
});
this.tabId = this.id;
this.$tabPanel = this.$panel;
},
generateTSV: function () {
var headerNames = Object.keys(_.first(this.fgsea));
var result = headerNames.join('\t') + '\n';
result += this.fgsea.map(function (pathway) {
return Object.values(pathway).map(function (value) {
return (_.isArray(value)) ? value.join(' ') : value.toString();
}).join('\t');
}).join('\n');
return result;
},
generateTableHTML: function () {
var tableHeaderNames = Object.keys(_.first(this.fgsea));
var thead = tableHeaderNames
.map(function (name) { return '<th>' + name + '</th>'; })
.join('');
var tbody = this.fgsea
.map(function (pathway) {
var tableRow = _.map(pathway, function (value, colname) {
var result = '<td>';
if (_.isArray(value)) {
if (_.size(value) >= 5) {
result += value.slice(0, 5).join(' ') + ' ...';
} else {
result += value.join(' ');
}
} else if (_.isNumber(value)) {
result += value.toPrecision(phantasus.fgseaTool.prototype.precision[colname]);
} else {
result += value.toString();
}
result += '</td>';
return result;
}).join('');
return '<tr class="c-pointer">' + tableRow + '</tr>';
})
.join('');
var table = [
'<table id="fgsea-table" class="table table-hover table-striped table-condensed">',
'<thead>' + thead + '</thead>',
'<tbody>' + tbody + '</tbody>',
'</table>'
].join('');
return table;
}
};
phantasus.gseaTool = function (heatmap, project) {
var self = this;
var fullDataset = project.getFullDataset();
var numberFields = phantasus.MetadataUtil.getMetadataSignedNumericFields(fullDataset
.getRowMetadata());
if (numberFields.length === 0) {
throw new Error('No fields in row annotation appropriate for ranking.');
}
var rows = numberFields.map(function (field) {
return field.getName();
});
var annotations = ['(None)'].concat(phantasus.MetadataUtil.getMetadataNames(fullDataset.getColumnMetadata()))
this.$dialog = $('<div style="background:white;" title="' + this.toString() + '"><h4>Please select rows.</h4></div>');
this.$el = $([
'<div class="container-fluid" style="height: 100%">',
' <div class="row" style="height: 100%">',
' <div data-name="configPane" class="col-xs-2"></div>',
' <div class="col-xs-10" style="height: 100%">',
' <div style="position:relative; height: 100%;" data-name="chartDiv"></div>',
' </div>',
'</div>',
'</div>'].join('')
);
var $notifyRow = this.$dialog.find('h4');
this.formBuilder = new phantasus.FormBuilder({
formStyle: 'vertical'
});
[{
name: 'rank_by',
options: rows,
value: _.first(rows),
type: 'select'
}, {
name: 'vertical',
type: 'checkbox',
value: false
}, {
name: 'annotate_by',
options: annotations,
value: _.first(annotations),
type: 'select'
}, {
type: 'button',
name: 'export_to_SVG'
}/*, {
name: 'chart_width',
type: 'range',
value: 400,
min: 60,
max: 800,
step: 10
}, {
name: 'chart_height',
type: 'range',
value: 400,
min: 60,
max: 800,
step: 10
}*/].forEach(function (a) {
self.formBuilder.append(a);
});
var onChange = _.debounce(function (e) {
var selectedDataset = project.getSelectedDataset();
var fullDataset = project.getSortedFilteredDataset();
$notifyRow.toggle(selectedDataset.getRowCount() === fullDataset.getRowCount());
if (selectedDataset.getRowCount() === fullDataset.getRowCount()) {
return;
}
if (self.promise) {
self.promise.cancelled = true;
self.promise.reject('Cancelled');
}
self.$configPane.find('button').css('display', 'none');
self.request(heatmap, project).then(function (url) {
self.url = url;
self.draw(url);
self.$configPane.find('button').css('display', 'block');
}, function (e) {
self.$chart.empty();
self.$configPane.find('button').css('display', 'none');
});
}, 500);
this.formBuilder.$form.on('change', 'select', onChange);
this.formBuilder.$form.on('change', 'input', onChange);
project.getRowSelectionModel().on('selectionChanged.chart', onChange);
this.$configPane = this.$el.find('[data-name=configPane]');
this.formBuilder.$form.appendTo(this.$configPane);
this.$configPane.find('button').css('display', 'none');
this.$el.appendTo(this.$dialog);
this.$chart = this.$el.find("[data-name=chartDiv]");
this.$dialog.dialog({
open: function (event, ui) {
$(this).css('overflow', 'hidden'); //this line does the actual hiding
},
close: function (event, ui) {
project.getRowSelectionModel().off("selectionChanged.chart", onChange);
self.$dialog.dialog('destroy').remove();
event.stopPropagation();
},
resizable: true,
height: 600,
width: 900
});
this.$configPane.find('button').on('click', function () {
phantasus.Util.promptBLOBdownload(self.url, "gsea-plot.svg");
});
onChange();
};
phantasus.gseaTool.prototype = {
toString: function () {
return 'GSEA Plot';
},
request: function (heatmap, project) {
this.$chart.empty();
phantasus.Util.createLoadingEl().appendTo(this.$chart);
this.promise = $.Deferred();
var promise = this.promise;
var selectedDataset = project.getSelectedDataset();
var parentDataset = selectedDataset.dataset;
var fullDataset = project.getFullDataset();
if (selectedDataset.getRowCount() === fullDataset.getRowCount()) {
this.promise.reject('Invalid rows');
// throw new Error('Invalid amount of rows are selected (zero rows or whole dataset selected)');
return this.promise;
}
var idxs = selectedDataset.rowIndices.map(function (idx) {
return parentDataset.rowIndices[idx] + 1;
//return idx + 1; // #' @param selectedGenes indexes of selected genes (starting from one, in the order of fData)
});
var self = this;
var rankBy = this.formBuilder.getValue('rank_by');
var vertical = this.formBuilder.getValue('vertical');
var height = 4;//this.formBuilder.getValue('chart_height');
var width = 6;//this.formBuilder.getValue('chart_width');
if (vertical) {
height = 6;
width = 6;
}
var request = {
rankBy: rankBy,
selectedGenes: idxs,
width: width,
height: height,
vertical: vertical,
addHeatmap: true,
pallete: heatmap.heatmap.colorScheme.getColors()
};
var annotateBy = this.formBuilder.getValue('annotate_by');
if (annotateBy === "(None)") {
request.width = width - 1;
} else {
request.showAnnotation = annotateBy;
var colorByVector = fullDataset.getColumnMetadata().getByName(annotateBy) ;
var colorModel = project.getColumnColorModel();
var uniqColors = {};
_.each(phantasus.VectorUtil.getValues(colorByVector), function (value) {
if (!uniqColors[value]) {
if (colorModel.containsDiscreteColor(colorByVector, value)
&& colorByVector.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
uniqColors[value] = colorModel.getMappedValue(colorByVector, value);
} else if (colorModel.isContinuous(colorByVector)) {
uniqColors[value] = colorModel.getContinuousMappedValue(colorByVector, value);
} else {
uniqColors[value] = phantasus.VectorColorModel.CATEGORY_ALL[_.size(uniqColors) % 60];
}
}
});
request.annotationColors = uniqColors;
}
fullDataset.getESSession().then(function (esSession) {
request.es = esSession;
ocpu.call('gseaPlot/print', request, function (session) {
if (promise.cancelled) {
return;
}
var svgPath = JSON.parse(session.txt)[0];
var absolutePath = phantasus.Util.getFilePath(session, svgPath);
phantasus.BlobFromPath.getFileObject(absolutePath, function (blob) {
promise.resolve(URL.createObjectURL(blob));
});
}, false, "::es")
.fail(function () {
promise.reject();
});
})
return self.promise;
},
draw: function (url) {
this.$chart.empty();
var svg = $('<img src="' + url + '" style="max-width: 100%; height: 100%; position: absolute; margin: auto; top: 0; left: 0; right: 0; bottom: 0;">');
svg.appendTo(this.$chart);
}
};
phantasus.HClusterTool = function () {
};
phantasus.HClusterTool.PRECOMPUTED_DIST = 'Matrix values (for a precomputed distance matrix)';
phantasus.HClusterTool.PRECOMPUTED_SIM = 'Matrix values (for a precomputed similarity matrix)';
phantasus.HClusterTool.Functions = [phantasus.Euclidean, phantasus.Jaccard,
new phantasus.OneMinusFunction(phantasus.Cosine),
new phantasus.OneMinusFunction(phantasus.KendallsCorrelation),
new phantasus.OneMinusFunction(phantasus.Pearson),
new phantasus.OneMinusFunction(phantasus.Spearman),
phantasus.HClusterTool.PRECOMPUTED_DIST,
phantasus.HClusterTool.PRECOMPUTED_SIM];
phantasus.HClusterTool.Functions.fromString = function (s) {
for (var i = 0; i < phantasus.HClusterTool.Functions.length; i++) {
if (phantasus.HClusterTool.Functions[i].toString() === s) {
return phantasus.HClusterTool.Functions[i];
}
}
throw new Error(s + ' not found');
};
phantasus.HClusterTool.createLinkageMethod = function (linkageString) {
var linkageMethod;
if (linkageString === 'Average') {
linkageMethod = phantasus.AverageLinkage;
} else if (linkageString === 'Complete') {
linkageMethod = phantasus.CompleteLinkage;
} else if (linkageString === 'Single') {
linkageMethod = phantasus.SingleLinkage;
} else {
throw new Error('Unknown linkage method ' + linkageString);
}
return linkageMethod;
};
phantasus.HClusterTool.execute = function (dataset, input) {
// note: in worker here
var linkageMethod = phantasus.HClusterTool
.createLinkageMethod(input.linkage_method);
var f = phantasus.HClusterTool.Functions.fromString(input.metric);
if (f === phantasus.HClusterTool.PRECOMPUTED_DIST) {
f = 0;
} else if (f === phantasus.HClusterTool.PRECOMPUTED_SIM) {
f = 1;
}
var rows = input.cluster == 'Rows' || input.cluster == 'Rows and columns';
var columns = input.cluster == 'Columns'
|| input.cluster == 'Rows and columns';
var doCluster = function (d, groupByFields) {
return (groupByFields && groupByFields.length > 0) ? new phantasus.HClusterGroupBy(
d, groupByFields, f, linkageMethod)
: new phantasus.HCluster(phantasus.HCluster
.computeDistanceMatrix(d, f), linkageMethod);
};
var rowsHcl;
var columnsHcl;
if (rows) {
rowsHcl = doCluster(
input.selectedColumnsToUseForClusteringRows ? new phantasus.SlicedDatasetView(dataset,
null, input.selectedColumnsToUseForClusteringRows) : dataset,
input.group_rows_by);
}
if (columns) {
columnsHcl = doCluster(
phantasus.DatasetUtil
.transposedView(input.selectedRowsToUseForClusteringColumns ? new phantasus.SlicedDatasetView(
dataset, input.selectedRowsToUseForClusteringColumns, null)
: dataset), input.group_columns_by);
}
return {
rowsHcl: rowsHcl,
columnsHcl: columnsHcl
};
};
phantasus.HClusterTool.prototype = {
toString: function () {
return 'Hierarchical Clustering';
},
init: function (project, form) {
form.setOptions('group_rows_by', phantasus.MetadataUtil
.getMetadataNames(project.getFullDataset().getRowMetadata()));
form
.setOptions('group_columns_by', phantasus.MetadataUtil
.getMetadataNames(project.getFullDataset()
.getColumnMetadata()));
form.setVisible('group_rows_by', false);
form
.setVisible('cluster_rows_in_space_of_selected_columns_only',
false);
form.$form.find('[name=cluster]').on(
'change',
function (e) {
var val = $(this).val();
var showGroupColumns = false;
var showGroupRows = false;
if (val === 'Columns') {
showGroupColumns = true;
} else if (val === 'Rows') {
showGroupRows = true;
} else {
showGroupColumns = true;
showGroupRows = true;
}
form.setVisible('group_columns_by', showGroupColumns);
form.setVisible('group_rows_by', showGroupRows);
form.setVisible(
'cluster_columns_in_space_of_selected_rows_only',
showGroupColumns);
form.setVisible(
'cluster_rows_in_space_of_selected_columns_only',
showGroupRows);
});
},
gui: function () {
return [{
name: 'metric',
options: phantasus.HClusterTool.Functions,
value: phantasus.HClusterTool.Functions[4].toString(),
type: 'select'
}, {
name: 'linkage_method',
options: ['Average', 'Complete', 'Single'],
value: 'Average',
type: 'select'
}, {
name: 'cluster',
options: ['Columns', 'Rows', 'Rows and columns'],
value: 'Columns',
type: 'select'
}, {
name: 'group_columns_by',
options: [],
type: 'bootstrap-select',
multiple: true
}, {
name: 'group_rows_by',
options: [],
type: 'bootstrap-select',
multiple: true
}, {
name: 'cluster_columns_in_space_of_selected_rows_only',
type: 'checkbox'
}, {
name: 'cluster_rows_in_space_of_selected_columns_only',
type: 'checkbox'
}];
},
execute: function (options) {
var project = options.project;
var heatmap = options.heatMap;
var selectedRowsToUseForClusteringColumns = options.input.cluster_columns_in_space_of_selected_rows_only ? project
.getRowSelectionModel().getViewIndices().values()
: null;
if (selectedRowsToUseForClusteringColumns != null && selectedRowsToUseForClusteringColumns.length === 0) {
selectedRowsToUseForClusteringColumns = null;
}
var selectedColumnsToUseForClusteringRows = options.input.cluster_rows_in_space_of_selected_columns_only ? project
.getColumnSelectionModel().getViewIndices().values()
: null;
if (selectedColumnsToUseForClusteringRows != null && selectedColumnsToUseForClusteringRows.length === 0) {
selectedColumnsToUseForClusteringRows = null;
}
var rows = options.input.cluster == 'Rows'
|| options.input.cluster == 'Rows and columns';
var columns = options.input.cluster == 'Columns'
|| options.input.cluster == 'Rows and columns';
options.input.selectedRowsToUseForClusteringColumns = selectedRowsToUseForClusteringColumns;
options.input.selectedColumnsToUseForClusteringRows = selectedColumnsToUseForClusteringRows;
var dataset = project.getSortedFilteredDataset();
if (options.input.background === undefined) {
options.input.background = true;
}
options.input.background = options.input.background && typeof Worker !== 'undefined';
var rowModelOrder;
var columnModelOrder;
if (rows) {
rowModelOrder = [];
for (var i = 0; i < dataset.getRowCount(); i++) {
rowModelOrder[i] = project.convertViewRowIndexToModel(i);
}
}
if (columns) {
columnModelOrder = [];
for (var i = 0; i < dataset.getColumnCount(); i++) {
columnModelOrder[i] = project.convertViewColumnIndexToModel(i);
}
}
if (options.input.background === false) {
var result = phantasus.HClusterTool.execute(dataset, options.input);
if (result.rowsHcl) {
var modelOrder = [];
for (var i = 0; i < result.rowsHcl.reorderedIndices.length; i++) {
modelOrder[i] = rowModelOrder[result.rowsHcl.reorderedIndices[i]];
}
heatmap.setDendrogram(result.rowsHcl.tree, false,
modelOrder);
}
if (result.columnsHcl) {
var modelOrder = [];
for (var i = 0; i < result.columnsHcl.reorderedIndices.length; i++) {
modelOrder[i] = columnModelOrder[result.columnsHcl.reorderedIndices[i]];
}
heatmap.setDendrogram(result.columnsHcl.tree, true, modelOrder);
}
} else {
var blob = new Blob(
['self.onmessage = function(e) {'
+ 'importScripts(e.data.scripts);'
+ 'self.postMessage(phantasus.HClusterTool.execute(phantasus.Dataset.fromJSON(e.data.dataset), e.data.input));'
+ '}']);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.postMessage({
scripts: phantasus.Util.getScriptPath(),
dataset: phantasus.Dataset.toJSON(dataset, {
columnFields: options.input.group_columns_by || [],
rowFields: options.input.group_rows_by || [],
seriesIndices: [0]
}),
input: options.input
});
worker.onmessage = function (e) {
var result = e.data;
if (result.rowsHcl) {
var modelOrder = [];
for (var i = 0; i < result.rowsHcl.reorderedIndices.length; i++) {
modelOrder[i] = rowModelOrder[result.rowsHcl.reorderedIndices[i]];
}
heatmap.setDendrogram(result.rowsHcl.tree, false,
modelOrder);
}
if (result.columnsHcl) {
var modelOrder = [];
for (var i = 0; i < result.columnsHcl.reorderedIndices.length; i++) {
modelOrder[i] = columnModelOrder[result.columnsHcl.reorderedIndices[i]];
}
heatmap.setDendrogram(result.columnsHcl.tree, true,
modelOrder);
}
worker.terminate();
window.URL.revokeObjectURL(url);
};
return worker;
}
}
};
/**
* Created by dzenkova on 11/18/16.
*/
phantasus.KmeansTool = function () {
};
phantasus.KmeansTool.prototype = {
toString: function () {
return "K-means";
},
gui: function (project) {
// z-score, robust z-score, log2, inverse log2
if (_.size(project.getRowFilter().enabledFilters) > 0 || _.size(project.getColumnFilter().enabledFilters) > 0) {
phantasus.FormBuilder.showInModal({
title: 'Warning',
html: 'Your dataset is filtered.<br/>' + this.toString() + ' will apply to unfiltered dataset. Consider using New Heat Map tool.',
z: 10000
});
}
return [{
name: "number_of_clusters",
type: "text"
}];
},
execute: function (options) {
var project = options.project;
var dataset = project.getFullDataset();
var promise = $.Deferred();
var number = parseInt(options.input.number_of_clusters);
if (isNaN(number)) {
throw new Error("Enter the expected number of clusters");
}
var replacena = "mean";
//console.log(dataset);
dataset.getESSession().then(function (essession) {
var args = {
es: essession,
k: number,
replacena: replacena
};
var req = ocpu.call("performKmeans/print", args, function (newSession) {
var result = JSON.parse(newSession.txt);
var v = dataset.getRowMetadata().add('clusters');
for (var i = 0; i < dataset.getRowCount(); i++) {
v.setValue(i, result[i].toString());
}
v.getProperties().set("phantasus.dataType", "string");
dataset.setESSession(Promise.resolve(newSession));
promise.resolve();
project.trigger("trackChanged", {
vectors: [v],
display: ["color"]
});
}, false, "::es");
req.fail(function () {
promise.reject();
throw new Error("Kmeans call to OpenCPU failed" + req.responseText);
});
});
return promise;
}
};
phantasus.LimmaTool = function () {
};
phantasus.LimmaTool.prototype = {
toString: function () {
return "Limma";
},
init: function (project, form) {
var _this = this;
var updateAB = function (fieldNames) {
var ids = [];
if (fieldNames != null) {
var vectors = phantasus.MetadataUtil.getVectors(project
.getFullDataset().getColumnMetadata(), fieldNames);
var idToIndices = phantasus.VectorUtil
.createValuesToIndicesMap(vectors);
idToIndices.forEach(function (indices, id) {
ids.push(id);
});
}
ids.sort();
form.setOptions("class_a", ids);
form.setOptions("class_b", ids);
};
var $field = form.$form.find("[name=field]");
$field.on("change", function (e) {
updateAB($(this).val());
});
if ($field[0].options.length > 0) {
$field.val($field[0].options[0].value);
}
updateAB($field.val());
},
gui: function (project) {
var dataset = project.getFullDataset();
if (_.size(project.getRowFilter().enabledFilters) > 0 || _.size(project.getColumnFilter().enabledFilters) > 0) {
phantasus.FormBuilder.showInModal({
title: 'Warning',
html: 'Your dataset is filtered.<br/>' + this.toString() + ' will apply to unfiltered dataset. Consider using New Heat Map tool.',
z: 10000
});
}
var fields = phantasus.MetadataUtil.getMetadataNames(dataset
.getColumnMetadata());
return [{
name: "field",
options: fields,
type: "select",
multiple: true
}, {
name: "class_a",
title: "Class A",
options: [],
value: "",
type: "checkbox-list",
multiple: true
}, {
name: "class_b",
title: "Class B",
options: [],
value: "",
type: "checkbox-list",
multiple: true
}];
},
execute: function (options) {
var project = options.project;
var field = options.input.field;
var classA = options.input.class_a;
var classB = options.input.class_b;
var dataset = project.getFullDataset();
var promise = $.Deferred();
if (classA.length == 0 || classB.length == 0) {
throw new Error("You must choose at least one option in each class");
}
// console.log(dataset);
var v = dataset.getColumnMetadata().getByName("Comparison");
if (v == null) {
v = dataset.getColumnMetadata().add("Comparison");
}
var vs = [];
field.forEach(function (name) {
vs.push(dataset.getColumnMetadata().getByName(name));
});
var checkCortege = function (vectors, curClass, curColumn) {
var columnInClass = false;
var isEqual = true;
for (j = 0; j < curClass.length; j += 1) {
isEqual = true;
for (var k = 0; k < vectors.length; k += 1) {
isEqual &= vectors[k].getValue(curColumn) == curClass[j].array[k];
}
columnInClass |= isEqual;
}
return columnInClass;
};
for (var i = 0; i < dataset.getColumnCount(); i++) {
var columnInA = checkCortege(vs, classA, i);
var columnInB = checkCortege(vs, classB, i);
if (columnInA && columnInB) {
var warning = "Chosen classes have intersection in column " + i;
throw new Error(warning);
}
v.setValue(i, columnInA ? "A" : (columnInB ? "B" : ""));
}
project.trigger("trackChanged", {
vectors: [v],
display: ["color"],
columns: true
});
var values = Array.apply(null, Array(project.getFullDataset().getColumnCount()))
.map(String.prototype.valueOf, "");
for (var j = 0; j < dataset.getColumnCount(); j++) {
values[j] = v.getValue(j);
}
dataset.getESSession().then(function (essession) {
var args = {
es: essession,
fieldValues: values
};
var req = ocpu.call("limmaAnalysis/print", args, function (session) {
var r = new FileReader();
var filePath = phantasus.Util.getFilePath(session, JSON.parse(session.txt)[0]);
r.onload = function (e) {
var contents = e.target.result;
var ProtoBuf = dcodeIO.ProtoBuf;
ProtoBuf.protoFromFile("./message.proto", function (error, success) {
if (error) {
alert(error);
}
var builder = success,
rexp = builder.build("rexp"),
REXP = rexp.REXP,
rclass = REXP.RClass;
var res = REXP.decode(contents);
var data = phantasus.Util.getRexpData(res, rclass);
var names = phantasus.Util.getFieldNames(res, rclass);
var vs = [];
names.forEach(function (name) {
if (name !== "symbol") {
var v = dataset.getRowMetadata().add(name);
for (var i = 0; i < dataset.getRowCount(); i++) {
v.setValue(i, data[name].values[i]);
}
vs.push(v);
}
});
// alert("Limma finished successfully");
dataset.setESSession(Promise.resolve(session));
project.trigger("trackChanged", {
vectors: vs,
display: []
});
promise.resolve();
})
};
phantasus.BlobFromPath.getFileObject(filePath, function (file) {
r.readAsArrayBuffer(file);
});
}, false, "::es");
req.fail(function () {
promise.reject();
throw new Error("Limma call failed" + req.responseText);
});
});
return promise;
}
};
phantasus.MarkerSelection = function () {
};
/**
* @private
*/
phantasus.MarkerSelection.Functions = [
phantasus.FisherExact,
phantasus.FoldChange, phantasus.MeanDifference, phantasus.SignalToNoise,
phantasus.createSignalToNoiseAdjust(), phantasus.TTest];
phantasus.MarkerSelection.Functions.fromString = function (s) {
for (var i = 0; i < phantasus.MarkerSelection.Functions.length; i++) {
if (phantasus.MarkerSelection.Functions[i].toString() === s) {
return phantasus.MarkerSelection.Functions[i];
}
}
throw s + ' not found';
};
phantasus.MarkerSelection.execute = function (dataset, input) {
var aIndices = [];
var bIndices = [];
for (var i = 0; i < input.numClassA; i++) {
aIndices[i] = i;
}
for (var i = input.numClassA; i < dataset.getColumnCount(); i++) {
bIndices[i] = i;
}
var f = phantasus.MarkerSelection.Functions.fromString(input.metric);
var permutations = new phantasus.PermutationPValues(dataset, aIndices,
bIndices, input.npermutations, f);
return {
rowSpecificPValues: permutations.rowSpecificPValues,
k: permutations.k,
fdr: permutations.fdr,
scores: permutations.scores
};
};
phantasus.MarkerSelection.prototype = {
toString: function () {
return 'Marker Selection';
},
init: function (project, form) {
var _this = this;
var updateAB = function (fieldNames) {
var ids = [];
if (fieldNames != null) {
var vectors = phantasus.MetadataUtil.getVectors(project
.getFullDataset().getColumnMetadata(), fieldNames);
var idToIndices = phantasus.VectorUtil
.createValuesToIndicesMap(vectors);
idToIndices.forEach(function (indices, id) {
ids.push(id);
});
}
ids.sort();
form.setOptions('class_a', ids);
form.setOptions('class_b', ids);
};
var $field = form.$form.find('[name=field]');
$field.on('change', function (e) {
updateAB($(this).val());
});
if ($field[0].options.length > 0) {
$field.val($field[0].options[0].value);
}
updateAB($field.val());
var $metric = form.$form.find('[name=metric]');
$metric.on('change', function (e) {
var isFishy = $(this).val() === 'Fisher Exact Test';
form.setVisible('grouping_value', isFishy);
form.setVisible('permutations', !isFishy);
form.setVisible('number_of_markers', !isFishy);
});
form.setVisible('grouping_value', false);
},
gui: function (project) {
var dataset = project.getSortedFilteredDataset();
var fields = phantasus.MetadataUtil.getMetadataNames(dataset
.getColumnMetadata());
return [
{
name: 'metric',
options: phantasus.MarkerSelection.Functions,
value: phantasus.SignalToNoise.toString(),
type: 'select',
help: ''
},
{
name: 'grouping_value',
value: '1',
help: 'Class values are categorized into two groups based on whether dataset values are greater than or equal to this value'
},
{
name: 'field',
options: fields,
type: 'select',
multiple: true
},
{
name: 'class_a',
title: 'Class A',
options: [],
value: '',
type: 'checkbox-list',
multiple: true
},
{
name: 'class_b',
title: 'Class B',
options: [],
value: '',
type: 'checkbox-list',
multiple: true
},
{
name: 'number_of_markers',
value: '100',
type: 'text',
help: 'The initial number of markers to show in each direction.'
}, {
name: 'permutations',
value: '0',
type: 'text'
}];
},
execute: function (options) {
var project = options.project;
// classA and classB are arrays of Identifiers if run via user
// interface. If run via JSON, will be string arrays
var classA = options.input.class_a;
var classB = options.input.class_b;
if (classA.length === 0 && classB.length === 0) {
throw 'No samples in class A and class B';
}
if (classA.length === 0) {
throw 'No samples in class A';
}
if (classB.length === 0) {
throw 'No samples in class B';
}
for (var i = 0; i < classA.length; i++) {
var val = classA[i];
if (!(val instanceof phantasus.Identifier)) {
classA[i] = new phantasus.Identifier(
phantasus.Util.isArray(val) ? val : [val]);
}
}
for (var i = 0; i < classB.length; i++) {
var val = classB[i];
if (!(val instanceof phantasus.Identifier)) {
classB[i] = new phantasus.Identifier(
phantasus.Util.isArray(val) ? val : [val]);
}
}
var npermutations = parseInt(options.input.permutations);
var dataset = project.getSortedFilteredDataset();
var fieldNames = options.input.field;
if (!phantasus.Util.isArray(fieldNames)) {
fieldNames = [fieldNames];
}
var vectors = phantasus.MetadataUtil.getVectors(dataset
.getColumnMetadata(), fieldNames);
var idToIndices = phantasus.VectorUtil.createValuesToIndicesMap(vectors);
var aIndices = [];
var bIndices = [];
classA.forEach(function (id) {
var indices = idToIndices.get(id);
if (indices === undefined) {
throw new Error(id + ' not found in ' + idToIndices.keys());
}
aIndices = aIndices.concat(indices);
});
classB.forEach(function (id) {
var indices = idToIndices.get(id);
if (indices === undefined) {
throw new Error(id + ' not found in ' + idToIndices.keys());
}
bIndices = bIndices.concat(indices);
});
var f = phantasus.MarkerSelection.Functions
.fromString(options.input.metric);
var classASet = {};
for (var i = 0; i < aIndices.length; i++) {
classASet[aIndices[i]] = true;
}
for (var i = 0; i < bIndices.length; i++) {
if (classASet[bIndices[i]]) {
throw 'The sample was found in class A and class B';
}
}
var isFishy = f.toString() === phantasus.FisherExact.toString();
if ((aIndices.length === 1 || bIndices.length === 1)
&& !isFishy && f.toString() !== phantasus.MeanDifference.toString()) {
f = phantasus.FoldChange;
}
var list1 = new phantasus.DatasetRowView(new phantasus.SlicedDatasetView(
dataset, null, aIndices));
var list2 = new phantasus.DatasetRowView(new phantasus.SlicedDatasetView(
dataset, null, bIndices));
// remove
// other
// marker
// selection
// fields
var markerSelectionFields = phantasus.MarkerSelection.Functions.map(
function (f) {
return f.toString();
}).concat(['odds_ratio', 'FDR(BH)', 'p_value']);
markerSelectionFields.forEach(function (name) {
var index = phantasus.MetadataUtil.indexOf(dataset.getRowMetadata(),
name);
if (index !== -1) {
dataset.getRowMetadata().remove(index);
options.heatMap.removeTrack(name, false);
}
});
var v = dataset.getRowMetadata().add(f.toString());
var vectors = [v];
var comparisonVector = dataset.getColumnMetadata().add('Comparison');
for (var i = 0; i < aIndices.length; i++) {
comparisonVector.setValue(aIndices[i], 'A');
}
for (var i = 0; i < bIndices.length; i++) {
comparisonVector.setValue(bIndices[i], 'B');
}
function done(result) {
if (result) {
var pvalueVector = dataset.getRowMetadata().add('p_value');
var fdrVector = dataset.getRowMetadata().add('FDR(BH)');
var kVector = dataset.getRowMetadata().add('k');
for (var i = 0, size = pvalueVector.size(); i < size; i++) {
pvalueVector.setValue(i, result.rowSpecificPValues[i]);
fdrVector.setValue(i, result.fdr[i]);
kVector.setValue(i, result.k[i]);
v.setValue(i, result.scores[i]);
}
kVector.getProperties().set(phantasus.VectorKeys.FORMATTER, {pattern: 'i'});
vectors.push(pvalueVector);
vectors.push(fdrVector);
vectors.push(kVector);
}
if (project.getRowFilter().getFilters().length > 0) {
project.getRowFilter().setAnd(true, true);
}
var rowFilters = project.getRowFilter().getFilters();
// remove existing top n filters
for (var i = 0; i < rowFilters.length; i++) {
if (rowFilters[i] instanceof phantasus.TopNFilter) {
project.getRowFilter().remove(i, true);
i--;
}
}
if (!isFishy) {
project.getRowFilter().add(
new phantasus.TopNFilter(
parseInt(options.input.number_of_markers),
phantasus.TopNFilter.TOP_BOTTOM, vectors[0]
.getName()), true);
}
project.setRowFilter(project.getRowFilter(), true);
project.setRowSortKeys([
new phantasus.SortKey(vectors[0].getName(),
isFishy ? phantasus.SortKey.SortOrder.ASCENDING
: phantasus.SortKey.SortOrder.DESCENDING)], true);
// select samples used in comparison
var selectedColumnIndices = new phantasus.Set();
aIndices.forEach(function (index) {
selectedColumnIndices.add(index);
});
bIndices.forEach(function (index) {
selectedColumnIndices.add(index);
});
project.getColumnSelectionModel().setViewIndices(selectedColumnIndices, true);
project.setColumnSortKeys([
new phantasus.SortKey(comparisonVector
.getName(), phantasus.SortKey.SortOrder.ASCENDING)], true);
project.trigger('trackChanged', {
vectors: vectors,
display: vectors.map(function () {
return 'text';
}),
columns: false
});
project.trigger('trackChanged', {
vectors: [comparisonVector],
display: ['color'],
columns: true
});
}
if (isFishy) {
var groupingValue = parseFloat(options.input.grouping_value);
var oddsRatioVector = dataset.getRowMetadata().add('odds_ratio');
var fdrVector = dataset.getRowMetadata().add('FDR(BH)');
var contingencyTableVector = dataset.getRowMetadata().add(
'contingency_table');
var pvalues = [];
for (var i = 0, size = dataset.getRowCount(); i < size; i++) {
var abcd = phantasus.createContingencyTable(list1.setIndex(i),
list2.setIndex(i), groupingValue);
contingencyTableVector.setValue(i, '[[' + abcd[0] + ', '
+ abcd[1] + '], [' + abcd[2] + ', ' + abcd[3] + ']]');
var ratio = (abcd[0] * abcd[3]) / (abcd[1] * abcd[2]);
if (isNaN(ratio) || ratio === Number.POSITIVE_INFINITY) {
ratio = 0;
}
oddsRatioVector.setValue(i, ratio);
v.setValue(i, phantasus.FisherExact.fisherTest(abcd[0], abcd[1],
abcd[2], abcd[3]));
pvalues.push(v.getValue(i));
}
var fdr = phantasus.FDR_BH(pvalues);
for (var i = 0, size = dataset.getRowCount(); i < size; i++) {
fdrVector.setValue(i, fdr[i]);
}
vectors.push(oddsRatioVector);
vectors.push(fdrVector);
vectors.push(contingencyTableVector);
done();
} else {
if (npermutations > 0) {
var subset = new phantasus.SlicedDatasetView(dataset, null,
aIndices.concat(bIndices));
options.input.background = options.input.background && typeof Worker !== 'undefined';
options.input.numClassA = aIndices.length;
options.input.npermutations = npermutations;
if (options.input.background) {
var blob = new Blob(
[
'self.onmessage = function(e) {'
+ 'importScripts(e.data.scripts);'
+ 'self.postMessage(phantasus.MarkerSelection.execute(phantasus.Dataset.fromJSON(e.data.dataset), e.data.input));'
+ '}']);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.postMessage({
scripts: phantasus.Util.getScriptPath(),
dataset: phantasus.Dataset.toJSON(subset, {
columnFields: [],
rowFields: [],
seriesIndices: [0]
}),
input: options.input
});
worker.onmessage = function (e) {
done(e.data);
worker.terminate();
window.URL.revokeObjectURL(url);
};
return worker;
} else {
done(phantasus.MarkerSelection.execute(subset, options.input));
}
} else {
for (var i = 0, size = dataset.getRowCount(); i < size; i++) {
v.setValue(i, f(list1.setIndex(i), list2.setIndex(i)));
}
// no permutations, compute asymptotic p-value if t-test
if (f.toString() === phantasus.TTest.toString() && typeof jStat !== 'undefined') {
var pvalueVector = dataset.getRowMetadata().add('p_value');
var fdrVector = dataset.getRowMetadata().add('FDR(BH)');
var rowSpecificPValues = new Float32Array(dataset.getRowCount());
for (var i = 0, size = dataset.getRowCount(); i < size; i++) {
list1.setIndex(i);
list2.setIndex(i);
var m1 = phantasus.Mean(list1);
var m2 = phantasus.Mean(list2);
var v1 = phantasus.Variance(list1, m1);
var v2 = phantasus.Variance(list2, m2);
var n1 = phantasus.CountNonNaN(list1);
var n2 = phantasus.CountNonNaN(list2);
var df = phantasus.DegreesOfFreedom(v1, v2, n1, n2);
var t = v.getValue(i);
var p = 2.0 * (1 - jStat.studentt.cdf(Math.abs(t), df));
rowSpecificPValues[i] = p;
pvalueVector.setValue(i, p);
}
vectors.push(pvalueVector);
var fdr = phantasus.FDR_BH(rowSpecificPValues);
for (var i = 0, size = dataset.getRowCount(); i < size; i++) {
fdrVector.setValue(i, fdr[i]);
}
vectors.push(fdrVector);
}
done();
}
}
}
};
phantasus.NearestNeighbors = function () {
};
phantasus.NearestNeighbors.Functions = [
phantasus.Cosine, phantasus.Euclidean,
phantasus.Jaccard, phantasus.KendallsCorrelation, phantasus.Pearson, phantasus.Spearman,
phantasus.WeightedMean];
phantasus.NearestNeighbors.Functions.fromString = function (s) {
for (var i = 0; i < phantasus.NearestNeighbors.Functions.length; i++) {
if (phantasus.NearestNeighbors.Functions[i].toString() === s) {
return phantasus.NearestNeighbors.Functions[i];
}
}
throw new Error(s + ' not found');
};
phantasus.NearestNeighbors.execute = function (dataset, input) {
var f = phantasus.NearestNeighbors.Functions.fromString(input.metric);
var permutations = new phantasus.PermutationPValues(dataset, null, null, input.npermutations, f,
phantasus.Vector.fromArray('', input.listValues));
return {
rowSpecificPValues: permutations.rowSpecificPValues,
k: permutations.k,
fdr: permutations.fdr,
scores: permutations.scores
};
};
phantasus.NearestNeighbors.prototype = {
toString: function () {
return 'Nearest Neighbors';
},
init: function (project, form) {
var $selectedOnly = form.$form.find('[name=use_selected_only]').parent();
form.$form.find('[name=compute_nearest_neighbors_of]').on(
'change',
function (e) {
var val = $(this).val();
if (val === 'selected rows' || val === 'column annotation') {
$($selectedOnly.contents()[1]).replaceWith(
document.createTextNode(' Use selected columns only'));
} else {
$($selectedOnly.contents()[1]).replaceWith(
document.createTextNode(' Use selected rows only'));
}
form.setVisible('annotation', false);
if (val === 'column annotation' || val === 'row annotation') {
var metadata = val === 'column annotation'
? project.getFullDataset().getColumnMetadata()
: project.getFullDataset().getRowMetadata();
var names = [];
// get numeric columns only
for (var i = 0; i < metadata.getMetadataCount(); i++) {
var v = metadata.get(i);
if (phantasus.VectorUtil.getDataType(v) === 'number') {
names.push(v.getName());
}
}
names.sort(function (a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return (a < b ? -1 : (a === b ? 0 : 1));
});
form.setOptions('annotation', names);
form.setVisible('annotation', true);
}
});
$($selectedOnly.contents()[1]).replaceWith(
document.createTextNode(' Use selected columns only'));
form.setVisible('annotation', false);
},
gui: function () {
return [
{
name: 'metric',
options: phantasus.NearestNeighbors.Functions,
value: phantasus.Pearson.toString(),
type: 'select'
}, {
name: 'compute_nearest_neighbors_of',
options: ['selected rows', 'selected columns', 'column annotation', 'row annotation'],
value: 'selected rows',
type: 'radio'
}, {
name: 'use_selected_only',
type: 'checkbox'
}, {
name: 'annotation',
type: 'bootstrap-select'
}, {
name: 'permutations',
value: '0',
type: 'text'
}];
},
execute: function (options) {
var project = options.project;
var isColumns = options.input.compute_nearest_neighbors_of == 'selected columns' ||
options.input.compute_nearest_neighbors_of == 'row annotation';
var isAnnotation = options.input.compute_nearest_neighbors_of == 'column annotation' ||
options.input.compute_nearest_neighbors_of == 'row annotation';
var heatMap = options.heatMap;
var f = phantasus.NearestNeighbors.Functions.fromString(options.input.metric);
var dataset = project.getSortedFilteredDataset();
if (isColumns) {
// compute the nearest neighbors of row, so need to transpose
dataset = phantasus.DatasetUtil.transposedView(dataset);
}
var selectedIndices = (isColumns ? project.getColumnSelectionModel()
: project.getRowSelectionModel()).getViewIndices().values();
if (!isAnnotation && selectedIndices.length === 0) {
throw new Error('No ' + (isColumns ? 'columns' : 'rows')
+ ' selected');
}
var spaceIndices = null;
if (options.input.use_selected_only) {
spaceIndices = (!isColumns ? project.getColumnSelectionModel()
: project.getRowSelectionModel()).getViewIndices().values();
dataset = phantasus.DatasetUtil.slicedView(dataset, null,
spaceIndices);
}
var d1 = phantasus.DatasetUtil.slicedView(dataset, selectedIndices, null);
var nearestNeighborsList;
if (isAnnotation) {
nearestNeighborsList = dataset.getColumnMetadata().getByName(options.input.annotation);
if (!nearestNeighborsList) {
throw new Error('No annotation selected.');
}
} else {
if (d1.getRowCount() > 1) {
// collapse each column in the dataset to a single value
var columnView = new phantasus.DatasetColumnView(d1);
var newDataset = new phantasus.Dataset({
name: '',
rows: 1,
columns: d1.getColumnCount()
});
for (var j = 0, ncols = d1.getColumnCount(); j < ncols; j++) {
var v = phantasus.Percentile(columnView.setIndex(j), 50);
newDataset.setValue(0, j, v);
}
d1 = newDataset;
}
nearestNeighborsList = new phantasus.DatasetRowView(d1);
}
var npermutations = parseInt(options.input.permutations);
var scoreVector = dataset.getRowMetadata().add(f.toString());
if (npermutations > 0) {
if (options.input.background === undefined) {
options.input.background = true;
}
options.input.background = options.input.background && typeof Worker !== 'undefined';
options.input.npermutations = npermutations;
var done = function (result) {
var pvalueVector = dataset.getRowMetadata().add('p_value');
var fdrVector = dataset.getRowMetadata().add('FDR(BH)');
var kVector = dataset.getRowMetadata().add('k');
for (var i = 0, size = pvalueVector.size(); i < size; i++) {
pvalueVector.setValue(i, result.rowSpecificPValues[i]);
fdrVector.setValue(i, result.fdr[i]);
kVector.setValue(i, result.k[i]);
scoreVector.setValue(i, result.scores[i]);
}
kVector.getProperties().set(phantasus.VectorKeys.FORMATTER, {pattern: 'i'});
var vectors = [pvalueVector, fdrVector, kVector, scoreVector];
project.trigger('trackChanged', {
vectors: vectors,
display: ['text'],
columns: isColumns
});
};
var listValues = new Float32Array(nearestNeighborsList.size());
for (var i = 0, size = listValues.length; i < size; i++) {
listValues[i] = nearestNeighborsList.getValue(i);
}
options.input.listValues = listValues;
if (options.input.background) {
var blob = new Blob(
[
'self.onmessage = function(e) {'
+ 'importScripts(e.data.scripts);'
+
'self.postMessage(phantasus.NearestNeighbors.execute(phantasus.Dataset.fromJSON(e.data.dataset), e.data.input));'
+ '}']);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.postMessage({
scripts: phantasus.Util.getScriptPath(),
dataset: phantasus.Dataset.toJSON(dataset, {
columnFields: [],
rowFields: [],
seriesIndices: [0]
}),
input: options.input
});
worker.onmessage = function (e) {
done(e.data);
worker.terminate();
window.URL.revokeObjectURL(url);
};
return worker;
} else {
done(phantasus.NearestNeighbors.execute(dataset, options.input));
}
} else {
var datasetRowView = new phantasus.DatasetRowView(dataset);
for (var i = 0, size = dataset.getRowCount(); i < size; i++) {
scoreVector.setValue(i, f(nearestNeighborsList, datasetRowView.setIndex(i)));
}
if (!isColumns) {
project.setRowSortKeys([
new phantasus.SortKey(f.toString(),
phantasus.SortKey.SortOrder.DESCENDING)], true);
} else {
project.setColumnSortKeys([
new phantasus.SortKey(f.toString(),
phantasus.SortKey.SortOrder.DESCENDING)], true);
}
project.trigger('trackChanged', {
vectors: [scoreVector],
display: ['text'],
columns: isColumns
});
}
}
};
phantasus.NewHeatMapTool = function () {
};
phantasus.NewHeatMapTool.prototype = {
toString: function () {
return 'New Heat Map';
},
// gui : function() {
// return [ {
// name : 'name',
// type : 'text'
// }, {
// name : 'include_selected_rows',
// type : 'checkbox',
// value : true
// }, {
// name : 'include_selected_columns',
// type : 'checkbox',
// value : true
// } ];
// },
execute: function (options) {
var project = options.project;
var heatMap = options.heatMap;
var dataset = project.getSelectedDataset({
selectedRows: true,
selectedColumns: true
});
phantasus.DatasetUtil.shallowCopy(dataset);
var indices = phantasus.Util.getTrueIndices(dataset);
var currentSessionPromise = dataset.getESSession();
dataset.setESSession(new Promise(function (resolve, reject) {
currentSessionPromise.then(function (esSession) {
var args = {
es: esSession,
rows: indices.rows,
columns: indices.columns
};
var req = ocpu.call("subsetES", args, function (newSession) {
dataset.esSource = 'original';
resolve(newSession);
//console.log('Old dataset session: ', esSession, ', New dataset session: ', newSession);
}, false, "::es");
req.fail(function () {
reject();
});
})
}));
//phantasus.DatasetUtil.toESSessionPromise(dataset);
// console.log(dataset);
// TODO see if we can subset dendrograms
// only handle contiguous selections for now
// if (heatMap.columnDendrogram != null) {
// var indices = project.getColumnSelectionModel().getViewIndices()
// .toArray();
// phantasus.DendrogramUtil.leastCommonAncestor();
// }
// if (heatMap.rowDendrogram != null) {
//
// }
var heatmap = new phantasus.HeatMap({
name: heatMap.getName(),
dataset: dataset,
parent: heatMap,
symmetric: project.isSymmetric() && dataset.getColumnCount() === dataset.getRowCount()
});
}
};
phantasus.OpenDatasetTool = function () {
};
phantasus.OpenDatasetTool.prototype = {
toString: function () {
return 'Open Dataset';
},
_read: function (options, deferred) {
var _this = this;
var project = options.project;
var heatMap = options.heatMap;
var file = options.input.file;
var action = options.input.open_file_action;
var dataset = project.getSortedFilteredDataset();
deferred.fail(function (err) {
var message = [
'Error opening ' + phantasus.Util.getFileName(file)
+ '.'];
if (err.message) {
message.push('<br />Cause: ');
message.push(err.message);
}
phantasus.FormBuilder.showInModal({
title: 'Error',
html: message.join(''),
focus: document.activeElement
});
});
deferred
.done(function (newDataset) {
var extension = phantasus.Util.getExtension(phantasus.Util
.getFileName(file));
var filename = phantasus.Util.getBaseFileName(phantasus.Util
.getFileName(file));
if (action === 'append' || action === 'append columns') {
// "append": append rows to current dataset
var appendRows = action === 'append';
// rename fields?
_.each(heatMap.options.rows, function (item) {
if (item.renameTo) {
var v = newDataset.getRowMetadata().getByName(
item.field);
if (v) {
v.setName(item.renameTo);
}
}
});
_.each(heatMap.options.columns, function (item) {
if (item.renameTo) {
var v = newDataset.getColumnMetadata()
.getByName(item.field);
if (v) {
v.setName(item.renameTo);
}
}
});
if (heatMap.options.datasetReady) {
heatMap.options.datasetReady(newDataset);
}
var currentDatasetMetadataNames = phantasus.MetadataUtil
.getMetadataNames(!appendRows ? dataset
.getRowMetadata() : dataset
.getColumnMetadata());
var newDatasetMetadataNames = phantasus.MetadataUtil
.getMetadataNames(!appendRows ? newDataset
.getRowMetadata() : newDataset
.getColumnMetadata());
if (currentDatasetMetadataNames.length > 1
|| newDatasetMetadataNames.length > 1) {
_this
._matchAppend(
newDatasetMetadataNames,
currentDatasetMetadataNames,
heatMap,
function (appendOptions) {
heatMap
.getProject()
.setFullDataset(
appendRows ? new phantasus.JoinedDataset(
dataset,
newDataset,
appendOptions.current_dataset_annotation_name,
appendOptions.new_dataset_annotation_name)
: new phantasus.TransposedDatasetView(
new phantasus.JoinedDataset(
new phantasus.TransposedDatasetView(
dataset),
new phantasus.TransposedDatasetView(
newDataset),
appendOptions.current_dataset_annotation_name,
appendOptions.new_dataset_annotation_name)),
true);
if (heatMap.options.renderReady) {
heatMap.options
.renderReady(heatMap);
heatMap.updateDataset();
}
if (appendRows) {
heatMap
.getHeatMapElementComponent()
.getColorScheme()
.setSeparateColorSchemeForRowMetadataField(
'Source');
var sourcesSet = phantasus.VectorUtil
.getSet(heatMap
.getProject()
.getFullDataset()
.getRowMetadata()
.getByName(
'Source'));
sourcesSet
.forEach(function (source) {
heatMap
.autoDisplay({
extension: phantasus.Util
.getExtension(source),
filename: source
});
});
}
heatMap.tabManager
.setTabTitle(
heatMap.tabId,
heatMap
.getProject()
.getFullDataset()
.getRowCount()
+ ' row'
+ phantasus.Util
.s(heatMap
.getProject()
.getFullDataset()
.getRowCount())
+ ' x '
+ heatMap
.getProject()
.getFullDataset()
.getColumnCount()
+ ' column'
+ phantasus.Util
.s(heatMap
.getProject()
.getFullDataset()
.getColumnCount()));
heatMap.revalidate();
});
} else { // no need to prompt
heatMap
.getProject()
.setFullDataset(
appendRows ? new phantasus.JoinedDataset(
dataset,
newDataset,
currentDatasetMetadataNames[0],
newDatasetMetadataNames[0])
: new phantasus.TransposedDatasetView(
new phantasus.JoinedDataset(
new phantasus.TransposedDatasetView(
dataset),
new phantasus.TransposedDatasetView(
newDataset),
currentDatasetMetadataNames[0],
newDatasetMetadataNames[0])),
true);
if (heatMap.options.renderReady) {
heatMap.options.renderReady(heatMap);
heatMap.updateDataset();
}
if (appendRows) {
heatMap
.getHeatMapElementComponent()
.getColorScheme()
.setSeparateColorSchemeForRowMetadataField(
'Source');
var sourcesSet = phantasus.VectorUtil
.getSet(heatMap.getProject()
.getFullDataset()
.getRowMetadata().getByName(
'Source'));
sourcesSet.forEach(function (source) {
heatMap.autoDisplay({
extension: phantasus.Util
.getExtension(source),
filename: source
});
});
}
heatMap.tabManager.setTabTitle(heatMap.tabId,
heatMap.getProject().getFullDataset()
.getRowCount()
+ ' row'
+ phantasus.Util.s(heatMap
.getProject()
.getFullDataset()
.getRowCount())
+ ' x '
+ heatMap.getProject()
.getFullDataset()
.getColumnCount()
+ ' column'
+ phantasus.Util.s(heatMap
.getProject()
.getFullDataset()
.getColumnCount()));
heatMap.revalidate();
}
} else if (action === 'overlay') {
_this
._matchOverlay(
phantasus.MetadataUtil
.getMetadataNames(newDataset
.getColumnMetadata()),
phantasus.MetadataUtil
.getMetadataNames(dataset
.getColumnMetadata()),
phantasus.MetadataUtil
.getMetadataNames(newDataset
.getRowMetadata()),
phantasus.MetadataUtil
.getMetadataNames(dataset
.getRowMetadata()),
heatMap,
function (appendOptions) {
phantasus.DatasetUtil.overlay({
dataset: dataset,
newDataset: newDataset,
rowAnnotationName: appendOptions.current_dataset_row_annotation_name,
newRowAnnotationName: appendOptions.new_dataset_row_annotation_name,
columnAnnotationName: appendOptions.current_dataset_column_annotation_name,
newColumnAnnotationName: appendOptions.new_dataset_column_annotation_name
});
});
} else if (action === 'open') { // new tab
console.log('open')
if (newDataset.length && newDataset.length > 0) {
for (var i = 0; i < newDataset.length; i++) {
new phantasus.HeatMap({
dataset: newDataset[i],
name: newDataset[i].seriesNames[0],
parent: heatMap,
inheritFromParent: false
});
}
} else {
new phantasus.HeatMap({
dataset: newDataset,
parent: heatMap,
inheritFromParent: false
});
}
} else {
console.log('Unknown action: ' + action);
}
if (action !== 'open') {
heatMap.revalidate();
}
});
},
execute: function (options) {
var file = options.input.file;
console.log("openDatasetTool.execute", file);
var _this = this;
var d = $.Deferred();
phantasus.OpenDatasetTool
.fileExtensionPrompt(file,
function (readOptions) {
if (!readOptions) {
readOptions = {};
}
readOptions.interactive = true;
if (options.input.isGEO) {
readOptions.isGEO = true;
}
if (options.input.preloaded) {
readOptions.preloaded = true;
}
var deferred = phantasus.DatasetUtil.read(file,
readOptions);
deferred.always(function () {
d.resolve();
});
_this._read(options, deferred);
});
return d;
}, // prompt for metadata field name in dataset and in file
_matchAppend: function (newDatasetMetadataNames,
currentDatasetMetadataNames, heatMap, callback) {
var tool = {};
tool.execute = function (options) {
return options.input;
};
tool.toString = function () {
return 'Select Fields';
};
tool.gui = function () {
var items = [
{
name: 'current_dataset_annotation_name',
options: currentDatasetMetadataNames,
type: 'select',
value: 'id',
required: true
}];
items.push({
name: 'new_dataset_annotation_name',
type: 'select',
value: 'id',
options: newDatasetMetadataNames,
required: true
});
return items;
};
phantasus.HeatMap.showTool(tool, heatMap, callback);
},
_matchOverlay: function (newDatasetColumnMetadataNames,
currentDatasetColumnMetadataNames, newDatasetRowMetadataNames,
currentDatasetRowMetadataNames, heatMap, callback) {
var tool = {};
tool.execute = function (options) {
return options.input;
};
tool.toString = function () {
return 'Select Fields';
};
tool.gui = function () {
var items = [];
items.push({
name: 'current_dataset_column_annotation_name',
options: currentDatasetColumnMetadataNames,
type: 'select',
value: 'id',
required: true
});
items.push({
name: 'new_dataset_column_annotation_name',
type: 'select',
value: 'id',
options: newDatasetColumnMetadataNames,
required: true
});
items.push({
name: 'current_dataset_row_annotation_name',
options: currentDatasetRowMetadataNames,
type: 'select',
value: 'id',
required: true
});
items.push({
name: 'new_dataset_row_annotation_name',
type: 'select',
value: 'id',
options: newDatasetRowMetadataNames,
required: true
});
return items;
};
phantasus.HeatMap.showTool(tool, heatMap, callback);
}
};
phantasus.OpenDatasetTool.fileExtensionPrompt = function (file, callback) {
var ext = phantasus.Util.getExtension(phantasus.Util.getFileName(file));
var deferred;
if (ext === 'seg' || ext === 'segtab') {
this._promptSegtab(function (regions) {
callback(regions);
});
} else {
callback(null);
}
};
phantasus.OpenDatasetTool._promptMaf = function (promptCallback) {
var formBuilder = new phantasus.FormBuilder();
formBuilder
.append({
name: 'MAF_gene_symbols',
value: '',
type: 'textarea',
required: true,
help: 'Enter one gene symbol per line to filter genes. Leave blank to show all genes.'
});
phantasus.FormBuilder
.showInModal({
title: 'Gene Symbols',
html: formBuilder.$form,
close: 'OK',
onClose: function () {
var text = formBuilder.getValue('MAF_gene_symbols');
var lines = phantasus.Util.splitOnNewLine(text);
var mafGeneFilter = new phantasus.Map();
for (var i = 0, nlines = lines.length, counter = 0; i < nlines; i++) {
var line = lines[i];
if (line !== '') {
mafGeneFilter.set(line, counter++);
}
}
var readOptions = mafGeneFilter.size() > 0 ? {
mafGeneFilter: mafGeneFilter
} : null;
promptCallback(readOptions);
}
});
};
phantasus.OpenDatasetTool._promptSegtab = function (promptCallback) {
var formBuilder = new phantasus.FormBuilder();
formBuilder
.append({
name: 'regions',
value: '',
type: 'textarea',
required: true,
help: 'Define the regions over which you want to define the CNAs. Enter one region per line. Each line should contain region_id, chromosome, start, and end separated by a tab. Leave blank to use all unique segments in the segtab file as regions.'
});
phantasus.FormBuilder
.showInModal({
title: 'Regions',
html: formBuilder.$form,
close: 'OK',
onClose: function () {
var text = formBuilder.getValue('regions');
var lines = phantasus.Util.splitOnNewLine(text);
var regions = [];
var tab = /\t/;
for (var i = 0, nlines = lines.length, counter = 0; i < nlines; i++) {
var line = lines[i];
if (line !== '') {
var tokens = line.split(tab);
if (tokens.length >= 4) {
regions.push({
id: tokens[0],
chromosome: tokens[1],
start: parseInt(tokens[2]),
end: parseInt(tokens[3])
});
}
}
}
var readOptions = regions.length > 0 ? {
regions: regions
} : null;
promptCallback(readOptions);
}
});
};
phantasus.OpenDendrogramTool = function (file) {
this._file = file;
};
phantasus.OpenDendrogramTool.prototype = {
toString: function () {
return 'Open Dendrogram';
},
init: function (project, form) {
var setValue = function (val) {
var isRows = val === 'Rows';
var names = phantasus.MetadataUtil.getMetadataNames(isRows ? project
.getFullDataset().getRowMetadata() : project
.getFullDataset().getColumnMetadata());
names.unshift('Newick file does not contain node ids');
form.setOptions('match_leaf_node_ids_to', names);
};
form.$form.find('[name=orientation]').on('change', function (e) {
setValue($(this).val());
});
setValue('Columns');
},
gui: function () {
return [{
name: 'orientation',
options: ['Columns', 'Rows'],
value: 'Columns',
type: 'radio'
}, {
name: 'match_leaf_node_ids_to',
options: [],
type: 'select'
}];
},
execute: function (options) {
var fileOrUrl = this._file;
var isColumns = options.input.orientation === 'Columns';
var dendrogramField = options.input.match_leaf_node_ids_to;
if (dendrogramField == '' || dendrogramField === 'Newick file does not contain node ids') {
dendrogramField = null;
}
var heatMap = options.heatMap;
var dendrogramDeferred = phantasus.Util.getText(fileOrUrl);
dendrogramDeferred
.done(function (text) {
var dataset = options.project.getSortedFilteredDataset();
if (isColumns) {
dataset = phantasus.DatasetUtil.transposedView(dataset);
}
var tree = phantasus.DendrogramUtil.parseNewick(text);
if (tree.leafNodes.length !== dataset.getRowCount()) {
throw new Error('# leaf nodes in dendrogram '
+ tree.leafNodes.length + ' != '
+ dataset.getRowCount());
}
var modelIndices = [];
if (dendrogramField != null) {
var vector = dataset.getRowMetadata().getByName(
dendrogramField);
var valueToIndex = phantasus.VectorUtil.createValueToIndexMap(vector);
for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
var newickId = tree.leafNodes[i].name;
var index = valueToIndex.get(newickId);
if (index === undefined) {
throw new Error('Unable to find dendrogram id '
+ tree.leafNodes[i].name
+ ' in annotations');
}
modelIndices.push(index);
}
} else {
// see if leaf node ids are indices
for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
var newickId = tree.leafNodes[i].name;
newickId = parseInt(newickId);
if (!isNaN(newickId)) {
modelIndices.push(newickId);
} else {
break;
}
}
if (modelIndices.length !== tree.leafNodes.length) {
modelIndices = [];
for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
modelIndices.push(i);
}
}
}
heatMap.setDendrogram(tree, isColumns, modelIndices);
});
}
};
phantasus.OpenFileTool = function (options) {
this.options = options || {};
};
phantasus.OpenFileTool.prototype = {
toString: function () {
return 'Open' + (this.options.file != null ? (' - ' + this.options.file.name) : '');
},
gui: function () {
var array = [{
name: 'open_file_action',
value: 'open',
type: 'bootstrap-select',
options: [{
name: 'Open session',
value: 'Open session'
}, {
divider: true
}, {
name: 'Append rows to current dataset',
value: 'append'
}, {
name: 'Append columns to current dataset',
value: 'append columns'
}, {
name: 'Overlay onto current dataset',
value: 'overlay'
}, {
name: 'Open dataset in new tab',
value: 'open'
}, {
divider: true
}, {
name: 'Open dendrogram',
value: 'Open dendrogram'
}]
}];
if (this.options.file == null) {
array.push({
name: 'file',
showLabel: false,
placeholder: 'Open your own file',
value: '',
type: 'file',
required: true,
help: phantasus.DatasetUtil.DATASET_AND_SESSION_FILE_FORMATS
});
}
array.options = {
ok: this.options.file != null,
size: 'modal-lg'
};
return array;
},
init: function (project, form, initOptions) {
var $preloaded = $('<div></div>');
form.$form
.find('[name=open_file_action]')
.on('change', function (e) {
var action = $(this).val();
if (action === 'append columns' ||
action === 'append' ||
action === 'open' ||
action === 'overlay') {
form.setHelpText('file',
phantasus.DatasetUtil.DATASET_FILE_FORMATS);
$preloaded.show();
} else if (action === 'Open dendrogram') {
form.setHelpText('file',
phantasus.DatasetUtil.DENDROGRAM_FILE_FORMATS);
$preloaded.hide();
} else if (action === 'Open session') {
form.setHelpText('file', phantasus.DatasetUtil.SESSION_FILE_FORMAT);
$preloaded.hide();
}
});
if (this.options.file == null) {
var _this = this;
var collapseId = _.uniqueId('phantasus');
$('<h4><a role="button" data-toggle="collapse" href="#'
+ collapseId
+ '" aria-expanded="false" aria-controls="'
+ collapseId + '">Preloaded datasets</a></h4>').appendTo($preloaded);
var $sampleDatasets = $('<div data-name="sampleData" id="' + collapseId + '" class="collapse"' +
' id="' + collapseId + '" style="overflow:auto;"></div>');
$preloaded.appendTo(form.$form);
var sampleDatasets = new phantasus.SampleDatasets({
$el: $sampleDatasets,
callback: function (heatMapOptions) {
_this.options.file = heatMapOptions.dataset;
_this.ok();
}
});
$sampleDatasets.appendTo($preloaded);
}
form.on('change', function (e) {
var value = e.value;
if (value !== '' && value != null) {
form.setValue('file', value);
_this.options.file = value;
_this.ok();
}
});
},
execute: function (options) {
var _this = this;
var isInteractive = this.options.file == null;
var heatMap = options.heatMap;
if (!isInteractive) {
options.input.file = this.options.file;
}
if (options.input.file.isGEO) {
options.input.isGEO = options.input.file.isGEO;
options.input.file = options.input.file.name;
}
if (options.input.file.preloaded) {
options.input.preloaded = options.input.file.preloaded;
options.input.file = options.input.file.name;
}
var project = options.project;
if (options.input.open_file_action === 'Open session') {
return phantasus.Util.getText(options.input.file).done(function (text) {
var options = JSON.parse(text);
options.tabManager = heatMap.getTabManager();
options.focus = true;
options.inheritFromParent = false;
options.landingPage = heatMap.options.landingPage;
new phantasus.HeatMap(options);
}).fail(function (err) {
phantasus.FormBuilder.showMessageModal({
title: 'Error',
message: 'Unable to load session',
focus: document.activeElement
});
});
} else if (options.input.open_file_action === 'append columns' ||
options.input.open_file_action === 'append' ||
options.input.open_file_action === 'open' ||
options.input.open_file_action === 'overlay') {
return new phantasus.OpenDatasetTool().execute(options);
} else if (options.input.open_file_action === 'Open dendrogram') {
phantasus.HeatMap.showTool(new phantasus.OpenDendrogramTool(
options.input.file), options.heatMap);
}
}
};
phantasus.PcaPlotTool = function (chartOptions) {
var _this = this;
this.project = chartOptions.project;
var project = this.project;
var drawFunction = null;
if (project.getFullDataset().getColumnCount() <= 1) {
throw new Error("Not enough columns (at least 2 required)");
}
if (_.size(project.getRowFilter().enabledFilters) > 0 || _.size(project.getColumnFilter().enabledFilters) > 0) {
phantasus.FormBuilder.showInModal({
title: 'Warning',
html: 'Your dataset is filtered.<br/>PCA Plot will apply to unfiltered dataset. Consider using New Heat Map tool.',
z: 10000
});
}
this.$el = $('<div class="container-fluid">'
+ '<div class="row">'
+ '<div data-name="configPane" class="col-xs-2"></div>'
+ '<div class="col-xs-10"><div style="position:relative;" data-name="chartDiv"></div></div>'
+ '<div class=""'
+ '</div></div>');
var formBuilder = new phantasus.FormBuilder({
formStyle: 'vertical'
});
this.formBuilder = formBuilder;
var rowOptions = [];
var columnOptions = [];
var numericRowOptions = [];
var numericColumnOptions = [];
var options = [];
var numericOptions = [];
var pcaOptions = [];
var naOptions = [{
name: "mean",
value: "mean"
}, {
name: "median",
value: "median"
}];
var updateOptions = function () {
var dataset = project.getFullDataset();
rowOptions = [{
name: "(None)",
value: ""
}];
columnOptions = [{
name: "(None)",
value: ""
}];
numericRowOptions = [{
name: "(None)",
value: ""
}];
numericColumnOptions = [{
name: "(None)",
value: ""
}];
options = [{
name: "(None)",
value: ""
}];
numericOptions = [{
name: "(None)",
value: ""
}];
pcaOptions = [];
for (var i = 1; i <= _this.project.getSelectedDataset().getColumnCount(); i++) {
pcaOptions.push({
name: "PC" + String(i),
value: i - 1
});
}
phantasus.MetadataUtil.getMetadataNames(dataset.getRowMetadata())
.forEach(
function (name) {
var dataType = phantasus.VectorUtil
.getDataType(dataset.getRowMetadata()
.getByName(name));
if (dataType === "number"
|| dataType === "[number]") {
numericRowOptions.push({
name: name + " (row)",
value: name
});
}
rowOptions.push({
name: name + " (row)",
value: name
});
});
phantasus.MetadataUtil.getMetadataNames(dataset.getColumnMetadata())
.forEach(
function (name) {
var dataType = phantasus.VectorUtil
.getDataType(dataset.getColumnMetadata()
.getByName(name));
if (dataType === "number"
|| dataType === "[number]") {
numericColumnOptions.push({
name: name + " (column)",
value: name
});
}
columnOptions.push({
name: name + " (column)",
value: name
});
});
};
updateOptions();
formBuilder.append({
name: "size",
type: "bootstrap-select",
options: numericColumnOptions
});
formBuilder.append({
name: 'shape',
type: 'bootstrap-select',
options: columnOptions
});
formBuilder.append({
name: "color",
type: "bootstrap-select",
options: columnOptions
});
formBuilder.append({
name: "x-axis",
type: "bootstrap-select",
options: pcaOptions,
value: 0
});
formBuilder.append({
name: "y-axis",
type: "bootstrap-select",
options: pcaOptions,
value: 1
});
formBuilder.append({
name: "label",
type: "bootstrap-select",
options: columnOptions,
value: columnOptions.indexOf('title') ? 'title' : null
});
formBuilder.append({
name: 'visible_labels',
type: 'bootstrap-select',
options: ['On', 'Off'],
value: 'On'
});
formBuilder.append({
name: 'export_to_SVG',
type: 'button'
});
function setVisibility() {
formBuilder.setOptions("color", columnOptions, true);
formBuilder.setOptions("size", numericColumnOptions, true);
formBuilder.setOptions("label", columnOptions, true);
}
this.tooltip = [];
formBuilder.$form.find("select").on("change", function (e) {
setVisibility();
drawFunction();
});
setVisibility();
var trackChanged = function () {
//// console.log("track changed");
updateOptions();
setVisibility();
formBuilder.setOptions("x-axis", pcaOptions, true);
formBuilder.setOptions("y-axis", pcaOptions, true);
};
project.getColumnSelectionModel().on("selectionChanged.chart", trackChanged);
project.getRowSelectionModel().on("selectionChanged.chart", trackChanged);
project.on("trackChanged.chart", trackChanged);
this.$chart = this.$el.find("[data-name=chartDiv]");
var $dialog = $('<div style="background:white;" title="Chart"></div>');
var $configPane = this.$el.find('[data-name=configPane]');
formBuilder.$form.appendTo($configPane);
this.$el.appendTo($dialog);
this.exportButton = this.$el.find('button[name=export_to_SVG]');
this.exportButton.toggle(false);
this.exportButton.on('click', function () {
var svgs = _this.$el.find(".main-svg");
var svgx = svgs[0].cloneNode(true);
svgs[1].childNodes.forEach(function (x) {
svgx.appendChild(x.cloneNode(true));
});
$(svgx).find('.drag').remove();
phantasus.Util.saveAsSVG(svgx, "pca-plot.svg");
});
$dialog.dialog({
close: function (event, ui) {
project.off('trackChanged.chart', trackChanged);
project.getRowSelectionModel().off('selectionChanged.chart', trackChanged);
project.getColumnSelectionModel().off('selectionChanged.chart', trackChanged);
$dialog.dialog('destroy').remove();
event.stopPropagation();
_this.pca = null;
},
resizable: true,
height: 620,
width: 950
});
this.$dialog = $dialog;
drawFunction = this.init();
drawFunction();
};
phantasus.PcaPlotTool.getVectorInfo = function (value) {
var field = value.substring(0, value.length - 2);
var isColumns = value.substring(value.length - 2) === '_c';
return {
field: field,
isColumns: isColumns
};
};
phantasus.PcaPlotTool.prototype = {
annotate: function (options) {
var _this = this;
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'annotation_name',
type: 'text',
required: true
});
formBuilder.append({
name: 'annotation_value',
type: 'text',
required: true
});
phantasus.FormBuilder
.showOkCancel({
title: 'Annotate Selection',
content: formBuilder.$form,
okCallback: function () {
var dataset = options.dataset;
var eventData = options.eventData;
var array = options.array;
var value = formBuilder.getValue('annotation_value');
var annotationName = formBuilder
.getValue('annotation_name');
// var annotate = formBuilder.getValue('annotate');
var isRows = true;
var isColumns = true;
var existingRowVector = null;
var rowVector = null;
if (isRows) {
existingRowVector = dataset.getRowMetadata()
.getByName(annotationName);
rowVector = dataset.getRowMetadata().add(
annotationName);
}
var existingColumnVector = null;
var columnVector = null;
if (isColumns) {
existingColumnVector = dataset.getColumnMetadata()
.getByName(annotationName);
columnVector = dataset.getColumnMetadata().add(
annotationName);
}
for (var p = 0, nselected = eventData.points.length; p < nselected; p++) {
var item = array[eventData.points[p].pointNumber];
if (isRows) {
if (_.isArray(item.row)) {
item.row.forEach(function (r) {
rowVector.setValue(r, value);
});
} else {
rowVector.setValue(item.row, value);
}
}
if (isColumns) {
columnVector.setValue(item.column, value);
}
}
if (isRows) {
phantasus.VectorUtil
.maybeConvertStringToNumber(rowVector);
_this.project.trigger('trackChanged', {
vectors: [rowVector],
display: existingRowVector != null ? []
: [phantasus.VectorTrack.RENDER.TEXT],
columns: false
});
}
if (isColumns) {
phantasus.VectorUtil
.maybeConvertStringToNumber(columnVector);
_this.project.trigger('trackChanged', {
vectors: [columnVector],
display: existingColumnVector != null ? []
: [phantasus.VectorTrack.RENDER.TEXT],
columns: true
});
}
}
});
},
init: function () {
var _this = this;
var dataset = _this.project.getFullDataset();
return function () {
_this.$chart.empty();
var plotlyDefaults = phantasus.PcaPlotTool.getPlotlyDefaults();
var data = [];
var layout = plotlyDefaults.layout;
var config = plotlyDefaults.config;
var colorBy = _this.formBuilder.getValue('color');
var sizeBy = _this.formBuilder.getValue('size');
var shapeBy = _this.formBuilder.getValue('shape');
var pc1 = _this.formBuilder.getValue('x-axis');
var pc2 = _this.formBuilder.getValue('y-axis');
var label = _this.formBuilder.getValue('label');
var drawLabels = _this.formBuilder.getValue('visible_labels') === 'On';
var getTrueVector = function (vector) {
while (vector && vector.indices && vector.indices.length === 0) {
vector = vector.v;
}
return vector;
};
var colorByVector = getTrueVector(dataset.getColumnMetadata().getByName(colorBy));
var sizeByVector = getTrueVector(dataset.getColumnMetadata().getByName(sizeBy));
var shapeByVector = getTrueVector(dataset.getColumnMetadata().getByName(shapeBy));
var textByVector = getTrueVector(dataset.getColumnMetadata().getByName(label));
_this.colorByVector = colorByVector;
var na = 'mean';
var color = colorByVector ? [] : null;
var size = sizeByVector ? [] : 12;
var shapes = shapeByVector ? [] : null;
var text = null;
if (sizeByVector) {
var minMax = phantasus.VectorUtil.getMinMax(sizeByVector);
var sizeFunction = d3.scale.linear()
.domain([minMax.min, minMax.max])
.range([6, 19])
.clamp(true);
size = _.map(phantasus.VectorUtil.toArray(sizeByVector), sizeFunction);
}
if (textByVector) {
text = phantasus.VectorUtil.toArray(textByVector);
}
if (shapeByVector) {
var allShapes = ['circle', 'square', 'diamond', 'cross', 'triangle-up', 'star', 'hexagram', 'bowtie', 'diamond-cross', 'hourglass', 'hash-open'];
var uniqShapes = {};
shapes = _.map(phantasus.VectorUtil.toArray(shapeByVector), function (value) {
if (!uniqShapes[value]) {
uniqShapes[value] = allShapes[_.size(uniqShapes) % _.size(allShapes)];
}
return uniqShapes[value]
});
if (_.size(uniqShapes) > _.size(allShapes)) {
phantasus.FormBuilder.showInModal({
title: 'Warning',
html: 'Too much factors for shapes. Repeating will occur'
});
}
_.each(uniqShapes, function (shape, categoryName) {
data.push({
x: [1000], y: [1000],
marker: {
symbol: shape,
color: '#000000',
size: 10
},
name: categoryName,
legendgroup: 'shapes',
mode: "markers",
type: "scatter",
showlegend: true
});
});
}
if (colorByVector) {
var colorModel = _this.project.getColumnColorModel();
var uniqColors = {};
color = _.map(phantasus.VectorUtil.toArray(colorByVector), function (value) {
if (!uniqColors[value]) {
if (colorModel.containsDiscreteColor(colorByVector, value)
&& colorByVector.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
uniqColors[value] = colorModel.getMappedValue(colorByVector, value);
} else if (colorModel.isContinuous(colorByVector)) {
uniqColors[value] = colorModel.getContinuousMappedValue(colorByVector, value);
} else {
uniqColors[value] = phantasus.VectorColorModel.CATEGORY_ALL[_.size(uniqColors) % 60];
}
}
return uniqColors[value]
});
_.each(uniqColors, function (color, categoryName) {
data.push({
x: [1000], y: [1000],
marker: {
fillcolor: color,
color: color,
size: 10
},
name: categoryName,
legendgroup: 'colors',
mode: "markers",
type: "scatter",
showlegend: true
});
});
}
data.unshift({
marker: {
color: color,
size: size,
symbol: shapes
},
name: "",
mode: drawLabels ? "markers+text" : "markers",
text: text,
textfont: {
size: 11
},
textposition: "top right",
type: "scatter",
showlegend: false
});
var expressionSetPromise = dataset.getESSession();
expressionSetPromise.then(function (essession) {
var args = {
es: essession,
replacena: na
};
var drawResult = function () {
data[0].x = _this.pca.pca[pc1];
data[0].y = _this.pca.pca[pc2];
layout.margin = {
b: 40,
l: 60,
t: 25,
r: 10
};
var xmin = _.min(data[0].x),
xmax = _.max(data[0].x),
ymin = _.min(data[0].y),
ymax = _.max(data[0].y);
layout.xaxis = {
title: _this.pca.xlabs[pc1],
range: [xmin - (xmax - xmin) * 0.15, xmax + (xmax - xmin) * 0.15],
zeroline: false
};
layout.yaxis = {
title: _this.pca.xlabs[pc2],
range: [ymin - (ymax - ymin) * 0.15, ymax + (ymax - ymin) * 0.15],
zeroline: false
};
layout.showlegend = true;
var $chart = $('<div></div>');
var plot = $chart[0];
$chart.appendTo(_this.$chart);
Plotly.newPlot(plot, data, layout, config).then(Plotly.annotate);
_this.exportButton.toggle(true);
plot.on('plotly_selected', function(eventData) {
var indexes = new phantasus.Set();
eventData.points.forEach(function (point) {
indexes.add(point.pointIndex);
});
_this.project.getColumnSelectionModel().setViewIndices(indexes, true);
});
};
if (!_this.pca) {
var req = ocpu.call("calcPCA/print", args, function (session) {
_this.pca = JSON.parse(session.txt);
drawResult();
}, false, "::es");
req.fail(function () {
new Error("PcaPlot call failed" + req.responseText);
});
} else {
drawResult();
}
}).catch(function (reason) {
alert("Problems occurred during transforming dataset to ExpressionSet\n" + reason);
});
};
}
};
phantasus.PcaPlotTool.getPlotlyDefaults = function () {
var layout = {
hovermode: 'closest',
autosize: true,
// paper_bgcolor: 'rgb(255,255,255)',
// plot_bgcolor: 'rgb(229,229,229)',
showlegend: false,
margin: {
l: 80,
r: 10,
t: 8, // leave space for modebar
b: 14,
autoexpand: true
},
titlefont: {
size: 12
},
xaxis: {
zeroline: false,
titlefont: {
size: 12
},
// gridcolor: 'rgb(255,255,255)',
showgrid: true,
// showline: true,
showticklabels: true,
tickcolor: 'rgb(127,127,127)',
ticks: 'outside'
},
yaxis: {
zeroline: false,
titlefont: {
size: 12
},
// gridcolor: 'rgb(255,255,255)',
showgrid: true,
// showline: true,
showticklabels: true,
tickcolor: 'rgb(127,127,127)',
ticks: 'outside'
}
};
var config = {
modeBarButtonsToAdd: [],
showLink: false,
displayModeBar: true, // always show modebar
displaylogo: false,
staticPlot: false,
showHints: true,
doubleClick: "reset",
modeBarButtonsToRemove: ['sendDataToCloud', 'zoomIn2d', 'zoomOut2d', 'hoverCompareCartesian', 'hoverClosestCartesian', 'autoScale2d']
};
return {
layout: layout,
config: config
};
};
phantasus.ProbeDebugTool = function () {
};
phantasus.ProbeDebugTool.prototype = {
toString: function () {
return 'DEBUG: Probe Debug Tool';
},
execute: function (options) {
var project = options.project;
var dataset = project.getFullDataset();
var promise = $.Deferred();
phantasus.DatasetUtil.probeDataset(dataset).then(function (status) {
alert('Sync status:' + status.toString());
promise.resolve();
});
return promise;
}
};
phantasus.ReproduceTool = function (project) {
var _this = this;
this.$el = $('<div class="container-fluid">'
+ '<div class="row">'
+ '<div data-name="controlPane" class="col-xs-2">'
+ '<button id="copy_btn" class="btn btn-default btn-sm">Copy to clipboard</button>'
+ '<button id="save_env_btn" style="margin-top:15px; display: none" class="btn btn-default btn-sm">Save environment</button>'
+ '</div>'
+ '<div class="col-xs-10"><div data-name="codePane"></div></div>'
+ '<div class=""></div>'
+ '</div></div>');
this.codePane = this.$el.find('[data-name=codePane]');
this.copyBtn = this.$el.find('#copy_btn');
this.saveEnvBtn = this.$el.find('#save_env_btn');
this.copyBtn.on('click', function () {
_this.codePane.find('textarea').select();
document.execCommand('copy');
_this.codePane.find('textarea').blur();
});
var $dialog = $('<div style="background:white;" title="' + this.toString() + '"></div>');
this.$el.appendTo($dialog);
$dialog.dialog({
close: function (event, ui) {
$dialog.dialog('destroy').remove();
event.stopPropagation();
},
resizable: true,
height: 620,
width: 950
});
this.execute(project);
};
phantasus.ReproduceTool.prototype = {
experimentalWarning: true,
toString: function () {
return 'Reproduce in R code';
},
execute: function (project) {
this.codePane.empty();
phantasus.Util.createLoadingEl().appendTo(this.codePane);
var _this = this;
var dataset = project.getFullDataset();
dataset.getESSession().then(function (esSession) {
ocpu.call('reproduceInR/print', {
sessionName: esSession.key
}, function (newSession) {
var text = JSON.parse(newSession.txt)[0];
text = '# docker image: dzenkova/phantasus:' + PHANTASUS_BUILD + '\n' + text;
var textarea = $('<textarea style="height: 100%; width: 100%; resize: none;" id="codeArea" readonly>' + text + '</textarea>');
_this.codePane.empty();
textarea.appendTo(_this.codePane);
_this.saveEnvBtn.off();
_this.saveEnvBtn.on('click', function () {
window.open(newSession.getLoc() + 'R/env/rda', '_blank');
});
_this.saveEnvBtn.css('display', 'block');
}).fail(function () {
throw new Error('Failed to reproduce in R. See console');
});
});
}
};
phantasus.SaveDatasetTool = function () {
};
phantasus.SaveDatasetTool.prototype = {
toString: function () {
return 'Save Dataset';
},
init: function (project, form) {
form.find('file_name').prop('autofocus', true).focus();
var seriesNames = [];
var dataset = project.getFullDataset();
for (var i = 0, nseries = dataset.getSeriesCount(); i < nseries; i++) {
seriesNames.push(dataset.getName(i)); // TODO check data type
}
form.setOptions('series', seriesNames.length > 1 ? seriesNames : null);
form.setVisible('series', seriesNames.length > 1);
},
gui: function () {
return [
{
name: 'file_name',
type: 'text',
help: '<a target="_blank" href="http://support.lincscloud.org/hc/en-us/articles/202105453-GCT-Gene-Cluster-Text-Format-">GCT 1.3</a>'
+ ' or <a target="_blank" href="http://www.broadinstitute.org/cancer/software/genepattern/gp_guides/file-formats/sections/gct">GCT 1.2</a> file name',
required: true
}, {
name: 'file_format',
type: 'radio',
options: [{
name: 'GCT version 1.2',
value: '1.2'
}, {
name: 'GCT version 1.3',
value: '1.3'
}],
value: '1.3',
}, {
name: 'series',
type: 'select',
options: [],
required: true
}, {
name: 'save_selection_only',
type: 'checkbox',
required: true
}];
},
execute: function (options) {
var project = options.project;
var format = options.input.file_format;
var fileName = options.input.file_name;
if (fileName === '') {
fileName = 'dataset';
}
var series = options.input.series;
var heatMap = options.heatMap;
var dataset = options.input.save_selection_only ? project.getSelectedDataset() : project.getSortedFilteredDataset();
var writer;
if (format === '1.2') {
writer = new phantasus.GctWriter12();
} else if (format === '1.3') {
writer = new phantasus.GctWriter();
}
if (series != null) {
var seriesIndex = phantasus.DatasetUtil.getSeriesIndex(dataset, series);
if (seriesIndex === -1) {
seriesIndex = 0;
}
dataset = seriesIndex === 0 ? dataset : new phantasus.DatasetSeriesView(dataset, [seriesIndex]);
}
var ext = writer.getExtension ? writer.getExtension() : '';
if (ext !== '' && !phantasus.Util.endsWith(fileName.toLowerCase(), '.' + ext)) {
fileName += '.' + ext;
}
writer.setNumberFormat(heatMap.getHeatMapElementComponent().getDrawValuesFormat());
var blobs = [];
var textArray = [];
var proxy = {
push: function (text) {
textArray.push(text);
if (textArray.length === 10000) {
var blob = new Blob([textArray.join('')], {type: 'text/plain;charset=charset=utf-8'});
textArray = [];
blobs.push(blob);
}
},
join: function () {
if (textArray.length > 0) {
var blob = new Blob([textArray.join('')], {type: 'text/plain;charset=charset=utf-8'});
blobs.push(blob);
textArray = [];
}
var blob = new Blob(blobs, {type: 'text/plain;charset=charset=utf-8'});
saveAs(blob, fileName, true);
}
};
writer.write(dataset, proxy);
}
};
phantasus.SaveImageTool = function () {
};
phantasus.SaveImageTool.prototype = {
toString: function () {
return 'Save Image';
},
init: function (project, form) {
form.find('file_name').prop('autofocus', true).focus();
},
gui: function () {
return [
{
name: 'file_name',
type: 'text',
required: true
}, {
name: 'format',
type: 'radio',
options: ['PDF', 'PNG', 'SVG'],
value: 'PNG',
required: true
}];
},
execute: function (options) {
var fileName = options.input.file_name;
if (fileName === '') {
fileName = 'image';
}
var format = options.input.format.toLowerCase();
if (!phantasus.Util.endsWith(fileName.toLowerCase(), '.' + format)) {
fileName += '.' + format;
}
var heatMap = options.heatMap;
heatMap.saveImage(fileName, format);
}
};
phantasus.SaveSessionTool = function () {
};
phantasus.SaveSessionTool.prototype = {
toString: function () {
return 'Save Session';
},
init: function (project, form) {
form.find('file_name').prop('autofocus', true).focus();
},
gui: function () {
return [
{
name: 'file_name',
type: 'text',
required: true
}];
},
execute: function (options) {
var fileName = options.input.file_name;
if (fileName === '') {
fileName = 'session.json';
}
if (!phantasus.Util.endsWith(fileName.toLowerCase(), '.json')) {
fileName += '.json';
}
var heatMap = options.heatMap;
// var options = {dataset: options.input.include_dataset};
var options = {dataset: true};
var json = heatMap.toJSON(options);
var nativeArrayToArray = Array.from || function (typedArray) {
var normalArray = Array.prototype.slice.call(typedArray);
normalArray.length === typedArray.length;
normalArray.constructor === Array;
};
var blob = new Blob([JSON.stringify(json, function (key, value) {
if (phantasus.Util.isArray(value)) {
return value instanceof Array ? value : nativeArrayToArray(value);
}
return value;
})], {type: 'application/json;charset=charset=utf-8'});
saveAs(blob, fileName, true);
}
};
phantasus.shinyGamTool = function () {
};
phantasus.shinyGamTool.prototype = {
toString: function () {
return "Submit to Shiny GAM";
},
gui: function () {
return [];
},
init: function (heatMap, form) {
form.appendContent('<p>Are you sure you want to submit to Shiny GAM analysis?');
var rows = phantasus.Dataset.toJSON(heatMap.getFullDataset()).rowMetadataModel.vectors;
var pValuePresent = _.size(_.where(rows, {'name': 'P.Value'})) > 0;
if (!pValuePresent) {
form.appendContent('<span class="phantasus-warning-color">Warning:</span>' +
'differential expression analysis (limma) is required to be run before submitting to Shiny GAM.');
}
form.appendContent('</p>');
form.appendContent('Result will open in a new window automatically. <br/>' +
'Your browser may be irresponsive for an amount of time');
},
execute: function (options) {
var dataset = options.project.getFullDataset();
var promise = $.Deferred();
dataset.getESSession().then(function (oldSession) {
ocpu.call('shinyGAMAnalysis/print', {
es: oldSession
}, function (context) {
window.open(JSON.parse(context.txt)[0], '_blank');
promise.resolve();
}, false, '::es').fail(function () {
console.error('Failed to submit to shiny GAM analysis');
promise.reject();
});
});
return promise;
}
};
phantasus.SimilarityMatrixTool = function () {
};
phantasus.SimilarityMatrixTool.Functions = [phantasus.Euclidean,
phantasus.Jaccard, phantasus.Cosine, phantasus.KendallsCorrelation, phantasus.Pearson, phantasus.Spearman];
phantasus.SimilarityMatrixTool.Functions.fromString = function (s) {
for (var i = 0; i < phantasus.SimilarityMatrixTool.Functions.length; i++) {
if (phantasus.SimilarityMatrixTool.Functions[i].toString() === s) {
return phantasus.SimilarityMatrixTool.Functions[i];
}
}
throw new Error(s + ' not found');
};
phantasus.SimilarityMatrixTool.execute = function (dataset, input) {
var isColumnMatrix = input.compute_matrix_for == 'Columns';
var f = phantasus.SimilarityMatrixTool.Functions.fromString(input.metric);
return phantasus.HCluster.computeDistanceMatrix(
isColumnMatrix ? new phantasus.TransposedDatasetView(dataset)
: dataset, f);
};
phantasus.SimilarityMatrixTool.prototype = {
toString: function () {
return 'Similarity Matrix';
},
init: function (project, form) {
},
gui: function () {
return [{
name: 'metric',
options: phantasus.SimilarityMatrixTool.Functions,
value: phantasus.SimilarityMatrixTool.Functions[4].toString(),
type: 'select'
}, {
name: 'compute_matrix_for',
options: ['Columns', 'Rows'],
value: 'Columns',
type: 'radio'
}];
},
execute: function (options) {
var project = options.project;
var heatMap = options.heatMap;
var isColumnMatrix = options.input.compute_matrix_for == 'Columns';
var f = phantasus.SimilarityMatrixTool.Functions
.fromString(options.input.metric);
var dataset = project.getSortedFilteredDataset();
var blob = new Blob(
['self.onmessage = function(e) {'
+ 'importScripts(e.data.scripts);'
+ 'self.postMessage(phantasus.SimilarityMatrixTool.execute(phantasus.Dataset.fromJSON(e.data.dataset), e.data.input));'
+ '}']);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.postMessage({
scripts: phantasus.Util.getScriptPath(),
dataset: phantasus.Dataset.toJSON(dataset, {
columnFields: [],
rowFields: [],
seriesIndices: [0]
}),
input: options.input
});
worker.onmessage = function (e) {
var name = heatMap.getName();
var matrix = e.data;
var n = isColumnMatrix ? dataset.getColumnCount() : dataset
.getRowCount();
var d = new phantasus.Dataset({
name: name,
rows: n,
columns: n
});
// set the diagonal
var isDistance = f.toString() === phantasus.Euclidean.toString()
|| f.toString() === phantasus.Jaccard.toString();
for (var i = 1; i < n; i++) {
for (var j = 0; j < i; j++) {
var value = matrix[i][j];
d.setValue(i, j, value);
d.setValue(j, i, value);
}
}
// no need to set diagonal if not distance as array already
// initialized to 0
if (!isDistance) {
for (var i = 0; i < n; i++) {
d.setValue(i, i, 1);
}
}
var metadata = isColumnMatrix ? dataset.getColumnMetadata()
: dataset.getRowMetadata();
d.rowMetadataModel = phantasus.MetadataUtil.shallowCopy(metadata);
d.columnMetadataModel = phantasus.MetadataUtil.shallowCopy(metadata);
var colorScheme;
if (!isDistance) {
colorScheme = {
type: 'fixed',
map: [{
value: -1,
color: 'blue'
}, {
value: 0,
color: 'white'
}, {
value: 1,
color: 'red'
}]
};
} else {
colorScheme = {
type: 'fixed',
map: [{
value: 0,
color: 'white'
}, {
value: phantasus.DatasetUtil.max(d),
color: 'red'
}]
};
}
new phantasus.HeatMap({
colorScheme: colorScheme,
name: name,
dataset: d,
parent: heatMap,
inheritFromParentOptions: {
rows: !isColumnMatrix,
columns: isColumnMatrix
}
});
worker.terminate();
window.URL.revokeObjectURL(url);
};
return worker;
}
};
phantasus.TransposeTool = function () {
};
phantasus.TransposeTool.prototype = {
toString: function () {
return 'Transpose';
},
execute: function (options) {
var project = options.project;
var heatMap = options.heatMap;
var dataset = new phantasus.TransposedDatasetView(project
.getSortedFilteredDataset());
// make a shallow copy of the dataset, metadata is immutable via the UI
var rowMetadataModel = phantasus.MetadataUtil.shallowCopy(dataset
.getRowMetadata());
var columnMetadataModel = phantasus.MetadataUtil.shallowCopy(dataset
.getColumnMetadata());
dataset.getRowMetadata = function () {
return rowMetadataModel;
};
dataset.getColumnMetadata = function () {
return columnMetadataModel;
};
// TODO see if we can subset dendrograms
// only handle contiguous selections for now
// if (heatMap.columnDendrogram != null) {
// var indices = project.getColumnSelectionModel().getViewIndices()
// .toArray();
// phantasus.DendrogramUtil.leastCommonAncestor();
// }
// if (heatMap.rowDendrogram != null) {
//
// }
var name = options.input.name || heatMap.getName();
new phantasus.HeatMap({
name: name,
dataset: dataset,
inheritFromParentOptions: {
transpose: true
},
parent: heatMap
});
}
};
phantasus.TsneTool = function () {
};
phantasus.TsneTool.execute = function (dataset, input) {
// note: in worker here
var matrix = [];
var rows = input.project == 'Rows';
if (!rows) {
dataset = new phantasus.TransposedDatasetView(dataset);
}
var N = dataset.getRowCount();
var f = phantasus.HClusterTool.Functions.fromString(input.metric);
if (f === phantasus.TsneTool.PRECOMPUTED_DIST) {
for (var i = 0; i < N; i++) {
matrix.push([]);
for (var j = i + 1; j < N; j++) {
matrix[i][j] = dataset.getValue(i, j);
}
}
} else if (f === phantasus.TsneTool.PRECOMPUTED_SIM) {
var max = phantasus.DatasetUtil.max(dataset);
for (var i = 0; i < N; i++) {
matrix.push([]);
for (var j = i + 1; j < N; j++) {
matrix[i][j] = max - dataset.getValue(i, j);
}
}
} else {
var list1 = new phantasus.DatasetRowView(dataset);
var list2 = new phantasus.DatasetRowView(dataset);
for (var i = 0; i < N; i++) {
matrix.push([]);
list1.setIndex(i);
for (var j = i + 1; j < N; j++) {
var d = f(list1, list2.setIndex(j));
matrix[i][j] = d;
}
}
}
var opt = {};
opt.epsilon = input.epsilon;
opt.perplexity = input.perplexity;
opt.dim = 2;
var tsne = new tsnejs.tSNE(opt);
tsne.initDataDist(matrix);
for (var k = 0; k < 1000; k++) {
tsne.step();
}
var Y = tsne.getSolution();
return {solution: Y};
}
;
phantasus.TsneTool.prototype = {
toString: function () {
return 't-SNE';
},
init: function (project, form) {
},
gui: function () {
return [{
name: 'metric',
options: phantasus.HClusterTool.Functions,
value: phantasus.HClusterTool.Functions[3].toString(),
type: 'select'
}, {
name: 'project',
options: ['Columns', 'Rows'],
value: 'Columns',
type: 'select'
}, {
name: 'epsilon',
value: '10',
type: 'text',
help: 'learning rate'
}, {
name: 'perplexity',
value: '30',
type: 'text',
help: 'number of effective nearest neighbors'
}];
},
execute: function (options) {
var project = options.project;
var heatMap = options.heatMap;
var rows = options.input.project == 'Rows';
var dataset = project.getSortedFilteredDataset();
options.input.epsilon = parseInt(options.input.epsilon);
options.input.perplexity = parseInt(options.input.perplexity);
var blob = new Blob(
['self.onmessage = function(e) {'
+ 'e.data.scripts.forEach(function (s) { importScripts(s); });'
+ 'self.postMessage(phantasus.TsneTool.execute(phantasus.Dataset.fromJSON(e.data.dataset), e.data.input));'
+ '}']);
var url = URL.createObjectURL(blob);
var worker = new Worker(url);
worker.postMessage({
scripts: [phantasus.Util.getScriptPath()],
dataset: phantasus.Dataset.toJSON(dataset, {
columnFields: [],
rowFields: [],
seriesIndices: [0]
}),
input: options.input
});
worker.onmessage = function (e) {
if (rows) {
dataset = new phantasus.TransposedDatasetView(dataset);
}
var result = e.data.solution;
var newDataset = new phantasus.Dataset({
name: 't-SNE',
rows: dataset.getColumnCount(),
columns: 2
});
for (var i = 0; i < result.length; i++) {
newDataset.setValue(i, 0, result[i][0]);
newDataset.setValue(i, 1, result[i][1]);
}
var idVector = newDataset.getColumnMetadata().add('id');
idVector.setValue(0, 'P1');
idVector.setValue(1, 'P2');
newDataset.setRowMetadata(phantasus.MetadataUtil.shallowCopy(dataset.getColumnMetadata()));
var min = phantasus.DatasetUtil.min(newDataset);
var max = phantasus.DatasetUtil.max(newDataset);
new phantasus.HeatMap({
inheritFromParentOptions: {transpose: !rows},
name: 't-SNE',
dataset: newDataset,
parent: heatMap,
columns: [{
field: 'id',
display: 'text'
}],
colorScheme: {
type: 'fixed',
map: [{
value: min,
color: colorbrewer.Greens[3][0]
}, {
value: max,
color: colorbrewer.Greens[3][2]
}]
}
});
worker.terminate();
window.URL.revokeObjectURL(url);
};
return worker;
}
};
phantasus.aboutDataset = function (options) {
var _this = this;
this.project = options.project;
var dataset = this.project.getFullDataset();
var deepMapper = function (value, index) {
if (!value.values) {
return _.map(value, deepMapper).join('');
}
return '<tr><td>' + index.toString() + '</td><td>' + value.values.toString() + '</td></tr>';
};
var experimentData = _.map(dataset.getExperimentData(), deepMapper).join('');
var $dialog = $('<div style="background:white;" title="' + phantasus.aboutDataset.prototype.toString() + '"></div>');
this.$el = $([
'<div class="container-fluid">',
'<div class="row" style="height: 100%">',
'<div data-name="experiment-data" class="col-xs-12">',
'<label for="experiment-data-table">Experiment data</label>',
'<table id="experiment-data-table" class="table table-hover table-striped table-condensed">',
'<tr><th>Name</th><th>Value</th></tr>',
experimentData,
'</table>',
'</div>',
'</div></div>'].join(''));
this.$el.appendTo($dialog);
$dialog.dialog({
dialogClass: 'phantasus',
close: function (event, ui) {
event.stopPropagation();
$(this).dialog('destroy');
},
resizable: true,
height: 580,
width: 900
});
this.$dialog = $dialog;
};
phantasus.aboutDataset.prototype = {
toString: function () {
return 'About dataset';
}
};
phantasus.AbstractCanvas = function (offscreen) {
this.canvas = phantasus.CanvasUtil.createCanvas();
this.lastClip = null;
if (offscreen) {
this.offscreenCanvas = phantasus.CanvasUtil.createCanvas();
}
this.offset = {
x: 0,
y: 0
};
};
phantasus.AbstractCanvas.prototype = {
visible: true,
invalid: true,
scrollX: 0,
scrollY: 0,
prefWidth: undefined,
prefHeight: undefined,
getCanvas: function () {
return this.canvas;
},
scrollTop: function (pos) {
if (pos === undefined) {
return this.offset.y;
}
this.offset.y = pos;
},
appendTo: function ($el) {
// if (this.offscreenCanvas) {
// $(this.offscreenCanvas).appendTo($el);
// }
$(this.canvas).appendTo($el);
},
scrollLeft: function (pos) {
if (pos === undefined) {
return this.offset.x;
}
this.offset.x = pos;
},
dispose: function () {
$(this.canvas).remove();
this.offscreenCanvas = undefined;
},
getPrefWidth: function () {
return this.prefWidth;
},
/**
* Tells this canvas to invalidate any offscreen cached images
*/
setInvalid: function (invalid) {
this.invalid = invalid;
},
setBounds: function (bounds) {
var backingScale = phantasus.CanvasUtil.BACKING_SCALE;
var canvases = [this.canvas];
if (this.offscreenCanvas) {
canvases.push(this.offscreenCanvas);
}
if (bounds.height != null) {
_.each(canvases, function (canvas) {
canvas.height = bounds.height * backingScale;
canvas.style.height = bounds.height + 'px';
});
}
if (bounds.width != null) {
_.each(canvases, function (canvas) {
canvas.width = bounds.width * backingScale;
canvas.style.width = bounds.width + 'px';
});
}
if (bounds.left != null) {
_.each(canvases, function (canvas) {
canvas.style.left = bounds.left + 'px';
});
}
if (bounds.top != null) {
_.each(canvases, function (canvas) {
canvas.style.top = bounds.top + 'px';
});
}
},
/**
* Paint this canvas using the specified clip.
*/
paint: function (clip) {
var canvas = this.canvas;
var context = canvas.getContext('2d');
phantasus.CanvasUtil.resetTransform(context);
var width = this.getUnscaledWidth();
var height = this.getUnscaledHeight();
context.clearRect(0, 0, width, height);
if (this.prePaint) {
phantasus.CanvasUtil.resetTransform(context);
context.translate(this.offset.x, this.offset.y);
this.prePaint(clip, context);
}
phantasus.CanvasUtil.resetTransform(context);
if (this.offscreenCanvas) {
if (this.invalid) {
var oc = this.offscreenCanvas.getContext('2d');
phantasus.CanvasUtil.resetTransform(oc);
context.translate(this.offset.x, this.offset.y);
oc.clearRect(0, 0, width, height);
this.draw(clip, oc);
}
if (width > 0 && height > 0) {
context.drawImage(this.offscreenCanvas, 0, 0, width, height);
}
} else {
this.draw(clip, context);
}
if (this.postPaint) {
phantasus.CanvasUtil.resetTransform(context);
context.translate(this.offset.x, this.offset.y);
this.postPaint(clip, context);
}
this.lastClip = clip;
this.invalid = false;
},
repaint: function () {
if (!this.lastClip) {
this.lastClip = {
x: 0,
y: 0,
width: this.getUnscaledWidth(),
height: this.getUnscaledHeight()
};
}
this.paint(this.lastClip);
},
/**
* Draw this canvas into the specified context.
*/
draw: function (clip, context) {
console.log('Not implemented');
},
getPrefHeight: function () {
return this.prefHeight;
},
setPrefWidth: function (prefWidth) {
this.prefWidth = prefWidth;
},
setPrefHeight: function (prefHeight) {
this.prefHeight = prefHeight;
},
isVisible: function () {
return this.visible;
},
setVisible: function (visible) {
if (this.visible !== visible) {
this.visible = visible;
this.canvas.style.display = visible ? '' : 'none';
}
},
getUnscaledWidth: function () {
return this.canvas.width / phantasus.CanvasUtil.BACKING_SCALE;
},
getUnscaledHeight: function () {
return this.canvas.height / phantasus.CanvasUtil.BACKING_SCALE;
},
getWidth: function () {
return this.canvas.width;
},
getHeight: function () {
return this.canvas.height;
}
};
phantasus.AbstractColorSupplier = function () {
this.fractions = [0, 0.5, 1];
this.colors = ['#0000ff', '#ffffff', '#ff0000'];
this.names = null; // optional color stop names
this.min = 0;
this.max = 1;
this.missingColor = '#c0c0c0';
this.scalingMode = phantasus.HeatMapColorScheme.ScalingMode.RELATIVE;
this.stepped = false;
this.sizer = new phantasus.HeatMapSizer();
this.conditions = new phantasus.HeatMapConditions();
this.transformValues = 0;// z-score, robust z-score
};
phantasus.AbstractColorSupplier.Z_SCORE = 1;
phantasus.AbstractColorSupplier.ROBUST_Z_SCORE = 2;
phantasus.AbstractColorSupplier.toJSON = function (cs) {
var json = {
fractions: cs.fractions,
colors: cs.colors,
min: cs.min,
max: cs.max,
missingColor: cs.missingColor,
scalingMode: cs.scalingMode,
stepped: cs.stepped,
transformValues: cs.transformValues
};
if (cs.names) {
json.names = cs.names;
}
if (cs.conditions && cs.conditions.array.length > 0) {
json.conditions = cs.conditions.array;
}
if (cs.sizer && cs.sizer.seriesName != null) {
json.size = {
seriesName: cs.sizer.seriesName,
min: cs.sizer.min,
max: cs.sizer.max
};
}
return json;
};
phantasus.AbstractColorSupplier.fromJSON = function (json) {
var cs = json.stepped ? new phantasus.SteppedColorSupplier()
: new phantasus.GradientColorSupplier();
if (json.scalingMode == null && json.type != null) {
json.scalingMode = json.type; // old
}
if (json.scalingMode === 'relative' || json.scalingMode === 0) {
json.scalingMode = 0;
} else if (json.scalingMode === 'fixed' || json.scalingMode === 1) {
json.scalingMode = 1;
} else { // default to relative
json.scalingMode = 0;
}
cs.setScalingMode(json.scalingMode);
if (json.min != null) {
cs.setMin(json.min);
}
if (json.max != null) {
cs.setMax(json.max);
}
if (json.missingColor != null) {
cs.setMissingColor(json.missingColor);
}
if (phantasus.HeatMapColorScheme.ScalingMode.RELATIVE !== json.scalingMode) {
cs.setTransformValues(json.transformValues);
}
if (json.map) { // old
json.values = json.map.map(function (item) {
return item.value;
});
json.colors = json.map.map(function (item) {
return item.color;
});
}
var fractions = json.fractions;
if (json.values) { // map values to fractions
fractions = [];
var values = json.values;
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
for (var i = 0; i < values.length; i++) {
var value = values[i];
min = Math.min(min, value);
max = Math.max(max, value);
}
var valueToFraction = d3.scale.linear().domain(
[min, max]).range(
[0, 1]).clamp(true);
for (var i = 0; i < values.length; i++) {
fractions.push(valueToFraction(values[i]));
}
if (json.min == null) {
cs.setMin(min);
}
if (json.max == null) {
cs.setMax(max);
}
}
if (json.colors != null && json.colors.length > 0) {
cs.setFractions({
colors: json.colors,
fractions: fractions,
names: json.names
});
}
if (json.size) {
cs.getSizer().setSeriesName(json.size.seriesName);
cs.getSizer().setMin(json.size.min);
cs.getSizer().setMax(json.size.max);
}
if (json.conditions && _.isArray(json.conditions)) {
// load conditions
json.conditions.forEach(function (condition) {
var gtf = function () {
return true;
};
var ltf = function () {
return true;
};
if (condition.seriesName == null) {
condition.seriesName = condition.series; // series is deprecated
}
if (condition.v1 != null && !isNaN(condition.v1)) {
gtf = condition.v1Op === 'gt' ? function (val) {
return val > condition.v1;
} : function (val) {
return val >= condition.v1;
};
}
if (condition.v2 != null && !isNaN(condition.v2)) {
ltf = condition.v2Op === 'lt' ? function (val) {
return val < condition.v2;
} : function (val) {
return val <= condition.v2;
};
}
condition.accept = function (val) {
return gtf(val) && ltf(val);
};
});
cs.conditions.array = json.conditions;
}
return cs;
};
phantasus.AbstractColorSupplier.prototype = {
getTransformValues: function () {
return this.transformValues;
},
setTransformValues: function (transformValues) {
this.transformValues = transformValues;
},
getSizer: function () {
return this.sizer;
},
getConditions: function () {
return this.conditions;
},
createInstance: function () {
throw 'not implemented';
},
copy: function () {
var c = this.createInstance();
c.stepped = this.stepped;
c.setFractions({
fractions: this.fractions.slice(0),
colors: this.colors.slice(0)
});
if (this.names != null) {
c.names = this.names.slice(0);
}
if (this.sizer) {
c.sizer = this.sizer.copy();
}
if (this.conditions) {
c.conditions = this.conditions.copy();
}
c.scalingMode = this.scalingMode;
c.min = this.min;
c.max = this.max;
c.missingColor = this.missingColor;
if (this.scalingMode !== phantasus.HeatMapColorScheme.ScalingMode.RELATIVE) {
c.transformValues = this.transformValues;
}
return c;
},
setMissingColor: function (missingColor) {
this.missingColor = missingColor;
},
getMissingColor: function () {
return this.missingColor;
},
getScalingMode: function () {
return this.scalingMode;
},
setScalingMode: function (scalingMode) {
if (scalingMode !== this.scalingMode) {
if (scalingMode === phantasus.HeatMapColorScheme.ScalingMode.RELATIVE) {
this.min = 0;
this.max = 1;
}
this.scalingMode = scalingMode;
}
},
isStepped: function () {
return false;
},
getColor: function (row, column, value) {
throw 'not implemented';
},
getColors: function () {
return this.colors;
},
getNames: function () {
return this.names;
},
getFractions: function () {
return this.fractions;
},
getMin: function () {
return this.min;
},
getMax: function () {
return this.max;
},
setMin: function (min) {
this.min = min;
},
setMax: function (max) {
// the min and max are set by heat map color scheme for each row
this.max = max;
},
/**
*
* @param options.fractions
* Array of stop fractions
* @param options.colors
* Array of stop colors
* @param options.names
* Array of stop names
*/
setFractions: function (options) {
var index = phantasus.Util.indexSort(options.fractions, true);
this.fractions = phantasus.Util.reorderArray(options.fractions, index);
this.colors = phantasus.Util.reorderArray(options.colors, index);
this.names = options.names ? phantasus.Util.reorderArray(options.names,
index) : null;
}
};
phantasus.AbstractComponent = function () {
this.lastClip = null;
var c = document.createElement('div');
c.setAttribute('tabindex', '0');
c.style.outline = 0;
c.style.overflow = 'hidden';
c.style.position = 'absolute';
this.el = c;
this.$el = $(c);
};
phantasus.AbstractComponent.prototype = {
visible: true,
invalid: true,
prefWidth: undefined,
prefHeight: undefined,
appendTo: function ($el) {
$(this.el).appendTo($el);
},
dispose: function () {
$(this.el).remove();
},
getPrefWidth: function () {
return this.prefWidth;
},
/**
* Tells this component to invalidate
*/
setInvalid: function (invalid) {
this.invalid = invalid;
},
setBounds: function (bounds) {
// if (bounds.height != null) {
// this.el.style.height = bounds.height + 'px';
// }
// if (bounds.width != null) {
// this.el.style.width = bounds.width + 'px';
// }
if (bounds.left != null) {
this.$el.css('left', bounds.left + 'px');
}
if (bounds.top != null) {
this.$el.css('top', bounds.top + 'px');
}
},
/**
* Paint this canvas using the specified clip.
*/
paint: function (clip) {
var width = this.getUnscaledWidth();
var height = this.getUnscaledHeight();
this.draw(clip);
this.lastClip = clip;
this.invalid = false;
},
repaint: function () {
if (!this.lastClip) {
this.lastClip = {
x: 0,
y: 0,
width: this.getUnscaledWidth(),
height: this.getUnscaledHeight()
};
}
this.paint(this.lastClip);
},
/**
* Draw this canvas into the specified context.
*/
draw: function (clip) {
},
getPrefHeight: function () {
return this.prefHeight;
},
setPrefWidth: function (prefWidth) {
this.prefWidth = prefWidth;
},
setPrefHeight: function (prefHeight) {
this.prefHeight = prefHeight;
},
isVisible: function () {
return this.visible;
},
setVisible: function (visible) {
if (this.visible !== visible) {
this.visible = visible;
this.el.style.display = visible ? '' : 'none';
}
},
getUnscaledWidth: function () {
return this.$el.width();
},
getUnscaledHeight: function () {
return this.$el.height();
},
getWidth: function () {
return this.$el.width();
},
getHeight: function () {
return this.$el.height();
}
};
/*
*
* @param tree An object with maxHeight, rootNode, leafNodes, nLeafNodes. Each node has an id
* (integer), name (string), children, depth, height, minIndex, maxIndex, parent. Leaf nodes also
* have an index.
The root has the largest height, leaves the smallest height.
*/
phantasus.AbstractDendrogram = function (heatMap, tree, positions, project,
type) {
phantasus.AbstractCanvas.call(this, true);
this._overviewHighlightColor = '#d8b365';
this._searchHighlightColor = '#e41a1c';
this._selectedNodeColor = type === phantasus.AbstractDendrogram.Type.COLUMN ? '#377eb8'
: '#984ea3';
this.tree = tree;
this.type = type;
this.squishEnabled = false;
this.heatMap = heatMap;
this.positions = positions;
this.project = project;
var $label = $('<span></span>');
$label.addClass('label label-info');
$label.css('position', 'absolute');
this.$label = $label;
var $squishedLabel = $('<span></span>');
$squishedLabel.addClass('label label-default');
$squishedLabel.css('position', 'absolute').css('top', 18);
this.$squishedLabel = $squishedLabel;
this.$label = $label;
this.cutHeight = this.tree.maxHeight;
this.drawLeafNodes = true;
this.lineWidth = 0.7;
this.selectedNodeIds = {};
this.selectedRootNodeIdToNode = {};
this.nodeIdToHighlightedPathsToRoot = {};
var _this = this;
this.defaultStroke = 'rgb(0,0,0)';
this.mouseMoveNodes = null;
var mouseMove = function (event) {
if (!phantasus.CanvasUtil.dragging) {
var position = phantasus.CanvasUtil.getMousePosWithScroll(
event.target, event, _this.lastClip.x, _this.lastClip.y);
if (_this.isDragHotSpot(position)) { // dendrogram cutter
_this.canvas.style.cursor = _this.getResizeCursor();
} else {
var nodes;
if (_this.getNodes) {
nodes = _this.getNodes(position);
} else {
var node = _this.getNode(position);
if (node) {
nodes = [node];
}
}
_this.mouseMoveNodes = nodes;
if (nodes != null) {
nodes.sort(function (a, b) {
return a.name < b.name;
});
var tipOptions = {
event: event
};
tipOptions[type === phantasus.AbstractDendrogram.Type.COLUMN ? 'columnNodes'
: 'rowNodes'] = nodes;
_this.heatMap.setToolTip(-1, -1, tipOptions);
_this.canvas.style.cursor = 'pointer';
} else {
_this.heatMap.setToolTip(-1, -1);
_this.canvas.style.cursor = 'default';
}
}
}
};
var mouseExit = function (e) {
if (!phantasus.CanvasUtil.dragging) {
_this.mouseMoveNodes = null;
_this.canvas.style.cursor = 'default';
}
};
if (type !== phantasus.AbstractDendrogram.Type.RADIAL) {
$(this.canvas)
.on(
'contextmenu',
function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
var position = phantasus.CanvasUtil.getMousePosWithScroll(e.target,
e, _this.lastClip.x,
_this.lastClip.y);
var selectedNode = _this.getNode(position);
phantasus.Popup.showPopup(
[
{
name: 'Flip',
disabled: selectedNode == null
}, {
name: 'Branch Color',
disabled: selectedNode == null
}, {
separator: true
},
{
name: 'Annotate...'
}, {
name: 'Save'
}, {
separator: true
}, {
name: 'Enrichment...'
}, {
separator: true
}, {
name: 'Squish Singleton Clusters',
checked: _this.squishEnabled
}, {
separator: true
}, {
name: 'Delete'
}],
{
x: e.pageX,
y: e.pageY
},
e.target,
function (menuItem, item) {
if (item === 'Save') {
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'file_name',
type: 'text',
required: true,
});
formBuilder.append({
name: 'leaf_node_id_field',
type: 'bootstrap-select',
required: true,
options: phantasus.MetadataUtil.getMetadataNames(
type === phantasus.AbstractDendrogram.Type.COLUMN
? project.getFullDataset().getColumnMetadata()
: project.getFullDataset().getRowMetadata())
});
phantasus.FormBuilder.showOkCancel({
title: 'Save Dendrogram',
content: formBuilder.$form,
focus: document.activeElement,
okCallback: function () {
var fileName = formBuilder.getValue('file_name');
if (fileName === '') {
fileName = 'dendrogram.txt';
}
var leafNodeIdField = formBuilder.getValue('leaf_node_id_field');
var out = [];
var vector = type === phantasus.AbstractDendrogram.Type.COLUMN
? project.getFullDataset().getColumnMetadata().getByName(leafNodeIdField)
: project.getFullDataset().getRowMetadata().getByName(leafNodeIdField);
var leafNodeToString = function (n) {
return vector.getValue(n.index);
};
phantasus.DendrogramUtil.writeNewick(tree.rootNode, out, leafNodeToString);
var blob = new Blob([out.join('')], {type: 'text/plain;charset=charset=utf-8'});
saveAs(blob, fileName, true);
}
});
} else if (item === 'Flip') {
if (selectedNode != null) {
var isColumns = phantasus.AbstractDendrogram.Type.COLUMN === _this.type;
var min = selectedNode.minIndex;
var max = selectedNode.maxIndex;
// phantasus.DendrogramUtil.dfs(selectedNode, function (n) {
// if (n.children) {
// n.children.reverse();
// }
// return true;
// });
var leafNodes = tree.leafNodes;
for (var i = min, index = max; i <= max; i++, index--) {
var n = leafNodes[i];
n.index = index;
n.maxIndex = index;
n.minIndex = index;
}
leafNodes.sort(function (a, b) {
return (a.index < b.index ? -1 : 1);
});
var setIndex = function (n) {
if (n.children != null && n.children.length > 0) {
for (var i = 0; i < n.children.length; i++) {
setIndex(n.children[i]);
}
var sum = 0;
for (var i = 0; i < n.children.length; i++) {
sum += n.children[i].index;
}
n.index = sum / n.children.length;
var maxIndex = -Number.MAX_VALUE;
var minIndex = Number.MAX_VALUE;
for (var i = 0; i < n.children.length; i++) {
maxIndex = Math.max(maxIndex, n.children[i].maxIndex);
minIndex = Math.min(minIndex, n.children[i].minIndex);
}
n.minIndex = minIndex;
n.maxIndex = maxIndex;
}
};
setIndex(selectedNode);
var currentOrder = [];
var count = isColumns ? heatMap.getProject().getSortedFilteredDataset().getColumnCount() : heatMap.getProject()
.getSortedFilteredDataset()
.getRowCount();
for (var i = 0; i < count; i++) {
currentOrder.push(isColumns ? project.convertViewColumnIndexToModel(i) : project.convertViewRowIndexToModel(i));
}
for (var i = min, j = max; i < j; i++, j--) {
var tmp = currentOrder[j];
currentOrder[j] = currentOrder[i];
currentOrder[i] = tmp;
}
var key = new phantasus.SpecifiedModelSortOrder(currentOrder, currentOrder.length, 'dendrogram', isColumns);
key.setPreservesDendrogram(true);
key.setLockOrder(2);
key.setUnlockable(false);
if (isColumns) {
heatMap.getProject().setColumnSortKeys([key], true);
} else {
heatMap.getProject().setRowSortKeys([key], true);
}
heatMap.revalidate();
}
} else if (item === 'Branch Color') {
if (selectedNode != null) {
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'color',
type: 'color',
value: selectedNode.color,
required: true,
style: 'max-width:50px;'
});
formBuilder.find('color').on(
'change',
function () {
var color = $(this).val();
phantasus.DendrogramUtil.dfs(selectedNode, function (n) {
n.color = color;
return true;
});
_this.setSelectedNode(null);
});
phantasus.FormBuilder.showInModal({
title: 'Color',
close: 'Close',
html: formBuilder.$form,
focus: document.activeElement
});
}
} else if (item === 'Annotate...') {
phantasus.HeatMap.showTool(
new phantasus.AnnotateDendrogramTool(
type === phantasus.AbstractDendrogram.Type.COLUMN),
_this.heatMap);
} else if (item === 'Enrichment...') {
phantasus.HeatMap.showTool(
new phantasus.DendrogramEnrichmentTool(
type === phantasus.AbstractDendrogram.Type.COLUMN),
_this.heatMap);
} else if (item === 'Squish Singleton Clusters') {
_this.squishEnabled = !_this.squishEnabled;
if (!_this.squishEnabled) {
_this.positions.setSquishedIndices(null);
}
} else if (item === 'Delete') {
_this.resetCutHeight();
_this.heatMap.setDendrogram(
null,
type === phantasus.AbstractDendrogram.Type.COLUMN);
}
});
return false;
});
$(this.canvas).on('mousemove', _.throttle(mouseMove, 100)).on(
'mouseout', _.throttle(mouseExit, 100)).on('mouseenter',
_.throttle(mouseMove, 100));
}
var dragStartScaledCutHeight = 0;
this.cutTreeHotSpot = false;
if (type !== phantasus.AbstractDendrogram.Type.RADIAL) {
this.hammer = phantasus.Util.hammer(this.canvas, ['pan', 'tap']).on(
'tap',
this.tap = function (event) {
if (!phantasus.CanvasUtil.dragging) {
var position = phantasus.CanvasUtil.getMousePosWithScroll(event.target,
event, _this.lastClip.x,
_this.lastClip.y);
_this.cutTreeHotSpot = _this.isDragHotSpot(position);
if (_this.cutTreeHotSpot) {
return;
}
var node = _this.getNode(position);
if (node != null && node.parent === undefined) {
node = null; // can't select root
}
var commandKey = phantasus.Util.IS_MAC ? event.srcEvent.metaKey
: event.srcEvent.ctrlKey;
_this.setSelectedNode(node,
event.srcEvent.shiftKey || commandKey);
}
}).on('panend', this.panend = function (event) {
phantasus.CanvasUtil.dragging = false;
_this.canvas.style.cursor = 'default';
_this.cutTreeHotSpot = true;
}).on(
'panstart',
this.panstart = function (event) {
var position = phantasus.CanvasUtil.getMousePosWithScroll(event.target, event,
_this.lastClip.x, _this.lastClip.y,
true);
_this.cutTreeHotSpot = _this.isDragHotSpot(position);
if (_this.cutTreeHotSpot) { // make sure start event
// was on hotspot
phantasus.CanvasUtil.dragging = true;
_this.canvas.style.cursor = _this.getResizeCursor();
dragStartScaledCutHeight = _this.scale(_this.cutHeight);
}
}).on(
'panmove',
this.panmove = function (event) {
if (_this.cutTreeHotSpot) {
var cutHeight;
if (_this.type === phantasus.AbstractDendrogram.Type.COLUMN) {
var delta = event.deltaY;
cutHeight = Math.max(
0,
Math.min(
_this.tree.maxHeight,
_this.scale.invert(dragStartScaledCutHeight
+ delta)));
} else if (_this.type === phantasus.AbstractDendrogram.Type.ROW) {
var delta = event.deltaX;
cutHeight = Math.max(
0,
Math.min(
_this.tree.maxHeight,
_this.scale.invert(dragStartScaledCutHeight
+ delta)));
} else {
var point = phantasus.CanvasUtil.getMousePos(event.target, event);
point.x = _this.radius - point.x;
point.y = _this.radius - point.y;
var radius = Math.sqrt(point.x * point.x
+ point.y * point.y);
if (radius <= 4) {
cutHeight = _this.tree.maxHeight;
} else {
cutHeight = Math.max(0, Math.min(
_this.tree.maxHeight,
_this.scale.invert(radius)));
}
}
if (cutHeight >= _this.tree.maxHeight) {
_this.resetCutHeight();
} else {
_this.setCutHeight(cutHeight);
}
event.preventDefault();
}
});
}
};
phantasus.AbstractDendrogram.Type = {
COLUMN: 0,
ROW: 1,
RADIAL: 2
};
phantasus.AbstractDendrogram.prototype = {
setSelectedNode: function (node, add) {
var _this = this;
var viewIndices;
var selectionModel = this.type === phantasus.AbstractDendrogram.Type.COLUMN ? this.project.getColumnSelectionModel()
: this.project.getRowSelectionModel();
if (node == null) {
// clear selection
_this.selectedNodeIds = {};
_this.selectedRootNodeIdToNode = {};
viewIndices = new phantasus.Set();
} else {
if (add) { // add to selection
viewIndices = selectionModel.getViewIndices();
} else {
viewIndices = new phantasus.Set();
_this.selectedNodeIds = {};
_this.selectedRootNodeIdToNode = {};
}
if (node != null) {
if (node.children === undefined) { // leaf node
var contains = _this.nodeIdToHighlightedPathsToRoot[node.id];
if (!add) {
_this.nodeIdToHighlightedPathsToRoot = {};
}
if (contains) {
delete _this.nodeIdToHighlightedPathsToRoot[node.id];
// toggle
} else {
_this.nodeIdToHighlightedPathsToRoot[node.id] = node;
}
} else {
_this.selectedRootNodeIdToNode[node.id] = node;
phantasus.DendrogramUtil.dfs(node, function (d) {
_this.selectedNodeIds[d.id] = true;
return true;
});
}
for (var i = node.minIndex; i <= node.maxIndex; i++) {
viewIndices.add(i);
}
}
}
_this.trigger('nodeSelectionChanged', _this.selectedRootNodeIdToNode);
selectionModel.setViewIndices(viewIndices, true);
_this.repaint();
},
getPathStroke: function (node) {
if (this.selectedNodeIds[node.id]) {
return this._selectedNodeColor;
}
if (node.color !== undefined) {
return node.color;
}
// if (node.search) {
// return this._searchHighlightColor;
// }
return this.defaultStroke;
},
/**
*
* @param node
* @return The color, if any, to draw a circle for a node in the dendrogram
*/
getNodeFill: function (node) {
if (this.selectedRootNodeIdToNode[node.id]) {
return this._selectedNodeColor;
}
if (node.search) {
return this._searchHighlightColor;
}
if (node.info !== undefined) {
return this._overviewHighlightColor;
}
},
resetCutHeight: function () {
this.positions.setSquishedIndices(null);
if (this.type === phantasus.AbstractDendrogram.Type.COLUMN) {
this.project.setGroupColumns([], true);
} else {
this.project.setGroupRows([], true);
}
this.$label.text('');
this.$squishedLabel.text('');
var dataset = this.project.getSortedFilteredDataset();
var clusterIdVector = this.type === phantasus.AbstractDendrogram.Type.COLUMN ? dataset
.getColumnMetadata().getByName('dendrogram_cut')
: dataset.getRowMetadata().getByName('dendrogram_cut');
if (clusterIdVector) {
for (var i = 0, size = clusterIdVector.size(); i < size; i++) {
clusterIdVector.setValue(i, NaN);
}
}
},
setCutHeight: function (height) {
this.cutHeight = height;
var squishedIndices = {};
var clusterNumber = 0;
var nsquished = 0;
var squishEnabled = this.squishEnabled;
var roots = phantasus.DendrogramUtil.cutAtHeight(this.tree.rootNode,
this.cutHeight);
var dataset = this.project.getSortedFilteredDataset();
var clusterIdVector = this.type === phantasus.AbstractDendrogram.Type.COLUMN ? dataset.getColumnMetadata().add('dendrogram_cut')
: dataset.getRowMetadata().add('dendrogram_cut');
for (var i = 0, nroots = roots.length; i < nroots; i++) {
var root = roots[i];
var minChild = phantasus.DendrogramUtil.getDeepestChild(root,
true);
var maxChild = phantasus.DendrogramUtil.getDeepestChild(root,
false);
var clusterId;
if (squishEnabled && minChild.index === maxChild.index) {
squishedIndices[minChild.index] = true;
clusterId = -2;
nsquished++;
} else {
clusterNumber++;
clusterId = clusterNumber;
}
for (var j = minChild.index; j <= maxChild.index; j++) {
clusterIdVector.setValue(j, clusterId);
}
}
this.$label.text((clusterNumber) + ' cluster'
+ phantasus.Util.s(clusterNumber));
if (nsquished > 0) {
this.$squishedLabel.text(nsquished + ' squished');
} else {
this.$squishedLabel.text('');
}
if (squishEnabled) {
this.positions.setSquishedIndices(squishedIndices);
}
if (this.heatMap.getTrackIndex(clusterIdVector.getName(),
this.type === phantasus.AbstractDendrogram.Type.COLUMN) === -1) {
var settings = {
discrete: true,
discreteAutoDetermined: true,
display: ['color']
};
this.heatMap.addTrack(clusterIdVector.getName(),
this.type === phantasus.AbstractDendrogram.Type.COLUMN,
settings);
}
if (this.type === phantasus.AbstractDendrogram.Type.COLUMN) {
this.project.setGroupColumns([new phantasus.SortKey(clusterIdVector.getName(), phantasus.SortKey.SortOrder.UNSORTED)], true);
} else {
this.project.setGroupRows([new phantasus.SortKey(clusterIdVector.getName(), phantasus.SortKey.SortOrder.UNSORTED)], true);
}
},
dispose: function () {
phantasus.AbstractCanvas.prototype.dispose.call(this);
this.$label.remove();
this.$squishedLabel.remove();
this.hammer.off('panend', this.panend).off('panstart',
this.panstart).off('panmove', this.panmove).off('tap', this.tap);
this.hammer.destroy();
this.$label = null;
this.$squishedLabel = null;
},
isCut: function () {
return this.cutHeight < this.tree.maxHeight;
},
getMinIndex: function () {
return 0;
},
getMaxIndex: function () {
return this.positions.getLength() - 1;
},
getNode: function (p) {
var _this = this;
if (this.lastNode) {
var xy = _this.toPix(this.lastNode);
if (Math.abs(xy[0] - p.x) < 4 && Math.abs(xy[1] - p.y) < 4) {
return this.lastNode;
}
}
this.lastNode = this._getNode(p);
return this.lastNode;
},
// getNode : function(p) {
// var x = p.x;
// var y = p.y;
// var leafIndex = this.positions.getIndex(x, true);
// if (leafIndex >= 0 && leafIndex < leafNodeIds.length) {
// leafid = leafNodeIds[leafIndex];
// } else {
// return null;
// }
// var n = leafNodes.get(leafid);
// if (n != null) {
// while (!n.isRoot()) {
// var parent = n.getParent();
// getNodePosition(parent, p);
// if (Math.abs(p.x - x) < 4 && Math.abs(p.y - y) < 4) {
// return parent;
// }
// n = parent;
// }
// }
// return null;
// },
_getNode: function (p) {
var _this = this;
// brute force search
var hit = null;
try {
phantasus.DendrogramUtil.dfs(this.tree.rootNode, function (node) {
var xy = _this.toPix(node);
if (Math.abs(xy[0] - p.x) < 4 && Math.abs(xy[1] - p.y) < 4) {
hit = node;
throw 'break';
}
return hit === null;
});
}
catch (x) {
// break of out dfs
}
return hit;
},
getResizeCursor: function () {
if (this.type === phantasus.AbstractDendrogram.Type.COLUMN) {
return 'ns-resize';
} else if (this.type === phantasus.AbstractDendrogram.Type.ROW) {
return 'ew-resize';
}
return 'nesw-resize';
},
isDragHotSpot: function (p) {
return false;
},
preDraw: function (context, clip) {
},
postDraw: function (context, clip) {
},
prePaint: function (clip, context) {
this.scale = this.createScale();
var min = this.getMinIndex(clip);
var max = this.getMaxIndex(clip);
if (min !== this.lastMinIndex || max !== this.lastMinIndex) {
this.lastMinIndex = min;
this.lastMaxIndex = max;
}
this.invalid = true;
},
draw: function (clip, context) {
context.translate(-clip.x, -clip.y);
context.strokeStyle = 'black';
context.fillStyle = 'black';
this.scale = this.createScale();
var min = this.lastMinIndex;
var max = this.lastMaxIndex;
context.lineWidth = this.lineWidth;
this.preDraw(context, clip);
context.strokeStyle = this.defaultStroke;
context.fillStyle = 'rgba(166,206,227,0.5)';
this.drawDFS(context, this.tree.rootNode, min, max, 0);
context.strokeStyle = 'black';
context.fillStyle = 'black';
this.postDraw(context, clip);
},
/**
* @abstract
*/
drawCutSlider: function () {
throw new Error();
},
postPaint: function (clip, context) {
context.strokeStyle = 'black';
this.paintMouseOver(clip, context);
this.drawCutSlider(clip, context);
// this.drawHighlightedPathsToRoot(context, this.lastMinIndex,
// this.lastMaxIndex);
},
// drawHighlightedPathsToRoot : function(context, minIndex, maxIndex) {
// context.lineWidth = 1;
// context.strokeStyle = 'black';
// context.textAlign = 'left';
// var i = 0;
// for ( var key in this.nodeIdToHighlightedPathsToRoot) {
// context.fillStyle = '#99d594';
// context.strokeStyle = context.fillStyle;
// var node = this.nodeIdToHighlightedPathsToRoot[key];
// if (node.collapsed) {
// for (var node = node.parent; node.collapsedChildren != null; node =
// node.parent) {
// node = node.parent;
// }
// }
// // var pix = this.toPix(node);
// // context.globalAlpha = 0.5;
// // context.beginPath();
// // context.arc(pix[0], pix[1], 8, Math.PI * 2, false);
// // context.fill();
// // context.globalAlpha = 1;
// for (var root = node; root.parent !== undefined; root = root.parent) {
// this
// .drawPathFromNodeToParent(context, root, minIndex,
// maxIndex);
// }
// i++;
// }
// },
getNodeRadius: function (node) {
// if (this._nodeRadiusScaleField != null) {
// var vals = node.info[this._nodeRadiusScaleField];
// if (vals === undefined) {
// return 4;
// }
// // TODO get max or min
// return this._nodeRadiusScale(vals[0]) * 8;
// }
return 4;
},
drawNode: function (context, node) {
},
drawDFS: function (context, node, minIndex, maxIndex) {
if (this.type !== phantasus.AbstractDendrogram.Type.RADIAL) {
if ((node.maxIndex < minIndex) || (node.minIndex > maxIndex)) {
return;
}
}
var nodeFill = this.getNodeFill(node);
if (nodeFill !== undefined) {
context.fillStyle = nodeFill;
this.drawNode(context, node);
}
context.strokeStyle = this.getPathStroke(node);
var children = node.children;
if (children !== undefined) {
this.drawNodePath(context, node, minIndex, maxIndex);
for (var i = 0, nchildren = children.length; i < nchildren; i++) {
this.drawDFS(context, children[i], minIndex, maxIndex);
}
}
}
};
phantasus.Util.extend(phantasus.AbstractDendrogram, phantasus.AbstractCanvas);
phantasus.Util.extend(phantasus.AbstractDendrogram, phantasus.Events);
/**
* Action object contains
* @param options.which Array of key codes
* @param options.shift Whether shift key is required
* @param options.commandKey Whether command key is required
* @param options.name Shortcut name
* @param options.cb Function callback
* @param options.accept Additional function to test whether to accept shortcut
* @param options.icon Optional icon to display
*/
phantasus.ActionManager = function () {
this.actionNameToAction = new phantasus.Map();
this.actions = [];
// TODO copy all row/column metadata
// pin/unpin tab,
// header stuff-display, delete.
this.add({
ellipsis: false,
name: 'Sort/Group',
cb: function (options) {
new phantasus.SortDialog(options.heatMap.getProject());
},
icon: 'fa fa-sort-alpha-asc'
});
var $filterModal = null;
this.add({
name: 'Filter',
ellipsis: false,
cb: function (options) {
if ($filterModal == null) {
var filterModal = [];
var filterLabelId = _.uniqueId('phantasus');
filterModal
.push('<div class="modal" tabindex="1" role="dialog" aria-labelledby="'
+ filterLabelId + '">');
filterModal.push('<div class="modal-dialog" role="document">');
filterModal.push('<div class="modal-content">');
filterModal.push('<div class="modal-header">');
filterModal
.push('<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>');
filterModal.push('<h4 class="modal-title" id="' + filterLabelId
+ '">Filter</h4>');
filterModal.push('</div>');
filterModal.push('<div class="modal-body"></div>');
filterModal.push('<div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">Close</button></div>');
filterModal.push('</div>');
filterModal.push('</div>');
filterModal.push('</div>');
$filterModal = $(filterModal.join(''));
$filterModal.on('mousewheel', function (e) {
e.stopPropagation();
});
var $filter = $('<div></div>');
$filter.appendTo($filterModal.find('.modal-body'));
var filterHtml = ['<ul class="nav nav-tabs" id="rowsOrColumns">',
' <li class="active"><a>Rows</a></li>',
' <li><a>Columns</a></li>',
'</ul>'];
// filterHtml
// .push('<div class="radio"><label><input type="radio" name="rowsOrColumns" value="rows" checked>Rows</label></div> ');
// filterHtml
// .push('<div class="radio"><label><input type="radio" name="rowsOrColumns" value="columns">Columns</label></div>');
var $filterChooser = $(filterHtml.join(''));
$filterChooser.appendTo($filter);
var columnFilterUI = new phantasus.FilterUI(options.heatMap.getProject(), true);
var rowFilterUI = new phantasus.FilterUI(options.heatMap.getProject(), false);
// options.heatMap.getProject().getRowFilter().on('focus', function (e) {
// $filterChooser.find('[value=rows]').prop('checked', true);
// columnFilterUI.$div.hide();
// rowFilterUI.$div.show();
// $filterModal.modal('show');
// phantasus.Util.trackEvent({
// eventCategory: '',
// eventAction: 'rowFilter'
// });
//
// });
// options.heatMap.getProject().getColumnFilter().on('focus', function (e) {
// $filterChooser.find('[value=columns]').prop('checked', true);
// columnFilterUI.$div.show();
// rowFilterUI.$div.hide();
// $filterModal.modal('show');
// phantasus.Util.trackEvent({
// eventCategory: '',
// eventAction: 'columnFilter'
// });
// });
rowFilterUI.$div.appendTo($filter);
columnFilterUI.$div.appendTo($filter);
columnFilterUI.$div.css('display', 'none');
var filterTabs = $filterChooser.find('li');
filterTabs.on('click', function (e) {
filterTabs.toggleClass('active', false);
var target = $(e.currentTarget);
var mode = target.text();
target.toggleClass('active', true);
if (mode === 'Columns') {
columnFilterUI.$div.show();
rowFilterUI.$div.hide();
} else {
columnFilterUI.$div.hide();
rowFilterUI.$div.show();
}
e.preventDefault();
});
$filterModal.appendTo(options.heatMap.$content);
$filterModal.on('hidden.bs.modal', function () {
options.heatMap.focus();
});
}
$filterModal.modal('show');
},
icon: 'fa fa-filter'
});
this.add({
name: 'Options',
ellipsis: false,
cb: function (options) {
options.heatMap.showOptions();
},
icon: 'fa fa-cog'
});
this.add({
which: [191], // slash
commandKey: true,
global: true,
name: 'Toggle Search',
cb: function (options) {
options.heatMap.getToolbar().toggleSearch();
}
});
//
this.add({
name: 'Close Tab',
cb: function (options) {
options.heatMap.getTabManager().remove(options.heatMap.tabId);
}
});
this.add({
name: 'Rename Tab',
ellipsis: false,
cb: function (options) {
options.heatMap.getTabManager().rename(options.heatMap.tabId);
}
});
this.add({
which: [88], // x
commandKey: true,
name: 'New Heat Map',
accept: function (options) {
return (!options.isInputField || window.getSelection().toString() === '');
},
cb: function (options) {
phantasus.HeatMap.showTool(new phantasus.NewHeatMapTool(),
options.heatMap);
}
});
this.add({
name: 'Submit to Shiny GAM',
cb: function (options) {
phantasus.HeatMap.showTool(new phantasus.shinyGamTool(), options.heatMap);
},
icon: 'fa fa-share-square-o'
});
if (phantasus.Util.getURLParameter('debug') !== null) {
this.add({
name: phantasus.ProbeDebugTool.prototype.toString(),
cb: function (options) {
phantasus.HeatMap.showTool(new phantasus.ProbeDebugTool(), options.heatMap)
}
});
this.add({
name: "DEBUG: Expose project",
cb: function (options) {
window.project = options.heatMap.project;
window.dataset = options.heatMap.project.getFullDataset();
window.heatmap = options.heatMap;
}
});
this.add({
name: phantasus.ReproduceTool.prototype.toString(),
cb: function (options) {
new phantasus.ReproduceTool(
options.heatMap.getProject()
);
}
});
}
this.add({
name: 'Submit to Enrichr',
cb: function (options) {
new phantasus.enrichrTool(
options.heatMap.getProject()
);
},
icon: 'fa'
});
this.add({
name: phantasus.fgseaTool.prototype.toString(),
cb: function (options) {
phantasus.initFGSEATool(options);
},
icon: 'fa'
});
this.add({
name: phantasus.gseaTool.prototype.toString(),
cb: function (options) {
new phantasus.gseaTool(
options.heatMap,
options.heatMap.getProject()
);
}
});
this.add({
which: [67], // C
commandKey: true,
name: 'Copy'
});
this.add({
which: [86], // V
commandKey: true,
name: 'Paste Dataset'
});
this.add({
global: true,
name: 'Open',
ellipsis: false,
cb: function (options) {
phantasus.HeatMap.showTool(new phantasus.OpenFileTool(), options.heatMap);
},
which: [79],
commandKey: true,
icon: 'fa fa-folder-open-o'
});
this.add({
name: 'Annotate',
children: [
'Annotate rows',
'Annotate columns'],
icon: 'fa fa-list'
});
this.add({
name: 'Annotate rows',
children: [
'From file', 'From database']
});
this.add({
name: 'Differential expression',
children: [
'Limma',
'DESeq2 (experimental)',
'Marker Selection'],
icon: 'fa fa-list'
});
this.add({
name: 'Clustering',
children: [
'K-means',
'Nearest Neighbors',
'Hierarchical Clustering'],
icon: 'fa'
});
this.add({
name: 'Plots',
children: [
'Chart',
'PCA Plot',
phantasus.gseaTool.prototype.toString()],
icon: 'fa fa-line-chart'
});
this.add({
name: 'Pathway analysis',
children: [
'Submit to Enrichr',
phantasus.fgseaTool.prototype.toString()],
icon: 'fa fa-table'
});
this.add({
name: 'Annotate columns',
cb: function (options) {
phantasus.HeatMap.showTool(new phantasus.AnnotateDatasetTool({target: 'Columns'}), options.heatMap);
}
});
this.add({
name: 'From file',
cb: function (options) {
phantasus.HeatMap.showTool(new phantasus.AnnotateDatasetTool({target: 'Rows'}), options.heatMap);
}
});
this.add({
name: 'From database',
cb: function (options) {
phantasus.initAnnotationConvertTool(options);
}
});
this.add({
ellipsis: false,
name: 'Save Image',
gui: function () {
return new phantasus.SaveImageTool();
},
cb: function (options) {
phantasus.HeatMap.showTool(this.gui(),
options.heatMap);
},
which: [83],
commandKey: true,
global: true,
icon: 'fa fa-file-image-o'
});
this.add({
ellipsis: false,
name: 'Save Dataset',
gui: function () {
return new phantasus.SaveDatasetTool();
},
cb: function (options) {
phantasus.HeatMap.showTool(this.gui(),
options.heatMap);
},
// shiftKey: true,
// which: [83],
// commandKey: true,
// global: true,
icon: 'fa fa-floppy-o'
});
this.add({
ellipsis: false,
name: 'Save Session',
gui: function () {
return new phantasus.SaveSessionTool();
},
cb: function (options) {
phantasus.HeatMap.showTool(this.gui(), options.heatMap);
},
icon: 'fa fa-anchor'
});
this.add({
ellipsis: true,
name: 'Get dataset link',
cb: function (options) {
var dataset = options.heatMap.getProject().getFullDataset();
dataset.getESSession().then(function (es) {
var key = es.getKey();
var location = window.location;
var datasetName = options.heatMap.getName();
var heatmapJson = options.heatMap.toJSON({dataset: false});
var publishReq = ocpu.call('publishSession/print', { sessionName: key, datasetName: datasetName, heatmapJson: heatmapJson }, function (tempSession) {
var parsedJSON = JSON.parse(tempSession.txt);
if (!parsedJSON.result === false) {
throw new Error('Failed to make session accessible');
}
var newLocation = location.origin + location.pathname + '?session=' + tempSession.key;
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'Link',
readonly: true,
value: newLocation
});
formBuilder.append({
name: 'copy',
type: 'button'
});
formBuilder.$form.find('button').on('click', function () {
formBuilder.$form.find('input')[0].select();
document.execCommand('copy');
});
formBuilder.appendContent('<h4>Please note that link will be valid for 30 days.</h4>');
phantasus.FormBuilder.showInModal({
title: 'Get dataset link',
close: 'Close',
html: formBuilder.$form,
focus: options.heatMap.getFocusEl()
});
});
publishReq.fail(function () {
throw new Error('Failed to make session accessible: ' + publishReq.responseText);
});
})
}
});
this.add({
name: 'About',
cb: function (options) {
var $div = $([
'<div>',
'Phantasus version: ' + PHANTASUS_VERSION + ', build: ' + PHANTASUS_BUILD + '<br/>',
'Changelog available at: <a href="https://raw.githubusercontent.com/ctlab/phantasus/master/NEWS" target="_blank">Github</a><br/>',
'Source Code available at: <a href="http://github.com/ctlab/phantasus" target="_blank">Github</a>',
'</div>'
].join('\n'));
phantasus.FormBuilder.showInModal({
title: 'About Phantasus',
close: 'Close',
html: $div,
focus: options.heatMap.getFocusEl()
});
}
});
this.add({
name: phantasus.aboutDataset.prototype.toString(),
cb: function (options) {
phantasus.aboutDataset({
project: options.heatMap.getProject()
})
},
});
if (typeof Plotly !== 'undefined') {
this.add({
name: 'Chart',
cb: function (options) {
new phantasus.ChartTool({
project: options.heatMap.getProject(),
heatmap: options.heatMap,
getVisibleTrackNames: _.bind(
options.heatMap.getVisibleTrackNames, options.heatMap)
});
},
icon: 'fa'
});
this.add({
name: 'PCA Plot',
cb: function (options) {
new phantasus.PcaPlotTool({
project: options.heatMap.getProject()
});
},
icon: 'fa'
});
}
this.add({
name: 'Zoom In',
cb: function (options) {
options.heatMap.zoom(true);
},
which: [107, 61, 187]
});
this.add({
name: 'Zoom Out',
cb: function (options) {
options.heatMap.zoom(false);
},
which: [173, 189, 109]
});
this.add({
name: 'Fit To Window',
cb: function (options) {
options.heatMap.fitToWindow({fitRows: true, fitColumns: true, repaint: true});
},
which: [48], // zero
commandKey: true,
icon: 'fa fa-compress'
});
this.add({
name: 'Fit Columns To Window',
cb: function (options) {
options.heatMap.fitToWindow({fitRows: false, fitColumns: true, repaint: true});
}
});
this.add({
name: 'Fit Rows To Window',
cb: function (options) {
options.heatMap.fitToWindow({fitRows: true, fitColumns: false, repaint: true});
}
});
this.add({
name: '100%',
cb: function (options) {
options.heatMap.resetZoom();
},
button: '100%'
});
this.add({
which: [35],
name: 'Go To End',
cb: function (options) {
options.heatMap.scrollLeft(options.heatMap.heatmap.getPreferredSize().width);
options.heatMap.scrollTop(options.heatMap.heatmap.getPreferredSize().height);
}
});
this.add({
which: [36], // home key
name: 'Go To Start',
cb: function (options) {
options.heatMap.scrollLeft(0);
options.heatMap.scrollTop(0);
}
});
this.add({
which: [34], // page down
commandKey: true,
name: 'Go To Bottom',
cb: function (options) {
options.heatMap
.scrollTop(options.heatMap.heatmap.getPreferredSize().height);
}
});
this.add({
which: [34], // page down
commandKey: false,
name: 'Scroll Page Down',
cb: function (options) {
var pos = options.heatMap.scrollTop();
options.heatMap.scrollTop(pos + options.heatMap.heatmap.getUnscaledHeight()
- 2);
}
});
this.add({
which: [33], // page up
commandKey: true,
name: 'Go To Top',
cb: function (options) {
options.heatMap
.scrollTop(0);
}
});
this.add({
which: [33], // page up
commandKey: false,
name: 'Scroll Page Up',
cb: function (options) {
var pos = options.heatMap.scrollTop();
options.heatMap.scrollTop(pos - options.heatMap.heatmap.getUnscaledHeight()
+ 2);
}
});
this.add({
which: [38], // up arrow
commandKey: true,
name: 'Zoom Out Rows',
cb: function (options) {
options.heatMap.zoom(false, {
columns: false,
rows: true
});
}
});
this.add({
which: [38], // up arrow
commandKey: false,
name: 'Scroll Up',
cb: function (options) {
options.heatMap.scrollTop(options.heatMap.scrollTop() - 8);
}
});
this.add({
which: [40], // down arrow
commandKey: true,
name: 'Zoom In Rows',
cb: function (options) {
options.heatMap.zoom(true, {
columns: false,
rows: true
});
}
});
this.add({
which: [40], // down arrow
commandKey: false,
name: 'Scroll Down',
cb: function (options) {
options.heatMap.scrollTop(options.heatMap.scrollTop() + 8);
}
});
this.add({
which: [37], // left arrow
commandKey: true,
name: 'Zoom Out Columns',
cb: function (options) {
options.heatMap.zoom(false, {
columns: true,
rows: false
});
}
});
this.add({
which: [37], // left arrow
commandKey: false,
name: 'Scroll Left',
cb: function (options) {
options.heatMap.scrollLeft(options.heatMap.scrollLeft() - 8);
}
});
this.add({
which: [39], // right arrow
commandKey: true,
name: 'Zoom In Columns',
cb: function (options) {
options.heatMap.zoom(true, {
columns: true,
rows: false
});
}
});
this.add({
which: [39], // right arrow
commandKey: false,
name: 'Scroll Right',
cb: function (options) {
options.heatMap.scrollLeft(options.heatMap.scrollLeft() + 8);
}
});
this.add({
name: 'Tutorial',
cb: function () {
window
.open('phantasus-tutorial.html');
}
});
this.add({
icon: 'fa fa-code',
name: 'Source Code',
cb: function () {
window.open('https://github.com/ctlab/phantasus');
}
});
var $findModal;
var $search;
this.add({
which: [65],
ellipsis: false,
shiftKey: true,
commandKey: true,
name: 'Search Menus',
cb: function (options) {
if ($findModal == null) {
var findModal = [];
var id = _.uniqueId('phantasus');
findModal
.push('<div class="modal" tabindex="1" role="dialog" aria-labelledby="'
+ id + '">');
findModal.push('<div class="modal-dialog" role="document">');
findModal.push('<div class="modal-content">');
findModal.push('<div class="modal-header">');
findModal
.push('<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>');
findModal.push('<h4 class="modal-title" id="' + id
+ '">Enter action</h4>');
findModal.push('</div>');
findModal.push('<div class="modal-body ui-front"><input class="form-control input-sm"></div>');
findModal.push('</div>');
findModal.push('</div>');
findModal.push('</div>');
$findModal = $(findModal.join(''));
$findModal.appendTo(options.heatMap.$content);
var allActions = options.heatMap.getActionManager().getActions();
$search = $findModal.find('input');
$search.on('keyup', function (e) {
if (e.which === 13) {
var text = $search.val().trim();
if (text !== '') {
var action = _this.getAction(text);
if (action) {
$findModal.modal('hide');
_this.execute(text, {event: e});
}
}
}
});
phantasus.Util.autosuggest({
$el: $search,
multi: false,
suggestWhenEmpty: false,
// history: options.history,
filter: function (tokens, response) {
var token = tokens[0].trim();
var matches = [];
var replaceRegex = new RegExp('(' + phantasus.Util.escapeRegex(token) + ')', 'i');
for (var i = 0; i < allActions.length; i++) {
if (allActions[i].cb) {
var name = allActions[i].name;
if (replaceRegex.test(name)) {
matches.push({
clear: true,
value: name,
label: '<span style="margin-left: 10px">'
+ name.replace(replaceRegex, '<b>$1</b>') + '</span>'
});
}
}
}
response(matches);
},
select: function () {
setTimeout(function () {
var text = $search.val().trim();
if (text !== '') {
var action = _this.getAction(text);
if (action) {
$findModal.modal('hide');
_this.execute(text);
}
}
}, 20);
}
});
$findModal.on('hidden.bs.modal', function () {
options.heatMap.focus();
});
}
$findModal.modal('show');
$search.focus();
}
});
this.add({
name: 'Keyboard Shortcuts',
cb: function (options) {
new phantasus.HeatMapKeyListener(options.heatMap).showKeyMapReference();
}
});
/*this.add({
name: 'Linking',
cb: function () {
window
.open('/linking.html');
}
});*/
this.add({
name: 'Contact',
icon: 'fa fa-envelope-o',
cb: function (options) {
phantasus.FormBuilder.showInModal({
title: 'Contact',
html: 'Please email us at alsergbox@gmail.com',
focus: options.heatMap.getFocusEl()
});
}
});
this.add({
which: [65], // a
commandKey: true,
name: 'Select All',
accept: function (options) {
var active = options.heatMap.getActiveComponent();
return (active === 'rowTrack' || active === 'columnTrack');
},
cb: function (options) {
var active = options.heatMap.getActiveComponent();
var selectionModel = active === 'rowTrack' ? options.heatMap.getProject()
.getRowSelectionModel() : options.heatMap.getProject()
.getColumnSelectionModel();
var count = active === 'rowTrack' ? options.heatMap.getProject()
.getSortedFilteredDataset().getRowCount() : options.heatMap
.getProject().getSortedFilteredDataset()
.getColumnCount();
var indices = new phantasus.Set();
for (var i = 0; i < count; i++) {
indices.add(i);
}
selectionModel.setViewIndices(indices, true);
}
});
var invertAction = function (options, isColumns) {
var model = isColumns ? options.heatMap.getProject().getColumnSelectionModel() : options.heatMap.getProject().getRowSelectionModel();
var viewIndices = model.getViewIndices();
var inverse = new phantasus.Set();
var n = n = isColumns ? options.heatMap.getProject().getSortedFilteredDataset().getColumnCount() : options.heatMap.getProject().getSortedFilteredDataset().getRowCount();
for (var i = 0; i < n; i++) {
if (!viewIndices.has(i)) {
inverse.add(i);
}
}
model.setViewIndices(inverse, true);
};
this.add({
name: 'Invert Selected Rows',
cb: function (options) {
invertAction(options, false);
}
});
this.add({
name: 'Invert Selected Columns',
cb: function (options) {
invertAction(options, true);
}
});
var clearAction = function (options, isColumns) {
var model = isColumns ? options.heatMap.getProject()
.getColumnSelectionModel() : options.heatMap.getProject()
.getRowSelectionModel();
model.setViewIndices(new phantasus.Set(), true);
};
this.add({
name: 'Clear Selected Rows',
cb: function (options) {
clearAction(options, false);
}
});
this.add({
name: 'Clear Selected Columns',
cb: function (options) {
clearAction(options, true);
}
});
var moveToTop = function (options, isColumns) {
var project = options.heatMap.getProject();
var selectionModel = !isColumns ? project.getRowSelectionModel()
: project
.getColumnSelectionModel();
var viewIndices = selectionModel.getViewIndices().values();
if (viewIndices.length === 0) {
return;
}
viewIndices.sort(function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
var converter = isColumns ? project.convertViewColumnIndexToModel
: project.convertViewRowIndexToModel;
converter = _.bind(converter, project);
var modelIndices = [];
for (var i = 0, n = viewIndices.length; i < n; i++) {
modelIndices.push(converter(viewIndices[i]));
}
var sortKey = new phantasus.MatchesOnTopSortKey(project, modelIndices, 'selection on top', isColumns);
sortKey.setLockOrder(1);
sortKey.setUnlockable(false);
if (isColumns) {
project
.setColumnSortKeys(
phantasus.SortKey
.keepExistingSortKeys(
[sortKey],
project
.getColumnSortKeys().filter(function (key) {
return !(key instanceof phantasus.MatchesOnTopSortKey && key.toString() === sortKey.toString());
})),
true);
} else {
project
.setRowSortKeys(
phantasus.SortKey
.keepExistingSortKeys(
[sortKey],
project
.getRowSortKeys().filter(function (key) {
return !(key instanceof phantasus.MatchesOnTopSortKey && key.toString() === sortKey.toString());
})),
true);
}
};
this.add({
name: 'Move Selected Rows To Top',
cb: function (options) {
moveToTop(options, false);
}
});
this.add({
name: 'Move Selected Columns To Top',
cb: function (options) {
moveToTop(options, true);
}
});
var selectAll = function (options, isColumns) {
var project = options.heatMap.getProject();
var selectionModel = !isColumns ? project.getRowSelectionModel()
: project
.getColumnSelectionModel();
var count = !isColumns ? project
.getSortedFilteredDataset()
.getRowCount() : project
.getSortedFilteredDataset()
.getColumnCount();
var indices = new phantasus.Set();
for (var i = 0; i < count; i++) {
indices.add(i);
}
selectionModel.setViewIndices(indices, true);
};
this.add({
name: 'Select All Rows',
cb: function (options) {
selectAll(options, false);
}
});
this.add({
name: 'Select All Columns',
cb: function (options) {
selectAll(options, true);
}
});
var copySelection = function (options, isColumns) {
var project = options.heatMap.getProject();
var dataset = project
.getSortedFilteredDataset();
var activeTrackName = options.heatMap.getSelectedTrackName(isColumns);
var v;
if (activeTrackName == null) {
v = isColumns ? dataset.getColumnMetadata()
.get(0) : dataset
.getRowMetadata().get(0);
} else {
v = isColumns ? dataset.getColumnMetadata()
.getByName(activeTrackName) : dataset
.getRowMetadata().getByName(activeTrackName);
}
var selectionModel = isColumns ? project
.getColumnSelectionModel() : project
.getRowSelectionModel();
var text = [];
var toStringFunction = phantasus.VectorTrack.vectorToString(v);
selectionModel.getViewIndices().forEach(
function (index) {
text.push(toStringFunction(v
.getValue(index)));
});
phantasus.Util.setClipboardData(text.join('\n'));
};
this.add({
name: 'Copy Selected Rows',
cb: function (options) {
copySelection(options, false);
}
});
this.add({
name: 'Copy Selected Columns',
cb: function (options) {
copySelection(options, true);
}
});
var annotateSelection = function (options, isColumns) {
var project = options.heatMap.getProject();
var selectionModel = isColumns ? project
.getColumnSelectionModel()
: project
.getRowSelectionModel();
if (selectionModel.count() === 0) {
phantasus.FormBuilder
.showMessageModal({
title: 'Annotate Selection',
html: 'No ' + (isColumns ? 'columns' : 'rows') + ' selected.',
focus: options.heatMap.getFocusEl()
});
return;
}
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'annotation_name',
type: 'text',
required: true
});
formBuilder.append({
name: 'annotation_value',
type: 'text',
required: true
});
phantasus.FormBuilder
.showOkCancel({
title: 'Annotate',
content: formBuilder.$form,
focus: options.heatMap.getFocusEl(),
okCallback: function () {
var value = formBuilder
.getValue('annotation_value');
var annotationName = formBuilder
.getValue('annotation_name');
var dataset = project
.getSortedFilteredDataset();
var fullDataset = project
.getFullDataset();
if (isColumns) {
dataset = phantasus.DatasetUtil
.transposedView(dataset);
fullDataset = phantasus.DatasetUtil
.transposedView(fullDataset);
}
var existingVector = fullDataset
.getRowMetadata()
.getByName(
annotationName);
var v = dataset
.getRowMetadata().add(
annotationName);
selectionModel
.getViewIndices()
.forEach(
function (index) {
v
.setValue(
index,
value);
});
phantasus.VectorUtil
.maybeConvertStringToNumber(v);
project
.trigger(
'trackChanged',
{
vectors: [v],
display: existingVector != null ? []
: [phantasus.VectorTrack.RENDER.TEXT],
columns: isColumns
});
}
});
};
this.add({
ellipsis: false,
name: 'Annotate Selected Rows',
cb: function (options) {
annotateSelection(options, false);
}
});
this.add({
ellipsis: false,
name: 'Annotate Selected Columns',
cb: function (options) {
annotateSelection(options, true);
}
});
this.add({
name: 'Copy Selected Dataset',
cb: function (options) {
var project = options.heatMap.getProject();
var dataset = project.getSelectedDataset({
emptyToAll: false
});
var columnMetadata = dataset
.getColumnMetadata();
var rowMetadata = dataset.getRowMetadata();
// only copy visible tracks
var visibleColumnFields = options.heatMap
.getVisibleTrackNames(true);
var columnFieldIndices = [];
_.each(visibleColumnFields, function (name) {
var index = phantasus.MetadataUtil.indexOf(
columnMetadata, name);
if (index !== -1) {
columnFieldIndices.push(index);
}
});
columnMetadata = new phantasus.MetadataModelColumnView(
columnMetadata, columnFieldIndices);
var rowMetadata = dataset.getRowMetadata();
// only copy visible tracks
var visibleRowFields = options.heatMap
.getVisibleTrackNames(false);
var rowFieldIndices = [];
_.each(visibleRowFields, function (name) {
var index = phantasus.MetadataUtil.indexOf(
rowMetadata, name);
if (index !== -1) {
rowFieldIndices.push(index);
}
});
rowMetadata = new phantasus.MetadataModelColumnView(
rowMetadata, rowFieldIndices);
var text = new phantasus.GctWriter()
.write(dataset);
phantasus.Util.setClipboardData(text);
}
});
var _this = this;
//console.log(_this);
[
new phantasus.HClusterTool(), new phantasus.MarkerSelection(),
new phantasus.NearestNeighbors(), new phantasus.AdjustDataTool(),
new phantasus.CollapseDatasetTool(), new phantasus.CreateAnnotation(), new phantasus.SimilarityMatrixTool(),
new phantasus.TransposeTool(), new phantasus.TsneTool(),
new phantasus.KmeansTool(), new phantasus.LimmaTool(), new phantasus.DESeqTool()].forEach(function (tool) {
_this.add({
ellipsis: false,
name: tool.toString(),
gui: function () {
return tool;
},
cb: function (options) {
phantasus.HeatMap.showTool(tool, options.heatMap);
}
});
});
this.add({
name: 'Edit Fonts',
ellipse: true,
cb: function (options) {
var trackInfo = options.heatMap.getLastSelectedTrackInfo();
var project = options.heatMap.getProject();
var model = trackInfo.isColumns ? project
.getColumnFontModel() : project
.getRowFontModel();
var chooser = new phantasus.FontChooser({fontModel: model, track: options.heatMap.getTrack(trackInfo.name, trackInfo.isColumns), heatMap: options.heatMap});
phantasus.FormBuilder.showInModal({
title: 'Edit Fonts',
html: chooser.$div,
close: 'Close',
focus: options.heatMap.getFocusEl()
});
}
});
};
phantasus.ActionManager.prototype = {
getActions: function () {
return this.actions;
},
getAction: function (name) {
return this.actionNameToAction.get(name);
},
execute: function (name, args) {
var action = this.getAction(name);
if (args == null) {
args = {};
}
args.heatMap = this.heatMap;
action.cb(args);
phantasus.Util.trackEvent({
eventCategory: 'Tool',
eventAction: name
});
},
add: function (action) {
this.actions.push(action);
this.actionNameToAction.set(action.name, action);
}
};
phantasus.CanvasUtil = function () {
};
phantasus.CanvasUtil.dragging = false;
phantasus.CanvasUtil.FONT_NAME = '"Helvetica Neue",Helvetica,Arial,sans-serif';
phantasus.CanvasUtil.FONT_COLOR = 'rgb(0, 0, 0)';
phantasus.CanvasUtil.getFontFamily = function (context) {
// older versions of Adobe choke when a font family contains a font that is not installed
return (typeof C2S !== 'undefined' && context instanceof C2S) || (typeof canvas2pdf !== 'undefined' && context instanceof canvas2pdf.PdfContext)
? 'Helvetica'
: phantasus.CanvasUtil.FONT_NAME;
};
phantasus.CanvasUtil.getPreferredSize = function (c) {
var size = c.getPreferredSize();
var prefWidth = c.getPrefWidth();
var prefHeight = c.getPrefHeight();
// check for override override
if (prefWidth !== undefined) {
size.widthSet = true;
size.width = prefWidth;
}
if (prefHeight !== undefined) {
size.heightSet = true;
size.height = prefHeight;
}
return size;
};
phantasus.CanvasUtil.BACKING_SCALE = 1;
if (typeof window !== 'undefined' && 'devicePixelRatio' in window) {
if (window.devicePixelRatio > 1) {
phantasus.CanvasUtil.BACKING_SCALE = window.devicePixelRatio;
}
}
phantasus.CanvasUtil.setBounds = function (canvas, bounds) {
var backingScale = phantasus.CanvasUtil.BACKING_SCALE;
if (bounds.height != null) {
canvas.height = bounds.height * backingScale;
canvas.style.height = bounds.height + 'px';
}
if (bounds.width != null) {
canvas.width = bounds.width * backingScale;
canvas.style.width = bounds.width + 'px';
}
if (bounds.left != null) {
canvas.style.left = bounds.left + 'px';
}
if (bounds.top != null) {
canvas.style.top = bounds.top + 'px';
}
};
phantasus.CanvasUtil.drawShape = function (context, shape, x, y, size2, isFill) {
if (size2 < 0) {
return;
}
context.beginPath();
if (shape === 'circle-minus') {
context.arc(x, y, size2, 0, 2 * Math.PI, false);
context.moveTo(x - size2, y);
context.lineTo(x + size2, y);
} else if (shape === 'circle') {
context.arc(x, y, size2, 0, 2 * Math.PI, false);
} else if (shape === 'square') {
context.rect(x - size2, y - size2, size2 * 2, size2 * 2);
} else if (shape === 'plus') {
// vertical line
context.moveTo(x, y - size2);
context.lineTo(x, y + size2);
// horizontal line
context.moveTo(x - size2, y);
context.lineTo(x + size2, y);
} else if (shape === 'x') {
context.moveTo(x - size2, y - size2);
context.lineTo(x + size2, y + size2);
context.moveTo(x + size2, y - size2);
context.lineTo(x - size2, y + size2);
} else if (shape === 'asterisk') {
// x with vertical line
context.moveTo(x - size2, y - size2);
context.lineTo(x + size2, y + size2);
context.moveTo(x + size2, y - size2);
context.lineTo(x - size2, y + size2);
context.moveTo(x, y - size2);
context.lineTo(x, y + size2);
} else if (shape === 'diamond') {
// start at middle top
context.moveTo(x, y - size2);
// right
context.lineTo(x + size2, y);
// bottom
context.lineTo(x, y + size2);
// left
context.lineTo(x - size2, y);
// top
context.lineTo(x, y - size2);
} else if (shape === 'triangle-up') {
// top
context.moveTo(x, y - size2);
// right
context.lineTo(x + size2, y + size2);
// left
context.lineTo(x - size2, y + size2);
context.lineTo(x, y - size2);
} else if (shape === 'triangle-down') {
// bottom
context.moveTo(x, y + size2);
// left
context.lineTo(x - size2, y - size2);
// right
context.lineTo(x + size2, y - size2);
context.lineTo(x, y + size2);
} else if (shape === 'triangle-left') {
// left
context.moveTo(x - size2, y);
// top
context.lineTo(x + size2, y - size2);
// bottom
context.lineTo(x + size2, y + size2);
context.lineTo(x - size2, y);
} else if (shape === 'triangle-right') {
// right
context.moveTo(x + size2, y);
// lower left
context.lineTo(x - size2, y + size2);
// upper left
context.lineTo(x - size2, y - size2);
context.lineTo(x + size2, y);
}
isFill ? context.fill() : context.stroke();
};
phantasus.CanvasUtil.drawLine = function (context, x1, y1, x2, y2) {
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
};
phantasus.CanvasUtil.resetTransform = function (context) {
context.setTransform(1, 0, 0, 1, 0, 0);
if (phantasus.CanvasUtil.BACKING_SCALE !== 1) {
context.scale(phantasus.CanvasUtil.BACKING_SCALE,
phantasus.CanvasUtil.BACKING_SCALE);
}
};
phantasus.CanvasUtil.bezierCurveTo = function (context, start, end) {
var m1 = (start[1] + end[1]) / 2;
context.beginPath();
context.moveTo(start[0], start[1]);
// context.lineTo(leftp[0], leftp[1]);
context.bezierCurveTo(start[0], m1, end[0], m1, end[0], end[1]);
context.stroke();
};
phantasus.CanvasUtil.createCanvas = function () {
var $c = $('<canvas></canvas>');
$c.attr('tabindex', '0');
$c.css({
cursor: 'default',
outline: 0,
overflow: 'hidden',
position: 'absolute',
'z-index': 1
});
return $c[0];
};
phantasus.CanvasUtil.getHeaderStringWidth = function (context, s) {
context.font = '14px ' + phantasus.CanvasUtil.getFontFamily(context);
return context.measureText(s).width + 18;
};
phantasus.CanvasUtil.forceSubPixelRendering = function (context) {
context.getImageData(0, 0, 1, 1);
};
phantasus.CanvasUtil.getVectorStringWidth = function (context, vector, positions,
end) {
if (positions.getSize() < 6) {
return 0;
}
var fontSize = Math.min(phantasus.VectorTrack.MAX_FONT_SIZE, positions.getSize() - 2);
if (fontSize <= 0) {
return 0;
}
context.font = fontSize + 'px ' + phantasus.CanvasUtil.getFontFamily(context);
var toString = phantasus.VectorTrack.vectorToString(vector);
var maxWidth = 0;
// var maxWidth2 = 0;
var n = end <= 0 ? vector.size() : Math.min(end, vector.size());
for (var i = 0; i < n; i++) {
var value = vector.getValue(i);
if (value != null && value != '') {
value = toString(value);
} else {
continue;
}
var width = context.measureText(value).width;
if (width > maxWidth) {
maxWidth = width;
}
// if (width > maxWidth2 && width < maxWidth) {
// maxWidth2 = width;
// }
}
return maxWidth === 0 ? maxWidth : (maxWidth + 2);
};
phantasus.CanvasUtil.clipString = function (context, string, availTextWidth) {
var textWidth = context.measureText(string).width;
if (textWidth <= availTextWidth) {
return string;
}
var clipString = '...';
availTextWidth -= context.measureText(clipString).width;
if (availTextWidth <= 0) {
// can not fit any characters
return clipString;
}
var width = 0;
for (var nChars = 0, stringLength = string.length; nChars < stringLength; nChars++) {
width += context.measureText(string[nChars]).width;
if (width > availTextWidth) {
string = string.substring(0, nChars);
break;
}
}
return string + clipString;
};
phantasus.CanvasUtil.toSVG = function (drawable, file) {
var totalSize = {
width: drawable.getWidth(),
height: drawable.getHeight()
};
var context = new C2S(totalSize.width, totalSize.height);
context.save();
drawable.draw({
x: 0,
y: 0,
width: totalSize.width,
height: totalSize.height
}, context);
context.restore();
var svg = context.getSerializedSvg();
var blob = new Blob([svg], {
type: 'text/plain;charset=utf-8'
});
saveAs(blob, file);
};
phantasus.CanvasUtil.getMousePos = function (element, event, useDelta) {
return phantasus.CanvasUtil.getMousePosWithScroll(element, event, 0, 0,
useDelta);
};
phantasus.CanvasUtil.getClientXY = function (event, useDelta) {
var clientX;
var clientY;
if (event.pointers) {
if (event.pointers.length > 0) {
clientX = event.pointers[0].clientX - (useDelta ? event.deltaX : 0);
clientY = event.pointers[0].clientY - (useDelta ? event.deltaY : 0);
} else {
clientX = event.srcEvent.clientX - (useDelta ? event.deltaX : 0);
clientY = event.srcEvent.clientY - (useDelta ? event.deltaY : 0);
}
} else {
clientX = event.clientX;
clientY = event.clientY;
}
return {
x: clientX,
y: clientY
};
};
phantasus.CanvasUtil.getMousePosWithScroll = function (element, event, scrollX,
scrollY, useDelta) {
return phantasus.CanvasUtil._getMousePosWithScroll(element, scrollX,
scrollY, phantasus.CanvasUtil.getClientXY(event, useDelta));
};
phantasus.CanvasUtil._getMousePosWithScroll = function (element, scrollX,
scrollY, clientXY) {
var rect = element.getBoundingClientRect();
return {
x: clientXY.x - rect.left + scrollX,
y: clientXY.y - rect.top + scrollY
};
};
/**
* @param {phantasus.Set} [] -
* options.set set of selected items
* @see phantasus.Table
*/
phantasus.CheckBoxList = function (options) {
var _this = this;
var set = options.set || new phantasus.Set();
options = $.extend(true, {}, {
height: '150px',
showHeader: false,
select: false,
search: true,
checkBoxSelectionOnTop: false,
rowHeader: function (item) {
var header = [];
// header
// .push('<div style="overflow: hidden;text-overflow: ellipsis;"
// class="phantasus-hover">');
header.push('<span><input name="toggle" type="checkbox" '
+ (set.has(_this.getter(item)) ? ' checked' : '') + '/> ');
header.push('</span>');
// header
// .push('<button
// style="background-color:inherit;position:absolute;top:0;right:0;line-height:inherit;padding:0px;margin-top:4px;"
// class="btn btn-link phantasus-hover-show">only</button>');
// header.push('</div>');
return header.join('');
// return '<span><input name="toggle"
// type="checkbox" '
// + (set.has(_this.getter(item)) ? ' checked' : '')
// + '/> </span>'
}
}, options);
options = phantasus.Table.createOptions(options);
if (options.columns.length === 1) {
options.maxWidth = 583;
}
var idColumn = options.columns[0];
for (var i = 0; i < options.columns.length; i++) {
if (options.columns[i].idColumn) {
idColumn = options.columns[i];
break;
}
}
this.getter = idColumn.getter;
var table = new phantasus.Table(options);
if (options.columns.length === 1) {
options.$el.find('.slick-table-header').find('[name=right]').remove();
}
this.table = table;
var html = [];
html.push('<div style="display:inline;">');
html.push('<div style="display:inline;" class="dropdown">');
html.push('<button class="btn btn-default btn-xs dropdown-toggle" type="button"' +
' data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">');
html.push('<i data-name="checkbox" class="fa fa-square-o"' +
' aria-hidden="true"></i>');
html.push(' <span class="fa fa-caret-down"></span>');
html.push('</button>');
html.push('<ul style="font-size:12px;" class="dropdown-menu">');
html.push('<li><a name="selectAll" href="#">Select All</a></li>');
html.push('<li><a name="selectNone" href="#">Select None</a></li>');
html.push('<li><a name="invertSel" href="#">Invert Selection</a></li>');
html.push('</ul>');
html.push('</div>');
html.push('<span data-name="available" style="font-size:12px;padding-left:6px;"></span>');
html.push('</div>');
var $checkBoxEl = $(html.join(''));
table.$header.find('[name=left]').html($checkBoxEl);
var $selection = $checkBoxEl.find('[data-name=available]');
var $selectAll = $checkBoxEl.find('[name=selectAll]');
var $selectNone = $checkBoxEl.find('[name=selectNone]');
var $cb = $checkBoxEl.find('[data-name=checkbox]');
var updateLabel = function () {
var label = [];
label.push('selected ');
label.push(phantasus.Util.intFormat(set.size()));
label.push(' of ');
label.push(phantasus.Util.intFormat(table.getAllItemCount()));
if (table.getFilteredItemCount() !== table.getAllItemCount()) {
label.push(', ');
label.push(phantasus.Util.intFormat(table.getFilteredItemCount()));
label.push(table.getFilteredItemCount() === 1 ? ' match' : ' matches');
}
$selection.html(label.join(''));
};
table.grid.on('filter', function (e) {
updateLabel();
});
$cb.on('click', function (e) {
if ($cb.hasClass('fa-square-o')) {
var items = table.getItems(); // select all
for (var i = 0; i < items.length; i++) {
set.add(_this.getter(items[i]));
}
} else { // select none
var items = table.getItems();
for (var i = 0; i < items.length; i++) {
set.remove(_this.getter(items[i]));
}
}
table.trigger('checkBoxSelectionChanged', {
source: _this,
set: set
});
e.preventDefault();
e.stopPropagation();
});
$selectAll.on('click', function (e) {
var items = table.getItems();
for (var i = 0, nitems = items.length; i < nitems; i++) {
set.add(_this.getter(items[i]));
}
_this.table.trigger('checkBoxSelectionChanged', {
source: _this,
set: set
});
e.preventDefault();
_this.table.redraw();
});
$checkBoxEl.find('[name=invertSel]').on('click', function (e) {
// selected become unselected, unselected become selected
var items = table.getItems();
for (var i = 0, nitems = items.length; i < nitems; i++) {
var val = _this.getter(items[i]);
if (set.has(val)) {
set.remove(val);
} else {
set.add(val);
}
}
_this.table.trigger('checkBoxSelectionChanged', {
source: _this,
set: set
});
e.preventDefault();
_this.table.redraw();
});
$selectNone.on('click', function (e) {
var items = table.getItems();
for (var i = 0, nitems = items.length; i < nitems; i++) {
set.remove(_this.getter(items[i]));
}
_this.table.trigger('checkBoxSelectionChanged', {
source: _this,
set: set
});
e.preventDefault();
_this.table.redraw();
});
this.set = set;
this.table = table;
updateLabel();
var priorCount = 0;
this.table.on('checkBoxSelectionChanged', function () {
if (set.size() === 0) {
$cb.attr('class', 'fa fa-square-o');
} else {
var items = table.getItems();
var count = 0;
var found = false;
var notFound = false;
for (var i = 0; i < items.length; i++) {
if (set.has(_this.getter(items[i]))) {
count++;
found = true;
if (notFound) {
break;
}
} else {
notFound = true;
if (found) {
break;
}
}
}
if (count === 0) {
$cb.attr('class', 'fa fa-square-o');
} else if (count === items.length) {
$cb.attr('class', 'fa fa-check-square-o');
} else {
$cb.attr('class', 'fa fa-minus-square-o');
}
}
updateLabel();
_this.table.redraw();
});
table.on('click',
function (e) {
var $target = $(e.target);
var item = table.getItems()[e.row];
var value = _this.getter(item);
if ($target.is('.phantasus-hover-show')) { // only
set.clear();
set.add(value);
_this.table.trigger('checkBoxSelectionChanged', {
source: _this,
set: set
});
} else if (!options.select
|| ($target.is('[type=checkbox]') && $target
.attr('name') === 'toggle')) {
if (set.has(value)) {
set.remove(value);
} else {
set.add(value);
}
_this.table.trigger('checkBoxSelectionChanged', {
source: _this,
set: set
});
}
});
};
phantasus.CheckBoxList.prototype = {
searchWithPredicates: function (predicates) {
this.table.searchWithPredicates(predicates);
},
autocomplete: function (tokens, cb) {
this.table.autocomplete(tokens, cb);
},
setHeight: function (height) {
this.table.setHeight(height);
},
resize: function () {
this.table.resize();
},
setSearchVisible: function (visible) {
this.table.setSearchVisible(visible);
},
getSelectedRows: function () {
return this.table.getSelectedRows();
},
getSelectedItems: function () {
return this.table.getSelectedItems();
},
setSelectedRows: function (rows) {
this.table.setSelectedRows(rows);
},
getItems: function (items) {
return this.table.getItems();
},
getAllItemCount: function () {
return this.table.getAllItemCount();
},
getFilteredItemCount: function () {
return this.table.getFilteredItemCount();
},
setFilter: function (f) {
this.table.setFilter(f);
},
redraw: function () {
this.table.redraw();
},
getSelection: function () {
return this.set;
},
clearSelection: function (values) {
this.set.clear();
this.table.redraw();
},
setValue: function (values) {
this.setSelectedValues(values);
},
setSelectedValues: function (values) {
this.set.clear();
if (phantasus.Util.isArray(values)) {
for (var i = 0; i < values.length; i++) {
this.set.add(values[i]);
}
} else {
this.set.add(values);
}
this.table.redraw();
},
val: function () {
return this.set.values();
},
on: function (evtStr, handler) {
this.table.on(evtStr, handler);
return this;
},
off: function (evtStr, handler) {
this.table.off(evtStr, handler);
},
setItems: function (items) {
// remove items in selection that are not in new items
var newItems = new phantasus.Set();
var getter = this.getter;
for (var i = 0; i < items.length; i++) {
newItems.add(getter(items[i]));
}
var selection = this.set;
selection.forEach(function (val) {
if (!newItems.has(val)) {
selection.remove(val);
}
});
this.table.setItems(items);
this.table.trigger('checkBoxSelectionChanged', {
source: this,
set: selection
});
}
};
/**
*
* @param options.colorModel
* @param options.track
* @param options.heatMap
* @constructor
*/
phantasus.ColorSchemeChooser = function (options) {
var colorModel = options.colorModel;
var track = options.track;
var heatMap = options.heatMap;
// ensure map exists
colorModel.getMappedValue(track.getVector(track.settings.colorByField), track.getVector(track.settings.colorByField).getValue(0));
var formBuilder = new phantasus.FormBuilder();
if (track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR)) {
formBuilder.append({
value: track.settings.colorByField != null,
type: 'checkbox',
name: 'use_another_annotation_to_determine_color'
});
var annotationNames = phantasus.MetadataUtil.getMetadataNames(
track.isColumns ? heatMap.getProject().getFullDataset().getColumnMetadata() : heatMap.getProject().getFullDataset().getRowMetadata());
annotationNames.splice(annotationNames.indexOf(track.getName()), 1);
formBuilder.append({
name: 'annotation_name',
type: 'bootstrap-select',
options: annotationNames,
search: annotationNames.length > 10,
value: track.settings.colorByField
});
}
formBuilder.append({
name: 'discrete',
type: 'checkbox',
value: track.getVector(track.settings.colorByField).getProperties().get(phantasus.VectorKeys.DISCRETE)
});
var dataType = phantasus.VectorUtil.getDataType(track.getVector(track.settings.colorByField));
var isNumber = dataType === 'number' || dataType === '[number]';
formBuilder.setVisible('discrete', isNumber);
formBuilder.setVisible('annotation_name', track.settings.colorByField != null);
var $chooser = $('<div></div>');
$chooser.appendTo(formBuilder.$form);
var updateChooser = function () {
var colorSchemeChooser;
var v = track
.getVector(track.settings.colorByField);
formBuilder.setValue('discrete', v.getProperties().get(phantasus.VectorKeys.DISCRETE));
if (v.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
colorModel.getMappedValue(v, v.getValue(0)); // make sure color map exists
colorSchemeChooser = new phantasus.DiscreteColorSchemeChooser(
{
colorScheme: {
scale: colorModel
.getDiscreteColorScheme(track
.getVector(track.settings.colorByField))
}
});
colorSchemeChooser.on('change', function (event) {
colorModel.setMappedValue(track
.getVector(track.settings.colorByField), event.value,
event.color);
track.setInvalid(true);
track.repaint();
});
} else {
colorModel.getContinuousMappedValue(v, v.getValue(0)); // make sure color map exists
colorSchemeChooser = new phantasus.HeatMapColorSchemeChooser(
{
showRelative: false
});
colorSchemeChooser
.setColorScheme(colorModel
.getContinuousColorScheme(v));
colorSchemeChooser.on('change', function (event) {
track.setInvalid(true);
track.repaint();
});
}
$chooser.html(colorSchemeChooser.$div);
track.setInvalid();
track.repaint();
};
formBuilder.find('use_another_annotation_to_determine_color').on('change', function () {
var checked = $(this).prop('checked');
formBuilder.setValue('annotation_name', null);
formBuilder.setVisible('annotation_name', checked);
if (!checked) {
track.settings.colorByField = null;
updateChooser();
} else {
$chooser.empty();
}
formBuilder.setVisible('discrete', false);
});
formBuilder.find('annotation_name').on('change', function () {
var annotationName = $(this).val();
// ensure map exists
colorModel.getMappedValue(track.getVector(annotationName), track.getVector(annotationName).getValue(0));
track.settings.colorByField = annotationName;
var dataType = phantasus.VectorUtil.getDataType(track.getVector(track.settings.colorByField));
var isNumber = dataType === 'number' || dataType === '[number]';
formBuilder.setVisible('discrete', isNumber);
updateChooser();
track.setInvalid(true);
track.repaint();
});
formBuilder.find('discrete').on('change', function () {
track.getVector(track.settings.colorByField).getProperties().set(phantasus.VectorKeys.DISCRETE, $(this).prop('checked'));
updateChooser();
track.setInvalid(true);
track.repaint();
});
updateChooser();
this.$div = formBuilder.$form;
};
phantasus.ColumnDendrogram = function (heatMap, tree, positions, project) {
phantasus.AbstractDendrogram.call(this, heatMap, tree, positions,
project, phantasus.AbstractDendrogram.Type.COLUMN);
};
phantasus.ColumnDendrogram.prototype = {
drawNode: function (context, node) {
var radius = this.getNodeRadius(node);
var pix = this.toPix(node);
context.beginPath();
context.arc(pix[0], pix[1], 4, Math.PI * 2, false);
context.fill();
},
isDragHotSpot: function (p) {
return Math.abs(this.scale(this.cutHeight) - p.y) <= 2;
},
drawCutSlider: function (clip, context) {
if (context.setLineDash) {
context.setLineDash([5]);
}
context.strokeStyle = 'black';
var ny = this.scale(this.cutHeight);
context.beginPath();
context.moveTo(clip.x, ny);
context.lineTo(this.getUnscaledWidth(), ny);
context.stroke();
if (context.setLineDash) {
context.setLineDash([]);
}
},
createScale: function () {
// root has the largest height, leaves the smallest height
return d3.scale.linear().domain([this.tree.maxHeight, 0]).range(
[0, this.getUnscaledHeight()]);
},
paintMouseOver: function (clip, context) {
if (this.project.getHoverColumnIndex() !== -1) {
phantasus.CanvasUtil.resetTransform(context);
context.translate(-clip.x, 0);
this.drawColumnBorder(context, this.positions, this.project
.getHoverColumnIndex(), this.getUnscaledWidth());
}
},
drawColumnBorder: function (context, positions, index, gridSize) {
var size = positions.getItemSize(index);
var pix = positions.getPosition(index);
// top and bottom lines
context.beginPath();
context.moveTo(pix + size, 0);
context.lineTo(pix + size, gridSize);
context.stroke();
context.beginPath();
context.moveTo(pix, 0);
context.lineTo(pix, gridSize);
context.stroke();
},
getMaxIndex: function (clip) {
return phantasus.Positions.getRight(clip, this.positions);
},
getMinIndex: function (clip) {
return phantasus.Positions.getLeft(clip, this.positions);
},
getPreferredSize: function (context) {
return {
width: Math.ceil(this.positions.getPosition(this.positions
.getLength() - 1)
+ this.positions
.getItemSize(this.positions.getLength() - 1)),
height: 100
};
},
toPix: function (node) {
var min = this.positions.getPosition(node.minIndex)
+ this.positions.getItemSize(node.minIndex) / 2;
var max = this.positions.getPosition(node.maxIndex)
+ this.positions.getItemSize(node.maxIndex) / 2;
return [(min + max) / 2, this.scale(node.height)];
},
drawPathFromNodeToParent: function (context, node) {
var pix = this.toPix(node);
var parentPix = this.toPix(node.parent);
context.beginPath();
context.moveTo(pix[0], pix[1]);
context.lineTo(pix[0], parentPix[1]);
context.lineTo(parentPix[0], parentPix[1]);
context.stroke();
},
drawNodePath: function (context, node, minIndex, maxIndex) {
var children = node.children;
var left = children[0];
var right = children[1];
// set up points for poly line
var ny = this.scale(node.height);
var rx = this.toPix(right)[0];
var ry = this.scale(right.height);
var lx = this.toPix(left)[0];
var ly = this.scale(left.height);
var x, y;
if (!this.drawLeafNodes) {
var leftIsLeaf = left.children !== undefined;
var rightIsLeaf = right.children !== undefined;
if (leftIsLeaf) {
ly = ny + 4;
}
if (rightIsLeaf) {
ry = ny + 4;
}
x = [rx, rx, lx, lx];
y = [ry, ny, ny, ly];
} else {
x = [rx, rx, lx, lx];
y = [ry, ny, ny, ly];
}
context.beginPath();
context.moveTo(x[0], y[0]);
for (var i = 1, length = x.length; i < length; i++) {
context.lineTo(x[i], y[i]);
}
context.stroke();
}
};
phantasus.Util.extend(phantasus.ColumnDendrogram, phantasus.AbstractDendrogram);
phantasus.ConditionalRenderingUI = function (heatmap) {
var _this = this;
this.heatmap = heatmap;
var $div = $('<div class="container-fluid" style="min-width:180px;"></div>');
$div.on('click', '[data-name=add]', function (e) {
var $this = $(this);
var $row = $this.closest('.phantasus-entry');
// add after
var index = $row.index();
var condition = {
seriesName: null,
color: 'rgb(0,0,0)',
shape: null,
inheritColor: true,
accept: function (val) {
return false;
}
};
heatmap.heatmap.getColorScheme().getConditions().insert(index,
condition);
$row.after(_this.add(condition));
e.preventDefault();
});
$div.on('click', '[data-name=delete]', function (e) {
var $this = $(this);
var $row = $this.closest('.phantasus-entry');
var index = $row.index() - 1;
heatmap.heatmap.getColorScheme().getConditions().remove(index);
heatmap.revalidate();
$row.remove();
e.preventDefault();
});
var html = [];
html
.push('<div class="phantasus-entry">');
html.push('<div class="row">');
html
.push('<div style="padding-bottom:20px;" class="col-xs-8"><a class="btn btn-default btn-xs"' +
' role="button"' +
' data-name="add" href="#">Add Condition</a></div>');
html.push('</div>');
html.push('</div>');
$div.append(html.join(''));
this.$div = $div;
heatmap.heatmap.getColorScheme().getConditions().getConditions().forEach(
function (c) {
_this.add(c).appendTo($div);
});
};
phantasus.ConditionalRenderingUI.prototype = {
add: function (condition) {
var _this = this;
// shape: shapes and line
// color: if no color cell is drawn using this shape, otherwise draw
// shape on top of cell
// seriesName name
// value >= x and <= x
var html = [];
html.push('<div style="border-top:1px solid LightGrey;padding-bottom:6px;padding-top:6px;"' +
' class="phantasus-entry">');
html.push('<form class="form-horizontal">');
// seriesName
html.push('<div class="form-group">');
html
.push('<label class="col-xs-2">Series</label>');
html.push('<div class="col-xs-6">');
html
.push('<select class="form-control phantasus-form-control-inline" name="cond_series">');
html.push(phantasus.Util.createOptions(phantasus.DatasetUtil
.getSeriesNames(this.heatmap.getProject().getFullDataset())));
html.push('</select>');
html.push('</div>');
html.push('</div>');
// condition
html.push('<div class="form-group">');
html.push('<label class="col-xs-2">Condition</label>');
html.push('<div class="col-xs-6">');
html
.push('<select class="form-control phantasus-form-control-inline" name="lower"><option value="gte">>=</option><option value="gt">></option></select>');
html
.push('<input class="form-control phantasus-form-control-inline" name="v1" size="5" type="text">');
html.push('<span style="margin-right:1em;">and</span>');
html
.push('<select class="form-control phantasus-form-control-inline" name="upper"><option value="lte"><=</option><option value="lt"><</option></select>');
html
.push('<input class="form-control phantasus-form-control-inline" name="v2" size="5" type="text">');
html.push('</div>');
html.push('</div>');
// shape
html.push('<div class="form-group">');
html.push('<label class="col-xs-2">Shape</label>');
var shapeField = new phantasus.ShapeField({shapes: phantasus.VectorShapeModel.FILLED_SHAPES, showNone: false});
html.push('<div class="col-xs-4">');
html.push('<div style="display:inline;" data-name="shapeHolder"></div>');
html.push('</div>');
html.push('</div>');
// color
html.push('<div class="form-group">');
html.push('<label class="col-xs-offset-2 col-xs-4"><input name="inherit_color"' +
' type="checkbox" checked> Inherit' +
' color</label>');
html.push('</div>');
html.push('<div class="form-group">');
html.push('<label class="col-xs-2">Color</label>');
html.push('<div class="col-xs-4">');
html
.push('<input class="form-control" type="color" name="color" style="display:inline;' +
' width:6em;" disabled>');
html.push('</div>');
html.push('</div>');
html.push('<div class="row"><div class="col-xs-11">');
html
.push('<a class="btn btn-default btn-xs" role="button" data-name="delete"' +
' href="#">Delete Condition</a>');
html.push('</div></div>');
html.push('</div>'); // phantasus-entry
var $el = $(html.join(''));
console.log($el.find('form').length);
$el.find('form').on('submit', function (e) {
e.preventDefault();
});
shapeField.$el.appendTo($el.find('[data-name=shapeHolder]'));
var $color = $el.find('[name=color]');
var $series = $el.find('[name=cond_series]');
var $v1 = $el.find('[name=v1]');
var $v2 = $el.find('[name=v2]');
var $v1Op = $el.find('[name=lower]');
var $v2Op = $el.find('[name=upper]');
var $inherit_color = $el.find('[name=inherit_color]');
$color.prop('disabled', condition.inheritColor);
$color.val(condition.color);
$series.val(condition.seriesName);
shapeField.setShapeValue(condition.shape);
if (condition.v1 != null && !isNaN(condition.v1)) {
$v1.val(condition.v1);
}
if (condition.v2 != null && !isNaN(condition.v2)) {
$v2.val(condition.v2);
}
$v1Op.val(condition.v1Op);
$v2Op.val(condition.v2Op);
function updateAccept() {
var v1 = parseFloat($($v1).val());
var v2 = parseFloat($($v2).val());
var v1Op = $v1Op.val();
var v2Op = $v2Op.val();
condition.v1 = v1;
condition.v2 = v2;
condition.v1Op = v1Op;
condition.v2Op = v2Op;
var gtf = function () {
return true;
};
var ltf = function () {
return true;
};
if (!isNaN(v1)) {
gtf = v1Op === 'gt' ? function (val) {
return val > v1;
} : function (val) {
return val >= v1;
};
}
if (!isNaN(v2)) {
ltf = v2Op === 'lt' ? function (val) {
return val < v2;
} : function (val) {
return val <= v2;
};
}
condition.accept = function (val) {
return gtf(val) && ltf(val);
};
_this.heatmap.revalidate();
}
$v1Op.on('change', function (e) {
updateAccept();
});
$v2Op.on('change', function (e) {
updateAccept();
});
$v1.on('keyup', _.debounce(function (e) {
updateAccept();
}, 100));
$v2.on('keyup', _.debounce(function (e) {
updateAccept();
}, 100));
$inherit_color.on('click', function (e) {
condition.inheritColor = $(this).prop('checked');
$color.prop('disabled', condition.inheritColor);
_this.heatmap.revalidate();
});
$color.on('change', function (e) {
condition.color = $(this).val();
_this.heatmap.revalidate();
});
shapeField.on('change', function (e) {
condition.shape = e.shape;
_this.heatmap.revalidate();
});
$series.on('change', function (e) {
condition.seriesName = $(this).val();
_this.heatmap.revalidate();
});
condition.seriesName = $series.val();
return $el;
}
};
phantasus.DatasetHistory = function () {};
phantasus.DatasetHistory.prototype = {
STORAGE_KEY: 'dataset_history',
STORAGE_LIMIT: 10,
render: function ($parent) {
var _this = this;
$parent.empty();
$('<h4>Or select dataset from your history </h4>').appendTo($parent);
var currentHistory = this.get();
if (!_.size(currentHistory)) {
var $example = $('<h5>But apparently there is no datasets in your history. <a href="#" id="example-dataset">Open example dataset</a></h5>');
var $example_button = $example.find('#example-dataset');
$example_button.on('click', function () {
_this.trigger('open',
{
"file":"GSE53986",
"options":{
"interactive":true,
"isGEO":true
}
}
);
});
$example.appendTo($parent);
} else {
var ul = $('<ul></ul>');
_.each(currentHistory, function (elem, idx) {
var li = $('<li title="' + elem.name + _this.datasetTypeToString(elem) +'"><a href="#" data-idx="' + idx + '">' + elem.name + _this.datasetTypeToString(elem) +'</a></li>');
li.appendTo(ul);
});
ul.appendTo($parent);
ul.on('click', 'a', function (evt) {
evt.preventDefault();
evt.stopPropagation();
var clickedIndex = $(evt.target).data('idx');
_this.remove(clickedIndex);
_this.trigger('open', currentHistory[clickedIndex].openParameters);
});
}
},
store: function (options) {
var current = JSON.parse(localStorage.getItem(this.STORAGE_KEY) || '[]');
current.unshift({name: options.name, openParameters: options.openParameters, description: options.description});
current.length = Math.min(current.length, this.STORAGE_LIMIT);
current = _.uniq(current, function (elem) { return elem.name; });
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(current));
this.trigger('changed');
},
remove: function (idx) {
var current = JSON.parse(localStorage.getItem(this.STORAGE_KEY) || '[]');
current.splice(idx, 1);
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(current));
},
get: function () {
return JSON.parse(localStorage.getItem(this.STORAGE_KEY) || '[]');
},
datasetTypeToString: function (datasetHistory) {
if (datasetHistory.description) {
return " - " + datasetHistory.description;
} else {
return " - unknown dataset";
}
}
};
phantasus.Util.extend(phantasus.DatasetHistory, phantasus.Events);
phantasus.datasetHistory = new phantasus.DatasetHistory();
phantasus.DendrogramUtil = {};
phantasus.DendrogramUtil.setIndices = function (root, counter) {
counter = counter || 0;
var setIndex = function (node) {
var children = node.children;
var maxIndex = children[0].maxIndex;
var minIndex = children[0].minIndex;
var sum = children[0].index;
for (var i = 1, length = children.length; i < length; i++) {
var child = children[i];
sum += child.index;
minIndex = Math.min(minIndex, child.minIndex);
maxIndex = Math.max(maxIndex, child.maxIndex);
}
node.minIndex = minIndex;
node.maxIndex = maxIndex;
node.index = sum / children.length;
node.children.sort(function (a, b) {
return (a.index === b.index ? 0 : (a.index < b.index ? -1 : 1));
});
};
var visit = function (node, callback) {
var children = node.children;
var n;
if (children && (n = children.length)) {
var i = -1;
while (++i < n) {
visit(children[i], callback);
}
}
callback(node);
};
visit(root, function (n) {
if (n.children === undefined) {
n.minIndex = counter;
n.maxIndex = counter;
n.index = counter;
counter++;
} else {
setIndex(n);
}
return true;
});
};
phantasus.DendrogramUtil.convertEdgeLengthsToHeights = function (rootNode) {
var maxHeight = 0;
function setHeights(node, height) {
var newHeight = height;
if (node.length !== undefined) {
newHeight += node.length;
}
node.height = newHeight;
maxHeight = Math.max(maxHeight, node.height);
if (node.children != null) {
node.children.forEach(function (child) {
setHeights(child, newHeight);
});
}
}
setHeights(rootNode, 0);
var counter = 0;
phantasus.DendrogramUtil.dfs(rootNode, function (node) {
node.id = counter;
counter++;
node.height = maxHeight - node.height;
return true;
});
return {
maxHeight: maxHeight,
n: counter
};
};
phantasus.DendrogramUtil.writeNewick = function (node, out, leafNodeIdFunction) {
if (node.children != null && node.children.length > 0) {
// indent
out.push('(');
for (var i = 0; i < node.children.length; i++) {
if (i > 0) {
out.push(',');
}
phantasus.DendrogramUtil.writeNewick(node.children[i], out, leafNodeIdFunction);
}
out.push(')');
}
out.push(node.index != null ? leafNodeIdFunction(node) : ''); // leaf nodes have index
out.push(':');
var parentHeight = node.parent ? node.parent.height : node.height;
out.push(parentHeight - node.height);
};
phantasus.DendrogramUtil.parseNewick = function (text) {
var rootNode = Newick.parse(text);
var counter = 0;
var leafNodes = [];
function visit(node) {
var children = node.children;
if (children !== undefined) {
var left = children[0];
var right = children[1];
left.parent = node;
right.parent = node;
visit(left);
visit(right);
} else { // leaf node
node.minIndex = counter;
node.maxIndex = counter;
node.index = counter;
leafNodes.push(node);
counter++;
}
}
visit(rootNode);
var maxHeight = phantasus.DendrogramUtil.convertEdgeLengthsToHeights(rootNode).maxHeight;
phantasus.DendrogramUtil.setNodeDepths(rootNode);
phantasus.DendrogramUtil.setIndices(rootNode);
return {
maxHeight: rootNode.height,
rootNode: rootNode,
leafNodes: leafNodes,
nLeafNodes: leafNodes.length
};
};
phantasus.DendrogramUtil.cutAtHeight = function (rootNode, h) {
var roots = [];
phantasus.DendrogramUtil.dfs(rootNode, function (node) {
if (node.height < h) {
roots.push(node);
return false;
}
return true;
});
roots.sort(function (a, b) {
return (a.index < b.index ? -1 : (a.index == b.index ? 0 : 1));
});
return roots;
};
phantasus.DendrogramUtil.getDeepestChild = function (node, isMin) {
while (true) {
if (node.children === undefined) {
return node;
}
var index;
if (isMin) {
index = node.children[0].index < node.children[node.children.length - 1].index ? 0
: node.children.length - 1;
} else {
index = node.children[0].index > node.children[node.children.length - 1].index ? 0
: node.children.length - 1;
}
node = node.children[index];
}
};
/**
* Pre-order depth first traversal 1. Visit the root. 2. Traverse the left
* subtree. 3. Traverse the right subtree.
*/
phantasus.DendrogramUtil.dfs = function (node, callback, childrenAccessor) {
if (childrenAccessor === undefined) {
childrenAccessor = function (n) {
return n.children;
};
}
if (callback(node)) {
var children = childrenAccessor(node);
var n;
if (children && (n = children.length)) {
var i = -1;
while (++i < n) {
phantasus.DendrogramUtil.dfs(children[i], callback,
childrenAccessor);
}
}
}
};
phantasus.DendrogramUtil.copyTree = function (tree) {
var counter = 0;
function recurse(node) {
var children = node.children;
if (children !== undefined) {
var newChildren = [];
for (var i = 0, n = children.length; i < n; i++) {
var copy = $.extend({}, children[i]);
copy.parent = node;
newChildren.push(copy);
}
node.children = newChildren;
for (var i = 0, n = newChildren.length; i < n; i++) {
recurse(newChildren[i]);
}
} else {
node.index = counter;
node.minIndex = counter;
node.maxIndex = counter;
counter++;
}
}
var rootNode = $.extend({}, tree.rootNode);
rootNode.parent = undefined;
recurse(rootNode);
return {
nLeafNodes: tree.nLeafNodes,
maxDepth: tree.maxDepth,
rootNode: rootNode
};
};
phantasus.DendrogramUtil.collapseAtDepth = function (rootNode, maxDepth) {
// restore collapsed children
phantasus.DendrogramUtil.dfs(rootNode, function (d) {
if (d.collapsedChildren) {
d.children = d.collapsedChildren;
d.collapsedChildren = undefined;
}
return true;
});
// collapse nodes below specified depth
phantasus.DendrogramUtil.dfs(rootNode, function (d) {
var depth = d.depth;
if (depth > maxDepth) {
d.collapsedChildren = d.children;
d.children = undefined;
return false;
}
return true;
});
};
phantasus.DendrogramUtil.setNodeDepths = function (rootNode) {
var max = 0;
function recurse(node, depth) {
var children = node.children;
node.depth = depth;
max = Math.max(depth, max);
if (children !== undefined) {
var i = -1;
var j = depth + 1;
var n = children.length;
while (++i < n) {
var d = recurse(children[i], j);
}
}
return node;
}
recurse(rootNode, 0);
return max;
};
phantasus.DendrogramUtil.sortDendrogram = function (root, vectorToSortBy,
project, summaryFunction) {
summaryFunction = summaryFunction || function (array) {
var min = Number.MAX_VALUE;
for (var i = 0; i < array.length; i++) {
// sum += array[i].weight;
min = Math.min(min, array[i].weight);
}
return min;
};
var setWeights = function (node) {
if (node.children !== undefined) {
var children = node.children;
for (var i = 0; i < children.length; i++) {
setWeights(children[i]);
}
node.weight = summaryFunction(children);
} else {
node.weight = vectorToSortBy.getValue(node.index);
}
};
setWeights(root);
// sort children by weight
var nodeIdToModelIndex = {};
var leafNodes = phantasus.DendrogramUtil.getLeafNodes(root);
_.each(leafNodes, function (node) {
nodeIdToModelIndex[node.id] = project.convertViewColumnIndexToModel(node.index);
});
phantasus.DendrogramUtil.dfs(root, function (node) {
if (node.children) {
node.children.sort(function (a, b) {
return (a.weight === b.weight ? 0 : (a.weight < b.weight ? -1
: 1));
});
}
return true;
});
phantasus.DendrogramUtil.setIndices(root);
var sortOrder = [];
_.each(leafNodes, function (node) {
var oldModelIndex = nodeIdToModelIndex[node.id];
var newIndex = node.index;
sortOrder[newIndex] = oldModelIndex;
});
return sortOrder;
};
phantasus.DendrogramUtil.leastCommonAncestor = function (leafNodes) {
function getPathToRoot(node) {
var path = new phantasus.Map();
while (node != null) {
path.set(node.id, node);
node = node.parent;
}
return path;
}
var path = getPathToRoot(leafNodes[0]);
for (var i = 1; i < leafNodes.length; i++) {
var path2 = getPathToRoot(leafNodes[i]);
path.forEach(function (node, id) {
if (!path2.has(id)) {
path.remove(id);
}
});
// keep only those in path that are also in path2
}
var max = -Number.MAX_VALUE;
var maxNode;
path.forEach(function (n, id) {
if (n.depth > max) {
max = n.depth;
maxNode = n;
}
});
return maxNode;
};
// phantasus.DendrogramUtil.computePositions = function(rootNode, positions)
// {
// if (rootNode == null) {
// return;
// }
// phantasus.DendrogramUtil._computePositions(rootNode, positions);
// };
// /**
// * position is (left+right)/2
// */
// phantasus.DendrogramUtil._computePositions = function(node, positions) {
// if (node.children !== undefined) {
// var children = node.children;
// var left = children[0];
// var right = children[1];
// phantasus.DendrogramUtil._computePositions(left, positions);
// phantasus.DendrogramUtil._computePositions(right, positions);
// phantasus.DendrogramUtil.setIndex(node);
// node.position = (left.position + right.position) / 2;
// } else {
// node.position = positions.getItemSize(node.index) / 2
// + positions.getPosition(node.index);
// }
// };
/**
*
* @param options.rootNode Dendrogram root node
* @param options.text Search text
* @param options.defaultMatchMode
* 'exact' or 'contains'
* @param options.matchAllPredicates Whether to match all predicates
*/
phantasus.DendrogramUtil.search = function (options) {
var searchText = options.text;
var rootNode = options.rootNode;
var tokens = phantasus.Util.getAutocompleteTokens(searchText);
var predicates;
var nmatches = 0;
var matchAllPredicates = options.matchAllPredicates === true;
if (tokens == null || tokens.length == 0) {
phantasus.DendrogramUtil.dfs(rootNode, function (node) {
node.search = false;
return true;
});
nmatches = -1;
} else {
predicates = phantasus.Util.createSearchPredicates({
tokens: tokens,
defaultMatchMode: options.defaultMatchMode
});
var npredicates = predicates.length;
phantasus.DendrogramUtil
.dfs(
rootNode,
function (node) {
var matches = false;
if (node.info) {
searchLabel:
if (!matchAllPredicates) { // at least one predicate matches
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
var filterColumnName = predicate.getField();
if (filterColumnName != null) {
var vals = node.info[filterColumnName];
for (var i = 0, nvals = vals.length; i < nvals; i++) {
if (predicate.accept(vals[i])) {
matches = true;
break searchLabel;
}
}
} else {
for (var name in node.info) {
var vals = node.info[name];
for (var i = 0, nvals = vals.length; i < nvals; i++) {
if (predicate.accept(vals[i])) {
matches = true;
break searchLabel;
}
}
}
}
}
} else { // all predicates must match
matches = true;
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
var filterColumnName = predicate.getField();
if (filterColumnName != null) {
var vals = node.info[filterColumnName];
for (var i = 0, nvals = vals.length; i < nvals; i++) {
if (!predicate.accept(vals[i])) {
matches = false;
break searchLabel;
}
}
} else {
for (var name in node.info) {
var vals = node.info[name];
for (var i = 0, nvals = vals.length; i < nvals; i++) {
if (!predicate.accept(vals[i])) {
matches = false;
break searchLabel;
}
}
}
}
}
}
}
node.search = matches;
if (matches) {
nmatches++;
}
return true;
}
);
}
return nmatches;
}
;
phantasus.DendrogramUtil.squishNonSearchedNodes = function (
heatMap,
isColumns) {
if (isColumns) {
heatMap.getHeatMapElementComponent().getColumnPositions().setSize(13);
} else {
heatMap.getHeatMapElementComponent().getRowPositions().setSize(13);
}
var expandedLeafNodes = {};
var dendrogram = isColumns ? heatMap.columnDendrogram
: heatMap.rowDendrogram;
phantasus.DendrogramUtil.dfs(dendrogram.tree.rootNode, function (node) {
for (var i = node.minIndex; i <= node.maxIndex; i++) {
if (node.search) {
expandedLeafNodes[i] = true;
}
}
return true;
});
var clusterIds = [];
var previous = expandedLeafNodes[0];
var squishedIndices = {};
if (!previous) {
squishedIndices[0] = true;
}
var clusterNumber = 0;
clusterIds.push(clusterNumber);
for (var i = 1, nleaves = dendrogram.tree.leafNodes.length; i < nleaves; i++) {
var expanded = expandedLeafNodes[i];
if (expanded !== previous) {
clusterNumber++;
previous = expanded;
}
if (!expanded) {
squishedIndices[i] = true;
}
clusterIds.push(clusterNumber);
}
if (isColumns) {
heatMap.getHeatMapElementComponent().getColumnPositions().setSquishedIndices(squishedIndices);
heatMap.getProject().setGroupColumns(
[new phantasus.SpecifiedGroupByKey(clusterIds)], false);
} else {
heatMap.getHeatMapElementComponent().getRowPositions().setSquishedIndices(squishedIndices);
heatMap.getProject().setGroupRows(
[new phantasus.SpecifiedGroupByKey(clusterIds)], false);
}
};
phantasus.DendrogramUtil.getLeafNodes = function (rootNode) {
var leafNodes = [];
phantasus.DendrogramUtil.dfs(rootNode, function (node) {
if (node.children === undefined) {
leafNodes.push(node);
}
return true;
});
return leafNodes;
};
phantasus.DiscreteColorSchemeChooser = function (options) {
var formBuilder = new phantasus.FormBuilder();
var map = options.colorScheme.scale;
formBuilder.append({
name: 'selected_value',
type: 'bootstrap-select',
options: map.keys()
});
var $select = formBuilder.find('selected_value');
formBuilder.append({
style: 'max-width:50px;',
name: 'selected_color',
type: 'color'
});
var selectedVal = $select.val();
var _this = this;
var $color = formBuilder.find('selected_color');
$color.val(map.get(selectedVal));
$color.on('change', function (e) {
var color = $(this).val();
map.set(selectedVal, color);
_this.trigger('change', {
value: selectedVal,
color: color
});
});
$select.on('change', function () {
selectedVal = $select.val();
var c = map.get(selectedVal);
$color.val(c);
});
this.$div = formBuilder.$form;
};
phantasus.DiscreteColorSchemeChooser.prototype = {};
phantasus.Util.extend(phantasus.DiscreteColorSchemeChooser, phantasus.Events);
phantasus.DiscreteColorSupplier = function () {
this.colorMap = new phantasus.Map();
this.hiddenValue = 0;
this.hiddenValues = new phantasus.Set();
phantasus.AbstractColorSupplier.call(this);
this.scalingMode = phantasus.HeatMapColorScheme.ScalingMode.FIXED;
};
phantasus.DiscreteColorSupplier.prototype = {
createInstance: function () {
return new phantasus.DiscreteColorSupplier();
},
/**
* @param.array Array of name, value, color pairs
*/
setColorMap: function (array) {
this.colorMap = new phantasus.Map();
this.colors = [];
this.fractions = [];
this.names = [];
this.min = Number.MAX_VALUE;
this.max = -Number.MAX_VALUE;
for (var i = 0; i < array.length; i++) {
this.colorMap.set(array[i].value, array[i].color);
this.fractions.push(array[i].value);
this.names.push(array[i].name);
this.colors.push(array[i].color);
this.min = Math.min(this.min, array[i].value);
this.max = Math.max(this.max, array[i].value);
}
},
copy: function () {
var c = this.createInstance();
c.names = this.names.slice(0);
c.colorMap = new phantasus.Map();
this.colorMap.forEach(function (color, value) {
c.colorMap.set(value, color);
});
c.colors = this.colors.slice(0);
c.fractions = this.fractions.slice(0);
this.hiddenValues.forEach(function (val) {
c.hiddenValues.add(val);
});
c.missingColor = this.missingColor;
return c;
},
isStepped: function () {
return true;
},
getColor: function (row, column, value) {
if (this.hiddenValues.has(value)) {
value = this.hiddenValue;
}
if (isNaN(value)) {
return this.missingColor;
}
return this.colorMap.get(value);
}
};
phantasus.Util.extend(phantasus.DiscreteColorSupplier,
phantasus.AbstractColorSupplier);
phantasus.Divider = function (vertical) {
phantasus.AbstractCanvas.call(this, false);
this.vertical = vertical;
var that = this;
var canvas = this.canvas;
canvas.style.cursor = vertical ? 'ew-resize' : 'ns-resize';
if (vertical) {
this.setBounds({
height: 15,
width: 4
});
} else {
this.setBounds({
height: 4,
width: 15
});
}
this.hammer = phantasus.Util.hammer(canvas, ['pan']).on('panstart',
this.panstart = function (event) {
that.trigger('resizeStart');
phantasus.CanvasUtil.dragging = true;
}).on('panmove', this.panmove = function (event) {
if (that.vertical) {
that.trigger('resize', {
delta: event.deltaX
});
} else {
that.trigger('resize', {
delta: event.deltaY
});
}
}).on('panend', this.panend = function (event) {
phantasus.CanvasUtil.dragging = false;
that.trigger('resizeEnd');
});
this.paint();
};
phantasus.Divider.prototype = {
dispose: function () {
phantasus.AbstractCanvas.prototype.dispose.call(this);
this.hammer.off('panstart', this.panstart).off('panmove', this.panmove).off('panend', this.panend);
this.hammer.destroy();
},
getPreferredSize: function () {
return {
width: 3,
height: this.getUnscaledHeight()
};
},
draw: function (clip, context) {
var width = this.getUnscaledWidth();
var height = this.getUnscaledHeight();
context.clearRect(0, 0, width, height);
context.strokeStyle = '#ddd';
if (!this.vertical) {// horizontal line at top
context.beginPath();
context.moveTo(0, 1.5);
context.lineTo(width, 1.5);
context.stroke();
} else { // vertical line at left
context.beginPath();
context.moveTo(0, 0);
context.lineTo(0, height);
context.stroke();
}
}
};
phantasus.Util.extend(phantasus.Divider, phantasus.AbstractCanvas);
phantasus.Util.extend(phantasus.Divider, phantasus.Events);
phantasus.DualList = function (leftOptions, rightOptions) {
var html = [];
html.push('<div class="container-fluid">');
html.push('<div class="row">');
html.push('<div class="col-xs-4"><label>Available Fields</label></div>');
html.push('<div class="col-xs-2"></div>');
html.push('<div class="col-xs-4"><label>Selected Fields</label></div>');
html.push('</div>'); // row
html.push('<div class="row">');
html
.push('<div class="col-xs-4"><select class="form-control" name="left" multiple></select></div>');
html
.push('<div class="col-xs-2"><div class="btn-group-vertical" role="group">'
+ '<button name="add" type="button" class="btn btn-xs btn-default">Add</button>'
+ '<button name="remove" type="button" class="btn btn-xs btn-default">Remove</button>'
+ '<button name="up" type="button" class="btn btn-xs btn-default">Move Up</button>'
+ '<button name="down" type="button" class="btn btn-xs btn-default">Move Down</button>'
+ '</div></div>');
html
.push('<div class="col-xs-4"><select class="form-control" name="right" multiple></select></div>');
html.push('</div>'); // row
html.push('</div>');
this.$el = $(html.join(''));
var _this = this;
this.$el.find('[name=add]').on('click', function () {
_this.addSelected();
});
this.$el.find('[name=remove]').on('click', function () {
_this.removeSelected();
});
this.$el.find('[name=up]').on('click', function () {
_this.moveUp();
});
this.$el.find('[name=down]').on('click', function () {
_this.moveDown();
});
this.left = this.$el.find('[name=left]')[0];
this.right = this.$el.find('[name=right]')[0];
for (var i = 0; i < leftOptions.length; i++) {
this.left.options[i] = leftOptions[i];
}
for (var i = 0; i < rightOptions.length; i++) {
this.right.options[i] = rightOptions[i];
}
};
phantasus.DualList.prototype = {
addSelected: function () {
var left = this.left;
var right = this.right;
for (var i = 0; i < left.options.length; i++) {
if (left.options[i].selected) {
var opt = left.options[i];
right.options[right.options.length] = new Option(opt.innerHTML,
opt.value);
left.options[i] = null;
i--;
}
}
},
addAll: function () {
var left = this.left;
var right = this.right;
for (var i = 0; i < left.options.length; i++) {
var opt = left.options[i];
right.options[right.options.length] = new Option(opt.innerHTML,
opt.value);
}
left.options.length = 0;
},
removeSelected: function () {
var left = this.left;
var right = this.right;
for (var i = 0; i < right.options.length; i++) {
if (right.options[i].selected) {
var opt = right.options[i];
left.options[left.options.length] = new Option(opt.innerHTML,
opt.value);
right.options[i] = null;
i--;
}
}
},
getOptions: function (isLeft) {
var sel = isLeft ? this.left : this.right;
var options = [];
for (var i = 0; i < sel.options.length; i++) {
options.push(sel.options[i].value);
}
return options;
},
removeAll: function () {
var left = this.left;
var right = this.right;
for (var i = 0; i < right.options.length; i++) {
var opt = right.options[i];
left.options[left.options.length] = new Option(opt.innerHTML,
opt.value);
}
right.options.length = 0;
},
moveUp: function () {
var right = this.right;
var selectedOptions = right.selectedOptions;
var indices = [];
for (var i = 0; i < selectedOptions.length; i++) {
indices.push(selectedOptions[i].index);
}
var index = phantasus.Util.indexSort(indices, false);
for (var i = 0; i < selectedOptions.length; i++) {
var sel = selectedOptions[index[i]].index;
var optHTML = right.options[sel].innerHTML;
var optVal = right.options[sel].value;
var opt1HTML = right.options[sel - 1].innerHTML;
var opt1Val = right.options[sel - 1].value;
right.options[sel] = new Option(opt1HTML, opt1Val);
right.options[sel - 1] = new Option(optHTML, optVal);
right.options.selectedIndex = sel - 1;
}
},
moveDown: function () {
var right = this.right;
var selectedOptions = right.selectedOptions;
var indices = [];
for (var i = 0; i < selectedOptions.length; i++) {
indices.push(selectedOptions[i].index);
}
var index = phantasus.Util.indexSort(indices, false);
for (var i = 0; i < selectedOptions.length; i++) {
var sel = selectedOptions[index[i]].index;
var optHTML = right.options[sel].innerHTML;
var optVal = right.options[sel].value;
var opt1HTML = right.options[sel + 1].innerHTML;
var opt1Val = right.options[sel + 1].value;
right.options[sel] = new Option(opt1HTML, opt1Val);
right.options[sel + 1] = new Option(optHTML, optVal);
right.options.selectedIndex = sel + 1;
}
}
};
phantasus.factorizeColumn = function (vector) {
var self = this;
this.v = vector;
if (vector.isFactorized()) {
this.values = vector.getFactorLevels();
} else {
this.values = phantasus.VectorUtil.getSet(vector).values();
}
var tooltipHelp = 'Drag items. Use Ctrl+Click or Shift+Click to select multiple items';
var valuesHTML = this.values.map(function (value) {
return '<li >' + value + '</li>'
}).join('');
this.$dialog = $('<div style="background:white;" title="' + phantasus.factorizeColumn.prototype.toString() + '"></div>');
this.$el = $([
'<div class="container-fluid" style="height: 100%">',
' <div class="row" style="height: 100%">',
' <div class="col-xs-12" data-name="selector" style="height: 100%">',
' <div class="form-group" style="height: 100%">',
' <label>Values<div style="padding-left: 5px;" class="fa fa-question-circle" data-toggle="tooltip" title="' + tooltipHelp + '"></div></label>',
' <ul class="sortable-list">' + valuesHTML + '</ul>',
' </div>',
' </div>',
' </div>',
'</div>',
'</div>'].join('')
);
this.selector = this.$el.find('.sortable-list');
this.selector.multisortable({
delay: 150
});
this.$el.appendTo(this.$dialog);
this.$el.find('[data-toggle="tooltip"]').tooltip({});
this.$dialog.dialog({
open: function (event, ui) {
$(this).css('overflow', 'visible');
},
close: function (event, ui) {
self.$dialog.dialog('destroy').remove();
event.stopPropagation();
},
buttons: {
'Apply': function () {
var newValues = self.selector.find('li').map(function(){
return $(this).text();
}).get();
vector.factorize(newValues);
self.$dialog.dialog('destroy').remove();
},
'Reset': function () {
vector.defactorize();
self.$dialog.dialog('destroy').remove();
},
'Cancel': function () {
self.$dialog.dialog('destroy').remove();
}
},
resizable: false,
height: 400,
width: 600
});
};
phantasus.factorizeColumn.prototype = {
toString: function () {
return "Change sort order";
}
};
/**
*
* @param options.fileCallback Callback when file is selected
* @param options.optionsCallback Callback when preloaded option is selected
* @constructor
*/
phantasus.FilePicker = function (options) {
var html = [];
html.push('<div>');
var myComputer = _.uniqueId('phantasus');
var url = _.uniqueId('phantasus');
var googleId = _.uniqueId('phantasus');
var preloaded = _.uniqueId('phantasus');
html.push('<ul style="margin-bottom:10px;" class="nav nav-pills phantasus">');
html.push('<li role="presentation" class="active"><a href="#' + myComputer + '"' +
' aria-controls="' + myComputer + '" role="tab" data-toggle="tab"><i class="fa fa-desktop"></i>' +
' My Computer</a></li>');
html.push('<li role="presentation"><a href="#' + url + '"' +
' aria-controls="' + url + '" role="tab" data-toggle="tav"><i class="fa fa-link"></i>' +
' URL</a></li>');
if (typeof gapi !== 'undefined') {
html.push('<li role="presentation"><a href="#' + googleId + '"' +
' aria-controls="' + googleId + '" role="tab" data-toggle="tab"><i class="fa' +
' fa-google"></i>' +
' Google</a></li>');
}
var $sampleDatasetsEl = $('<div class="phantasus-preloaded"></div>');
if (navigator.onLine) {
html.push('<li role="presentation"><a href="#' + preloaded + '"' +
' aria-controls="' + preloaded + '" role="tab" data-toggle="tab"><i class="fa fa-database"></i>' +
' Preloaded Datasets</a></li>');
// lazy load
new phantasus.SampleDatasets({
$el: $sampleDatasetsEl,
show: true,
callback: function (heatMapOptions) {
options.optionsCallback(heatMapOptions);
}
});
}
html.push('</ul>');
html.push('<div class="tab-content"' +
' style="text-align:center;cursor:pointer;height:300px;">');
html.push('<div role="tabpanel" class="tab-pane active" id="' + myComputer + '">');
html.push('<div data-name="drop" class="phantasus-file-drop phantasus-landing-panel">');
html.push('<button class="btn btn-default"><span class="fa-stack"><i' +
' class="fa fa-file-o' +
' fa-stack-2x"></i> <i class="fa fa-plus fa-stack-1x"></i></span> Select File</button>' +
' <div style="padding-top:10px;">or Copy and Paste Clipboard Data, <span' +
' class="phantasus-drag-text">Drag and' +
' Drop</span></div>');
html.push('<input name="hiddenFile" style="display:none;" type="file">');
html.push('</div>');
html.push('</div>');
html.push('<div role="tabpanel" class="tab-pane" id="' + url + '">');
html.push('<div class="phantasus-landing-panel">');
html.push('<input name="url" placeholder="Enter a URL" class="form-control"' +
' style="display:inline;max-width:400px;' +
' type="text"><button name="openUrl" class="btn btn-default"' +
' type="button">Go</button>');
html.push('</div>');
html.push('</div>');
if (typeof gapi !== 'undefined') {
html.push('<div role="tabpanel" class="tab-pane" id="' + googleId + '">');
html.push('<div class="phantasus-landing-panel">');
html.push('<button name="google" class="btn btn-default">Browse Google Drive</button>');
html.push('</div>');
html.push('</div>');
}
if (navigator.onLine) {
html.push('<div role="tabpanel" class="tab-pane" id="' + preloaded + '">');
html.push('<div class="phantasus-landing-panel">');
html.push('</div>');
html.push('</div>');
}
html.push('</div>'); // tab-content
html.push('</div>');
var $el = $(html.join(''));
$sampleDatasetsEl.appendTo($el.find('#' + preloaded + ' > .phantasus-landing-panel'));
this.$el = $el;
var $file = $el.find('[name=hiddenFile]');
var $myComputer = $el.find('[id=' + myComputer + ']');
this.$el.find('.nav').on('click', 'li > a', function (e) {
e.preventDefault();
$(this).tab('show');
});
var $url = $el.find('[name=url]');
$url.on('keyup', function (evt) {
if (evt.which === 13) {
var text = $.trim($(this).val());
if (text !== '') {
options.fileCallback(text);
}
}
});
$el.find('[name=openUrl]').on('click', function (evt) {
var text = $.trim($url.val());
if (text !== '') {
options.fileCallback(text);
}
});
var $google = $el.find('[name=google]');
$google.on('click', function () {
var developerKey = 'AIzaSyBCRqn5xgdUsJZcC6oJnIInQubaaL3aYvI';
var clientId = '936482190815-85k6k06b98ihv272n0b7f7fm33v5mmfa.apps.googleusercontent.com';
var scope = ['https://www.googleapis.com/auth/drive'];
var oauthToken;
var pickerApiLoaded = false;
var oauthToken;
// Use the API Loader script to load google.picker and gapi.auth.
function onApiLoad() {
gapi.load('auth', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
function onAuthApiLoad() {
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
// Create and render a Picker object for picking user Photos.
function createPicker() {
if (pickerApiLoaded && oauthToken) {
var picker = new google.picker.PickerBuilder().addView(google.picker.ViewId.DOCS)
.setOAuthToken(oauthToken)
.setDeveloperKey(developerKey)
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
$('.picker-dialog-bg').css('z-index', 1052); // make it appear above modals
$('.picker-dialog').css('z-index', 1053);
}
}
function pickerCallback(data) {
if (data.action == google.picker.Action.PICKED) {
var file = data.docs[0];
var fileName = file.name;
var accessToken = gapi.auth.getToken().access_token;
var xhr = new XMLHttpRequest();
var url = new String('https://www.googleapis.com/drive/v3/files/' + file.id + '?alt=media');
url.name = fileName;
url.headers = {'Authorization': 'Bearer ' + accessToken};
options.fileCallback(url);
}
}
onApiLoad();
});
$file.on('change', function (evt) {
var files = evt.target.files; // FileList object
for (var i = 0; i < files.length; i++) {
options.fileCallback(files[i]);
}
});
$(window).on('paste.phantasus', this.paste = function (e) {
if ($myComputer.is(':visible')) {
var text = e.originalEvent.clipboardData.getData('text/plain');
if (text != null && text.length > 0) {
e.preventDefault();
e.stopPropagation();
var url;
if (text.indexOf('http') === 0) {
url = text;
} else {
var blob = new Blob([text]);
url = new String(window.URL.createObjectURL(blob));
url.name = 'clipboard';
}
options.fileCallback(url);
}
}
});
var $drop = $el.find('[data-name=drop]');
var _this = this;
$el.on('remove', function () {
$(window).off(_this.paste).off(_this.dragover).off(_this.dragenter).off(_this.dragleave).off(_this.drop);
});
var clicking = false;
$drop.on('click', function (e) {
if (!clicking) {
clicking = true;
$file.click();
clicking = false;
}
// e.preventDefault();
});
$(window).on(
'dragover',
this.dragover = function (e) {
if ($myComputer.is(':visible')) {
$drop.addClass('drag');
e.preventDefault();
e.stopPropagation();
}
}).on(
'dragenter',
this.dragenter = function (e) {
if ($myComputer.is(':visible')) {
$drop.addClass('drag');
e.preventDefault();
e.stopPropagation();
}
}).on('dragleave', this.dragleave = function (e) {
if ($myComputer.is(':visible')) {
$drop.removeClass('drag');
e.preventDefault();
e.stopPropagation();
}
}).on('drop', this.drop = function (e) {
if ($myComputer.is(':visible')) {
$drop.removeClass('drag');
if (e.originalEvent.dataTransfer) {
if (e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
var files = e.originalEvent.dataTransfer.files;
for (var i = 0; i < files.length; i++) {
options.fileCallback(files[i]);
}
} else {
var url = e.originalEvent.dataTransfer.getData('URL');
options.fileCallback(url);
e.preventDefault();
e.stopPropagation();
}
}
}
});
};
phantasus.FilterUI = function (project, isColumns) {
var _this = this;
this.project = project;
this.isColumns = isColumns;
var $div = $('<div style="min-width:180px;"></div>');
this.$div = $div;
$div.append(this.addBase());
var $filterMode = $div.find('[name=filterMode]');
$filterMode.on('change', function (e) {
var isAndFilter = $filterMode.prop('checked');
(isColumns ? project.getColumnFilter() : project.getRowFilter())
.setAnd(isAndFilter);
isColumns ? _this.project.setColumnFilter(_this.project
.getColumnFilter(), true) : _this.project.setRowFilter(
_this.project.getRowFilter(), true);
e.preventDefault();
});
$div.on('click', '[data-name=add]', function (e) {
var $this = $(this);
var $row = $this.closest('.phantasus-entry');
// add after
var index = $row.index();
var newFilter = new phantasus.AlwaysTrueFilter();
(isColumns ? project.getColumnFilter() : project.getRowFilter())
.insert(index, newFilter);
$row.after(_this.add(newFilter));
e.preventDefault();
});
$div.on('click', '[data-name=delete]', function (e) {
var $this = $(this);
var $row = $this.closest('.phantasus-entry');
var index = $row.index() - 1;
(isColumns ? project.getColumnFilter() : project.getRowFilter())
.remove(index);
$row.remove();
isColumns ? _this.project.setColumnFilter(_this.project
.getColumnFilter(), true) : _this.project.setRowFilter(
_this.project.getRowFilter(), true);
e.preventDefault();
});
$div.on('submit', 'form', function (e) {
var $this = $(this);
e.preventDefault();
});
$div.on('change', '[name=by]', function (e) {
var $this = $(this);
var fieldName = $this.val();
var $row = $this.closest('.phantasus-entry');
var index = $row.index() - 1;
if (fieldName == '') {
$row.find('[data-name=ui]').empty();
} else {
_this.createFilter({
fieldName: fieldName,
$div: $this
});
}
isColumns ? _this.project.setColumnFilter(_this.project
.getColumnFilter(), true) : _this.project.setRowFilter(
_this.project.getRowFilter(), true);
});
// show initial filters
var combinedFilter = (isColumns ? project.getColumnFilter() : project
.getRowFilter());
var filters = combinedFilter.getFilters ? combinedFilter.getFilters() : [];
for (var i = 0; i < filters.length; i++) {
this.createFilter({
filter: filters[i]
});
}
if (combinedFilter.on) {
combinedFilter.on('add', function (e) {
_this.createFilter({
filter: e.filter
});
});
combinedFilter.on('remove', function (e) {
// offset of 1 for header
var $row = $div.find('.phantasus-entry')[1 + e.index].remove();
});
combinedFilter.on('and', function (e) {
$filterMode.prop('checked', e.source.isAnd());
});
}
};
phantasus.FilterUI.rangeFilter = function (project, name, isColumns, $ui, filter) {
$ui.empty();
var html = [];
html.push('<label>Range of values</label><br />');
html
.push('<div style="display:inline-block"><label>>= </label> <input style="max-width:100px;" class="form-control input-sm" name="min" type="text" /></div>');
html
.push('<div style="display:inline-block; margin-left: 5px;"><label> and <= </label> <input style="max-width:100px;" class="form-control input-sm" name="max" type="text" /></div>');
html.push('<br /><a data-name="switch" href="#">Switch to top filter</a>');
var $form = $(html.join(''));
$form.appendTo($ui);
$ui.find('[data-name=switch]')
.on(
'click',
function (e) {
e.preventDefault();
var newFilter = phantasus.FilterUI.topFilter(project,
name, isColumns, $ui);
var index = -1;
var filters = isColumns ? project.getColumnFilter()
.getFilters() : project.getRowFilter()
.getFilters();
for (var i = 0; i < filters.length; i++) {
if (filters[i] === filter) {
index = i;
break;
}
}
if (index === -1) {
throw new Error('Filter not found.');
}
(isColumns ? project.getColumnFilter() : project
.getRowFilter()).set(index, newFilter);
isColumns ? project.setColumnFilter(project
.getColumnFilter(), true) : project
.setRowFilter(project.getRowFilter(), true);
});
var $min = $ui.find('[name=min]');
var $max = $ui.find('[name=max]');
if (!filter) {
filter = new phantasus.RangeFilter(-Number.MAX_VALUE, Number.MAX_VALUE,
name, isColumns);
} else {
$min.val(filter.min);
$max.val(filter.max);
}
$min.on('keyup', _.debounce(function (e) {
filter.setMin(parseFloat($.trim($(this).val())));
isColumns ? project.setColumnFilter(project.getColumnFilter(), true)
: project.setRowFilter(project.getRowFilter(), true);
}, 500));
$max.on('keyup', _.debounce(function (e) {
filter.setMax(parseFloat($.trim($(this).val())));
isColumns ? project.setColumnFilter(project.getColumnFilter(), true)
: project.setRowFilter(project.getRowFilter(), true);
}, 500));
return filter;
};
phantasus.FilterUI.topFilter = function (project, name, isColumns, $ui, filter) {
$ui.empty();
var html = ['<label>Direction: </label>',
'<select class="form-control input-sm phantasus-filter-input" name="direction">',
'<option value="Top">Top</option>',
'<option value="Bottom">Bottom</option>',
'<option value="TopBottom">Top/Bottom</option>',
'</select>',
'<label>Amount:</label>',
'<input class="form-control input-sm phantasus-filter-input" name="n" type="text" />',
'<br /><a data-name="switch" href="#">Switch to range filter</a>'];
var $form = $(html.join(''));
$form.appendTo($ui);
var $n = $ui.find('[name=n]');
var $direction = $ui.find('[name=direction]');
$ui.find('[data-name=switch]')
.on(
'click',
function (e) {
e.preventDefault();
var newFilter = phantasus.FilterUI.rangeFilter(project,
name, isColumns, $ui);
var index = -1;
var filters = isColumns ? project.getColumnFilter()
.getFilters() : project.getRowFilter()
.getFilters();
for (var i = 0; i < filters.length; i++) {
if (filters[i] === filter) {
index = i;
break;
}
}
if (index === -1) {
throw new Error('Filter not found.');
}
(isColumns ? project.getColumnFilter() : project
.getRowFilter()).set(index, newFilter);
isColumns ? project.setColumnFilter(project
.getColumnFilter(), true) : project
.setRowFilter(project.getRowFilter(), true);
});
if (!filter) {
filter = new phantasus.TopNFilter(NaN, phantasus.TopNFilter.TOP, name, isColumns);
} else {
var dirVal;
if (filter.direction === phantasus.TopNFilter.TOP) {
dirVal = 'Top';
} else if (filter.direction === phantasus.TopNFilter.BOTTOM) {
dirVal = 'Bottom';
} else {
dirVal = 'TopBottom';
}
$direction.val(dirVal);
$n.val(filter.n);
}
$direction.on('change', function () {
var dir = $(this).val();
var dirVal;
if (dir === 'Top') {
dirVal = phantasus.TopNFilter.TOP;
} else if (dir === 'Bottom') {
dirVal = phantasus.TopNFilter.BOTTOM;
} else {
dirVal = phantasus.TopNFilter.TOP_BOTTOM;
}
filter.setDirection(dirVal);
isColumns ? project.setColumnFilter(project.getColumnFilter(), true)
: project.setRowFilter(project.getRowFilter(), true);
});
$n.on('keyup', _.debounce(function (e) {
filter.setN(parseInt($.trim($(this).val())));
isColumns ? project.setColumnFilter(project.getColumnFilter(), true)
: project.setRowFilter(project.getRowFilter(), true);
}, 500));
return filter;
};
phantasus.FilterUI.prototype = {
/**
*
* @param options
* options.$div div to add filter to or null to add to end
* options.filter Pre-existing filter or null to create filter
* options.fieldName Field name to filter on
*/
createFilter: function (options) {
var index = -1;
var $div = options.$div;
var isColumns = this.isColumns;
var filter = options.filter;
var project = this.project;
var fieldName = filter ? filter.name : options.fieldName;
var $ui;
if (!$div) {
// add filter to end
var $add = $(this.add(filter));
$add.appendTo(this.$div);
$ui = $add.find('[data-name=ui]');
} else { // existing $div
var $row = $div.closest('.phantasus-entry');
index = $row.index() - 1;
$ui = $row.find('[data-name=ui]');
}
$ui.empty();
var vector = (isColumns ? this.project.getFullDataset()
.getColumnMetadata() : this.project.getFullDataset()
.getRowMetadata()).getByName(fieldName);
if (filter instanceof phantasus.RangeFilter) {
phantasus.FilterUI.rangeFilter(project, fieldName, isColumns, $ui,
filter);
} else if (filter instanceof phantasus.TopNFilter) {
phantasus.FilterUI.topFilter(project, fieldName, isColumns, $ui,
filter);
} else if (filter == null && phantasus.VectorUtil.isNumber(vector)
&& phantasus.VectorUtil.containsMoreThanNValues(vector, 9)) {
filter = phantasus.FilterUI.rangeFilter(project, fieldName,
isColumns, $ui, filter);
} else {
var set = phantasus.VectorUtil.getSet(vector);
var array = set.values();
array.sort(phantasus.SortKey.ASCENDING_COMPARATOR);
array = array.map(function (item) {
if (item === '') {
return {valueOf: function () { return ''; }, toString: function () { return '(None)'; }};
} else if (item === null || item === undefined) {
return {valueOf: function () { return item }, toString: function () { return '(NULL)'; }};
}
return item;
});
if (!filter) {
filter = new phantasus.VectorFilter(new phantasus.Set(), set
.size(), fieldName, isColumns);
} else {
filter.maxSetSize = array.length;
}
var checkBoxList = new phantasus.CheckBoxList({
responsive: false,
$el: $ui,
items: array,
set: filter.set
});
checkBoxList.on('checkBoxSelectionChanged', function () {
isColumns ? project.setColumnFilter(project.getColumnFilter(),
true) : project.setRowFilter(project.getRowFilter(),
true);
});
}
if (index !== -1) {
// set the filter index
if (fieldName !== '') {
(isColumns ? project.getColumnFilter() : project.getRowFilter())
.set(index, filter);
} else {
(isColumns ? project.getColumnFilter() : project.getRowFilter())
.set(index, new phantasus.AlwaysTrueFilter());
}
}
return filter;
},
addBase: function () {
var html = [];
html
.push('<div style="padding-bottom:2px;border-bottom:1px solid #eee" class="phantasus-entry">');
html.push('<div class="row">');
html
.push('<div class="col-xs-12">'
+ '<div class="checkbox"><label><input type="checkbox" name="filterMode">Pass all filters</label></div> '
+ '</div>');
html.push('</div>');
html.push('<div class="row">');
html
.push('<div class="col-xs-8"><a class="btn btn-default btn-xs" role="button"' +
' data-name="add" href="#">Add</a></div>');
html.push('</div>');
html.push('</div>');
return html.join('');
},
add: function (filter) {
var project = this.project;
var isColumns = this.isColumns;
var fields = phantasus.MetadataUtil.getMetadataNames(isColumns ? project
.getFullDataset().getColumnMetadata() : project
.getFullDataset().getRowMetadata());
var html = [];
html.push('<div class="phantasus-entry">');
html.push('<div class="form-group" style="margin-bottom: 0px;">');
html.push('<label>Field:</label>');
// field
html.push('<select style="max-width:150px;overflow-x:hidden; display: inline-block; margin: 5px; padding: 5px; line-height: normal; height: auto;" name="by" class="form-control input-sm">');
html.push('<option disabled selected value style="display: none">--select field--</option>');
var filterField = filter ? filter.toString() : null;
_.each(fields, function (field) {
html.push('<option value="' + field + '"');
if (field === filterField) {
html.push(' selected');
}
html.push('>');
html.push(field);
html.push('</option>');
});
html.push('</select>');
html.push('</div>');
html.push('<div class="row">');
// filter ui
html.push('<div data-name="ui" class="col-xs-12"></div>');
html.push('</div>');
// end filter ui
// add/delete
html
.push('<div style="padding-bottom:6px; border-bottom:1px solid #eee" class="row">');
html.push('<div class="col-xs-11">');
html
.push('<a class="btn btn-default btn-xs" role="button" data-name="delete"' +
' href="#">Remove</a>');
html.push('</div>');
html.push('</div>'); // row
html.push('</div>'); // phantasus-entry
return html.join('');
}
};
/**
*
* @param options.fontModel
* @param options.track
* @param options.heatMap
* @constructor
*/
phantasus.FontChooser = function (options) {
var _this = this;
var fontModel = options.fontModel;
var track = options.track;
var heatMap = options.heatMap;
// ensure map exists
fontModel.getMappedValue(track.getVector(track.settings.fontField), track.getVector(track.settings.fontField).getValue(0));
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
value: track.settings.fontField != null,
type: 'checkbox',
name: 'use_another_annotation_to_determine_font'
});
var annotationNames = phantasus.MetadataUtil.getMetadataNames(
track.isColumns ? heatMap.getProject().getFullDataset().getColumnMetadata() : heatMap.getProject().getFullDataset().getRowMetadata());
annotationNames.splice(annotationNames.indexOf(track.getName()), 1);
formBuilder.append({
name: 'annotation_name',
type: 'bootstrap-select',
options: annotationNames,
search: annotationNames.length > 10,
value: track.settings.fontField
});
formBuilder.setVisible('annotation_name', track.settings.fontField != null);
formBuilder.append({
name: 'selected_value',
type: 'bootstrap-select',
search: true,
options: fontModel.getMap(track.settings.fontField != null ? track.settings.fontField : track.getName()).keys()
});
var $selectedValue = formBuilder.find('selected_value');
formBuilder.append({
name: 'selected_font',
type: 'bootstrap-select',
options: [{name: 'normal', value: 400}, {name: 'bold', value: 700}, {name: 'bolder', value: 900}]
});
var repaint = function () {
track.setInvalid(true);
track.repaint();
};
formBuilder.find('use_another_annotation_to_determine_font').on('change', function () {
var checked = $(this).prop('checked');
formBuilder.setValue('annotation_name', null);
formBuilder.setValue('selected_value', null);
formBuilder.setVisible('annotation_name', checked);
if (!checked) {
track.settings.fontField = null;
}
repaint();
});
formBuilder.find('annotation_name').on('change', function () {
var annotationName = $(this).val();
fontModel.getMappedValue(track.getVector(annotationName), track.getVector(annotationName).getValue(0));
track.settings.fontField = annotationName;
// ensure map exists
formBuilder.setOptions('selected_value', fontModel.getMap(track.settings.fontField != null ? track.settings.fontField : track.getName()).keys());
formBuilder.setValue('selected_value', null);
repaint();
});
var $selectedFont = formBuilder.find('selected_font');
$selectedFont.on('change', function (e) {
fontModel.setMappedValue(track.getVector(track.settings.fontField), $selectedValue.val(), {weight: $(this).val()});
repaint();
});
var updateMappedValue = function () {
var selectedVal = $selectedValue.val();
var mappedValue = fontModel.getMappedValue(track.getVector(track.settings.fontField), selectedVal);
formBuilder.setValue('selected_font', mappedValue.weight);
};
$selectedValue.on('change', function () {
// update displayed value
updateMappedValue();
});
updateMappedValue();
this.$div = formBuilder.$form;
};
phantasus.Util.extend(phantasus.FontChooser, phantasus.Events);
phantasus.FormBuilder = function (options) {
var _this = this;
this.prefix = _.uniqueId('form');
this.$form = $('<form></form>');
this.$form.attr('role', 'form').attr('id', this.prefix);
this.formStyle = options == null || options.formStyle == null ? 'horizontal' : options.formStyle;
this.$form.addClass('phantasus');
if (this.formStyle === 'horizontal') {
this.titleClass = 'col-xs-12 control-label';
this.labelClass = 'col-xs-4 control-label';
this.$form.addClass('form-horizontal');
} else if (this.formStyle === 'vertical') {
this.labelClass = 'control-label';
this.titleClass = 'control-label';
} else if (this.formStyle === 'inline') {
this.titleClass = '';
this.labelClass = '';
this.$form.addClass('form-inline');
}
this.$form.on('submit', function (e) {
e.preventDefault();
});
this.$form.on(
'dragover',
function (e) {
var node = $(e.originalEvent.srcElement).parent().parent()
.prev();
if (node.is('select') && node.hasClass('file-input')) {
$(e.originalEvent.srcElement).parent().css('border',
'1px solid black');
e.preventDefault();
e.stopPropagation();
}
}).on(
'dragenter',
function (e) {
var node = $(e.originalEvent.srcElement).parent().parent()
.prev();
if (node.is('select') && node.hasClass('file-input')) {
$(e.originalEvent.srcElement).parent().css('border',
'1px solid black');
e.preventDefault();
e.stopPropagation();
}
}).on('dragleave', function (e) {
var node = $(e.originalEvent.srcElement).parent().parent().prev();
if (node.is('select') && node.hasClass('file-input')) {
$(e.originalEvent.srcElement).parent().css('border', '');
e.preventDefault();
e.stopPropagation();
}
}).on('drop', function (e) {
var node = $(e.originalEvent.srcElement).parent().parent().prev();
if (node.is('select') && node.hasClass('file-input')) {
var isMultiple = node.data('multiple'); // multiple files?
$(e.originalEvent.srcElement).parent().css('border', '');
var name = node.attr('name');
name = name.substring(0, name.length - '_picker'.length);
if (e.originalEvent.dataTransfer) {
if (e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
var files = e.originalEvent.dataTransfer.files;
_this.setValue(name, isMultiple ? files : files[0]);
_this.trigger('change', {
name: name,
value: files[0]
});
} else {
var url = e.originalEvent.dataTransfer.getData('URL');
e.preventDefault();
e.stopPropagation();
_this.setValue(name, isMultiple ? [url] : url);
_this.trigger('change', {
name: name,
value: url
});
}
}
}
});
// this.labelColumnDef = '4';
// this.fieldColumnDef = '8';
};
phantasus.FormBuilder.showProgressBar = function (options) {
var content = [];
content.push('<div class="container-fluid">');
content.push('<div class="row">');
content.push('<div class="col-xs-8">');
content
.push(
'<div class="progress progress-striped active"><div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div></div>');
content.push('</div>'); // col
content.push('<div class="col-xs-2">');
content
.push('<input class="btn btn-default" type="button" name="stop" value="Cancel">');
content.push('</div>'); // col
content.push('</div>'); // row
if (options.subtitle) {
content.push('<div class="row"><div class="col-xs-8">');
content.push('<p class="text-muted">');
content.push(options.subtitle);
content.push('</p>');
content.push('</div></div>');
}
content.push('</div>');
var $content = $(content.join(''));
$content.find('[name=stop]').on('click', function (e) {
options.stop();
e.preventDefault();
});
return phantasus.FormBuilder.showInDraggableDiv({
title: options.title,
$content: $content
});
};
phantasus.FormBuilder.showInDraggableDiv = function (options) {
var width = options.width || '300px';
var html = [];
html
.push('<div style="z-index: 1050; top: 100px; position:absolute; padding-left:10px; padding-right:10px; width:'
+ width
+ ' ; background:white; box-shadow: 0 5px 15px rgba(0,0,0,0.5); border: 1px solid rgba(0,0,0,0.2); border-radius: 6px;">');
if (options.title != null) {
html
.push('<h4 style="cursor:move; border-bottom: 1px solid #e5e5e5;" name="header">'
+ options.title + '</h4>');
}
html.push('<div name="content"></div>');
html.push('</div>');
var $div = $(html.join(''));
var $content = $div.find('[name=content]');
$div.find('[name=header]').on('dblclick', function () {
if ($content.css('display') === 'none') {
$content.css('display', '');
} else {
$content.css('display', 'none');
}
});
options.$content.appendTo($content);
$div.css('left', ($(window).width() / 2) - $content.outerWidth() / 2);
$div.draggable({
//handle: '[name=header]',
containment: 'document'
});
// $div.resizable();
$div.appendTo(options.appendTo != null ? options.appendTo : $(document.body));
return $div;
};
phantasus.FormBuilder.showMessageModal = function (options) {
var $div = phantasus.FormBuilder
._showInModal({
modalClass: options.modalClass,
title: options.title,
html: options.html,
footer: ('<button type="button" class="btn btn-default"' +
' data-dismiss="modal">OK</button>'),
backdrop: options.backdrop,
size: options.size,
focus: options.focus,
appendTo: options.appendTo
});
$div.find('button').focus();
return $div;
// if (options.draggable) {
// $div.draggable({
// handle : $div.find(".modal-header")
// });
// }
};
phantasus.FormBuilder._showInModal = function (options) {
var html = [];
options = $.extend({}, {
size: '',
close: true,
modalClass: ''
}, options);
html.push('<div tabindex="-1" class="modal' + (options.modalClass ? (' ' + options.modalClass) : '') + '" role="dialog"' +
' aria-hidden="false"');
if (options.z) {
html.push(' style="z-index: ' + options.z + ' !important;"');
}
html.push('>');
html.push('<div class="modal-dialog ' + options.size + '">');
html.push('<div class="modal-content">');
html.push(' <div class="modal-header">');
if (options.close) {
html
.push(' <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>');
}
if (options.title != null) {
html.push('<h4 class="modal-title">' + options.title + '</h4>');
}
html.push('</div>');
html.push('<div class="modal-body">');
html.push('</div>');
if (options.footer) {
html.push('<div class="modal-footer">');
html.push(options.footer);
}
html.push('</div>');
html.push('</div>');
html.push('</div>');
html.push('</div>');
var $div = $(html.join(''));
$div.on('mousewheel', function (e) {
e.stopPropagation();
});
$div.find('.modal-body').html(options.html);
$div.prependTo(options.appendTo != null ? options.appendTo : $(document.body));
$div.modal({
keyboard: true,
backdrop: options.backdrop === true ? true : false
}).on('hidden.bs.modal', function (e) {
$div.remove();
if (options.onClose) {
options.onClose();
}
if (options.focus) {
$(options.focus).focus();
}
});
return $div;
};
/**
*
* @param options.z Modal z-index
* @param options.title Modal title
* @param options.html Model content
* @param options.close Whether to show a close button in the footer
* @param options.onClose {Function} Funtion to invoke when modal is hidden
* @param options.backdrop Whether to show backdrop
* @param.options Modal size
* @param options.focus Element to return focus to when modal is hidden
* @param options.modalClass
*/
phantasus.FormBuilder.showInModal = function (options) {
return phantasus.FormBuilder
._showInModal({
modalClass: options.modalClass,
title: options.title,
html: options.html,
footer: options.close ? ('<button type="button" class="btn btn-default" data-dismiss="modal">'
+ options.close + '</button>')
: null,
onClose: options.onClose,
appendTo: options.appendTo,
backdrop: options.backdrop,
size: options.size,
focus: options.focus,
z: options.z // was used before yet dissappeared
});
// if (options.draggable) {
// $div.draggable({
// handle : $div.find(".modal-header")
// });
// }
};
/**
*
* @param options.ok
* @param options.cancel
* @param options.apply
* @param options.title
* @param options.content
* @param options.okCallback
* @param options.cancelCallba
* @param options.okFocus
*
*/
phantasus.FormBuilder.showOkCancel = function (options) {
options = $.extend({}, {
ok: true,
cancel: true
}, options);
var footer = [];
if (options.ok) {
footer
.push('<button name="ok" type="button" class="btn btn-default">OK</button>');
}
if (options.apply) {
footer
.push('<button name="apply" type="button" class="btn btn-default">Apply</button>');
}
if (options.cancel) {
footer
.push('<button name="cancel" type="button" data-dismiss="modal" class="btn btn-default">Cancel</button>');
}
var $div = phantasus.FormBuilder._showInModal({
title: options.title,
html: options.content,
footer: footer.join(''),
size: options.size,
close: options.close,
onClose: options.onClose,
focus: options.focus,
appendTo: options.appendTo
});
// if (options.align === 'right') {
// $div.css('left', $(window).width()
// - $div.find('.modal-content').width() - 60);
// }
var $ok = $div.find('[name=ok]');
$ok.on('click', function (e) {
if (options.okCallback) {
options.okCallback();
}
$div.modal('hide');
});
$div.find('[name=cancel]').on('click', function (e) {
if (options.cancelCallback) {
options.cancelCallback();
}
$div.modal('hide');
});
if (options.okFocus) {
$ok.focus();
}
if (options.draggable) {
$div.draggable({
handle: '.modal-header',
containment: 'document'
});
}
return $div;
};
phantasus.FormBuilder.hasChanged = function (object, keyToUIElement) {
var keys = _.keys(keyToUIElement);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = object[key];
var $element = keyToUIElement[key];
if (value !== phantasus.FormBuilder.getValue($element)) {
return true;
}
}
return false;
};
phantasus.FormBuilder.getValue = function ($element) {
var list = $element.data('phantasus.checkbox-list');
if (list != null) {
return list.val();
}
if ($element.attr('type') === 'radio') {
return $element.filter(':checked').val();
}
if ($element.data('type') === 'file') {
return $element.data('files');
}
if ($element.data('type') === 'collapsed-checkboxes') {
var result = [];
$element.find('input').each(function (a, checkbox) {
var $checkbox = $(checkbox);
if ($checkbox.prop('checked')) {
result.push($checkbox.prop('name'));
}
});
return result;
}
return $element.attr('type') === 'checkbox' ? $element.prop('checked') : $element.val();
};
phantasus.FormBuilder.prototype = {
appendContent: function ($content) {
this.$form.append($content);
},
addSeparator: function () {
var html = [];
html.push('<div class="form-group">');
if (this.formStyle === 'horizontal') {
html.push('<div class="col-xs-12">');
}
html.push('<hr />');
if (this.formStyle === 'horizontal') {
html.push('</div>');
}
html.push('</div>');
this.$form.append(html.join(''));
},
_append: function (html, field, isFieldStart) {
var _this = this;
var required = field.required;
var name = field.name;
var type = field.type;
if (type == 'separator') {
if (this.formStyle === 'horizontal') {
html.push('<div class="col-xs-12">');
} else {
html.push('<div class="form-group">');
}
html.push('<hr />');
html.push('</div>');
return;
}
var title = field.title;
var disabled = field.disabled;
var help = field.help;
var value = field.value;
var showLabel = field.showLabel;
var tooltipHelp = field.tooltipHelp;
var selectedFormat = field.selectedFormat || 'count';
var style = field.style || '';
var col = '';
var labelColumn = '';
if (this.formStyle === 'horizontal') {
col = field.col || 'col-xs-8';
}
if (showLabel === undefined) {
showLabel = 'checkbox' !== type && 'button' !== type
&& 'radio' !== type;
showLabel = showLabel || field.options !== undefined;
}
var id = _this.prefix + '_' + name;
if (title === undefined) {
title = name.replace(/_/g, ' ');
title = title[0].toUpperCase() + title.substring(1);
}
var endingDiv = false;
if (showLabel) {
html.push('<label for="' + id + '" class="' + this.labelClass
+ '">');
html.push(title);
if (tooltipHelp) {
html.push('<div style="padding-left: 5px;" class="fa fa-question-circle" data-toggle="tooltip" data-placement="top" title="' + tooltipHelp + '"></div>');
}
html.push('</label>');
if (isFieldStart && this.formStyle !== 'inline') {
html.push('<div class="' + col + '">');
endingDiv = true;
}
} else if (isFieldStart && this.formStyle === 'horizontal') { // no label
html.push('<div class="col-xs-offset-4 ' + col + '">');
endingDiv = true;
}
if ('radio' === type) {
if (field.options) {
_.each(field.options,
function (choice) {
var isChoiceObject = _.isObject(choice)
&& choice.value !== undefined;
var optionValue = isChoiceObject ? choice.value
: choice;
var optionText = isChoiceObject ? choice.name
: choice;
var selected = value === optionValue;
html.push('<div class="radio"><label>');
html.push('<input style="' + style + '" value="' + optionValue
+ '" name="' + field.name
+ '" type="radio"');
if (selected) {
html.push(' checked');
}
html.push('> ');
if (choice.icon) {
html.push('<span class="' + choice.icon
+ '"></span> ');
}
optionText = optionText[0].toUpperCase()
+ optionText.substring(1);
html.push(optionText);
html.push('</label></div>');
});
} else {
html.push('<div class="radio"><label>');
html.push('<input style="' + style + '" value="' + value + '" name="' + name
+ '" id="' + id + '" type="radio"');
if (field.checked) {
html.push(' checked');
}
html.push('> ');
html.push(value[0].toUpperCase() + value.substring(1));
html.push('</label></div>');
}
} else if ('collapsed-checkboxes' === type) {
var checkboxes = field.checkboxes;
html.push('<div id="' + id + '" data-name="' + name + '" data-type="collapsed-checkboxes">');
html.push('<button style="' + style + '" type="button" class="btn btn-default btn-sm" data-toggle="collapse" data-target="#' + id + '_collapse">');
if (field.icon) {
html.push('<span class="' + field.icon + '"></span> ');
}
html.push(value ? value : title);
html.push('</button>');
html.push('<div class="collapse" id="' + id + '_collapse">' +
' <div class="well">');
checkboxes.forEach(function (checkbox) {
var name = checkbox.name;
var checkboxId = id + name;
var value = checkbox.value;
var disabled = checkbox.disabled;
var title = checkbox.title || name;
html.push('<div class="checkbox"><label>');
html.push('<input style="' + style + '" name="' + name + '" id="' + checkboxId
+ '" type="checkbox"');
if (value) {
html.push(' checked');
}
if (disabled) {
html.push(' disabled');
}
html.push('> ');
html.push(title);
html.push('</label></div>');
});
html.push('</div></div>');
html.push('</div>');
} else if ('checkbox' === type) {
html.push('<div class="checkbox"><label>');
html.push('<input style="' + style + '" name="' + name + '" id="' + id
+ '" type="checkbox"');
if (value) {
html.push(' checked');
}
if (disabled) {
html.push(' disabled');
}
html.push('> ');
html.push(title);
html.push('</label></div>');
} else if ('checkbox-list' === type) {
html.push('<div name="' + name + '" class="checkbox-list"><div>');
} else if ('triple-select' === type) {
html.push('<h5 style="margin-top: 5px; margin-bottom: 5px;">' + name + ':</h5>');
html.push('<select style="' + field.comboboxStyle + '" name="' + field.firstName + '" id="' + id
+ '" class="form-control">');
_.each(field.firstOptions, function (value, index) {
html.push('<option value="');
html.push(value);
html.push('"');
if (index === 0) {
html.push(' selected');
}
html.push('>');
html.push(value);
html.push('</option>');
});
html.push('</select>');
if (field.firstDivider) {
html.push('<span id="' + name +'-first-divider">' + field.firstDivider + '</span>');
}
html.push('<select style="' + field.comboboxStyle + '" name="' + field.secondName + '" id="' + id
+ '" class="form-control">');
_.each(field.secondOptions, function (value, index) {
html.push('<option value="');
html.push(value);
html.push('"');
if (index === 0) {
html.push(' selected');
}
html.push('>');
html.push(value);
html.push('</option>');
});
html.push('</select>');
if (field.secondDivider) {
html.push('<span id="' + name +'-second-divider">' + field.secondDivider + '</span>');
}
html.push('<select style="' + field.comboboxStyle + '" name="' + field.thirdName + '" id="' + id
+ '" class="form-control">');
_.each(field.thirdOptions, function (value, index) {
html.push('<option value="');
html.push(value);
html.push('"');
if (index === 0) {
html.push(' selected');
}
html.push('>');
html.push(value);
html.push('</option>');
});
html.push('</select>');
} else if ('select' == type || type == 'bootstrap-select') {
// if (field.multiple) {
// field.type = 'bootstrap-select';
// type = 'bootstrap-select';
// }
if (type == 'bootstrap-select') {
html.push('<select style="' + style + '" data-size="5" data-live-search="' + (field.search ? true : false) + '" data-selected-text-format="' + selectedFormat + '" name="'
+ name + '" id="' + id
+ '" data-actions-box="' + (field.selectAll ? true : false) + '" class="selectpicker' + (this.formStyle !== 'inline' ? ' form-control' : '') + '"');
} else {
html.push('<select style="' + style + '" name="' + name + '" id="' + id
+ '" class="form-control"');
}
if (disabled) {
html.push(' disabled');
}
if (field.multiple) {
html.push(' multiple');
}
html.push('>');
_.each(field.options, function (choice) {
if (choice && choice.divider) {
html.push('<option data-divider="true"></option>');
} else {
html.push('<option value="');
var isChoiceObject = _.isObject(choice)
&& choice.value !== undefined;
var optionValue = isChoiceObject ? choice.value : choice;
var optionText = isChoiceObject ? choice.name : choice;
html.push(optionValue);
html.push('"');
var selected = false;
if (_.isObject(value)) {
selected = value[optionValue];
} else if (_.isArray(value)) {
selected = value.indexOf(optionValue) !== -1;
} else {
selected = value == optionValue;
}
if (selected) {
html.push(' selected');
}
html.push('>');
html.push(optionText);
html.push('</option>');
}
});
html.push('</select>');
if (field.type == 'bootstrap-select' && field.toggle) {
html.push('<p class="help-block"><a data-name="' + name
+ '_all" href="#">All</a> | <a data-name="' + name
+ '_none" href="#">None</a></p>');
_this.$form.on('click', '[data-name=' + name + '_all]',
function (evt) {
evt.preventDefault();
var $select = _this.$form
.find('[name=' + name + ']');
$select.selectpicker('val', $.map($select
.find('option'), function (o) {
return $(o).val();
}));
$select.trigger('change');
});
_this.$form.on('click', '[data-name=' + name + '_none]',
function (evt) {
evt.preventDefault();
var $select = _this.$form
.find('[name=' + name + ']');
$select.selectpicker('val', []);
$select.trigger('change');
});
}
} else if ('textarea' == type) {
html.push('<textarea style="' + style + '" id="' + id + '" class="form-control" name="'
+ name + '"');
if (required) {
html.push(' required');
}
if (field.placeholder) {
html.push(' placeholder="' + field.placeholder + '"');
}
if (disabled) {
html.push(' disabled');
}
html.push('>');
if (value != null) {
html.push(value);
}
html.push('</textarea>');
} else if ('button' == type) {
html.push('<button style="' + style + '" id="' + id + '" name="' + name
+ '" type="button" class="btn btn-default btn-sm">');
if (field.icon) {
html.push('<span class="' + field.icon + '"></span> ');
}
html.push(value ? value : title);
html.push('</button>');
} else if ('custom' === type) {
html.push(value);
} else if ('file' === type) {
var isMultiple = field.multiple == null ? false : field.multiple;
html
.push('<select data-multiple="'
+ isMultiple
+ '" data-type="file" title="'
+ (field.placeholder || (isMultiple ? 'Choose one or more files...'
: 'Choose a file...'))
+ '" name="'
+ name
+ '_picker" data-width="35%" class="file-input selectpicker form-control">');
var options = [];
if (field.options) {
options = options.concat(field.options);
}
var allowedInputs = field.allowedInputs || {all: true};
// data types are file, dropbox, url, GEO, preloaded and predefined
if (allowedInputs.all || allowedInputs.computer) options.push('My Computer');
if (allowedInputs.all || allowedInputs.url) options.push('URL');
if (allowedInputs.all || allowedInputs.geo) options.push('GEO Datasets');
if (allowedInputs.all || allowedInputs.saved) options.push('Saved on server datasets');
if (field.text != null) {
options.push(field.text);
}
_.each(options, function (choice, index) {
var isChoiceObject = _.isObject(choice)
&& choice.value !== undefined;
var optionValue = isChoiceObject ? choice.value : choice;
var optionText = isChoiceObject ? choice.name : choice;
html.push('<option value="');
html.push(optionValue);
html.push('"');
if (isChoiceObject && choice.disabled) {
html.push(' disabled');
}
if (optionValue === 'My Computer') {
html.push(' data-icon="fa fa-desktop"');
} else if (optionValue === 'URL') {
html.push(' data-icon="fa fa-external-link"');
} else if (optionValue === 'GEO Datasets') {
html.push(' data-icon="fa fa-external-link"');
} else if (optionValue === 'Saved on server datasets') {
html.push(' data-icon="fa fa-desktop"');
}
html.push('>');
html.push(optionText);
html.push('</option>');
});
html.push('</select>');
html.push('<div>');
html.push('<div id="'+name+'_url"style="display: none">');
html
.push('<input placeholder="'
+ (isMultiple ? 'Enter one or more URLs'
: 'Enter a URL')
+ '" class="form-control" style="width:50%; display:inline-block;" type="text" name="'
+ name + '_url">');
html.push('<input type="submit" style="margin-left: 10px;" class="btn button-default" value="Load">');
html.push('</div>');
/* if (field.preloadedExists) {
html
.push('<input placeholder="'
+ 'Enter a name of preloaded dataset this server provides them'
+ '" class="form-control" style="width:50%; display:none;" type="text" name="'
+ name + '_pre">');
}*/
if (field.gse !== false) {
html.push('<div id="'+name+'_geo" style="display: none">');
html
.push('<input placeholder="'
+ "Enter a GSE or GDS identifier (e.g. GSE53986)"
+ '" class="form-control" style="width:50%; display:inline-block;" type="text" name="'
+ name + '_geo">');
html.push('<input type="submit" style="margin-left: 10px;" class="btn button-default" value="Load">');
html.push('</div>');
}
if (field.text) {
html
.push('<input class="form-control" style="width:50%; display:none;" type="text" name="'
+ name + '_text">');
}
html.push('<div id="'+name+'_pre" style="display: none">');
html
.push('<input placeholder="'
+ 'Enter a dataset name here'
+ '" class="form-control" style="width:50%; display:inline-block;" type="text" name="'
+ name + '_pre">');
html.push('<input type="submit" style="margin-left: 10px;" class="btn button-default" value="Load">');
html.push('</div>');
html.push('</div>');
html.push('<input style="display:none;" type="file" name="' + name
+ '_file"' + (isMultiple ? ' multiple' : '') + '>');
// browse button clicked
// select change
_this.$form
.on(
'change',
'[name=' + name + '_picker]',
function (evt) {
var $this = $(this);
var val = $this.val();
var showUrlInput = val === 'URL';
var showGSEInput = val === 'GEO Datasets';
var showTextInput = val === field.text;
var showPreInput = val === 'Saved on server datasets';
if ('My Computer' === val) {
_this.$form.find('[name=' + name + '_file]')
.click();
_this.$form.find('[name=' + name + '_picker]').selectpicker('val', '');
}
_this.$form.find('#' + name + '_url')
.css('display', showUrlInput ? 'block' : 'none');
_this.$form.find('[name=' + name + '_text]')
.css('display', showTextInput ? 'block' : 'none');
_this.$form.find('#' + name + '_geo')
.css('display', showGSEInput ? 'block' : 'none');
_this.$form.find('#' + name + '_pre')
.css('display', showPreInput ? 'block' : 'none');
});
// URL
var URL_dispatcher = function (form) {
var $div = form.find('#' + name + '_url');
var $input = form.find('[name=' + name + '_url]');
var text = $.trim($input.val());
if (isMultiple) {
text = text.split(',').filter(function (t) {
t = $.trim(t);
return t !== '';
});
}
_this.trigger('change', {
name: name,
value: text
});
$input.val('');
$div.css('display', 'none');
};
//??
_this.$form.on('keyup', '[name=' + name + '_text]', function (evt) {
var text = $.trim($(this).val());
_this.setValue(name, text);
if (evt.which === 13) {
_this.trigger('change', {
name: name,
value: text
});
}
});
// GEO
var geo_dispatcher = function (form) {
var $div = form.find('#' + name + '_geo');
var $input = form.find('[name=' + name + '_geo]');
var text = $.trim($input.val());
// console.log('environment', evt);
// console.log('object to trigger with result', _this, 'name', name, 'text', text);
_this.trigger('change', {
name: name,
value: {
name: text.toUpperCase(),
isGEO: true
}
});
$input.val('');
$div.css('display', 'none');
};
// Preloaded
var PRE_dispatcher = function (form) {
var $div = form.find('#' + name + '_pre');
var $input = form.find('[name=' + name + '_pre]');
var text = $.trim($input.val());
// console.log('environment', evt);
//console.log('object to trigger with result', _this, 'name', name, 'text', text);
_this.trigger('change', {
name: name,
value: {
name: text,
preloaded: true
}
});
$input.val('');
$div.css('display', 'none');
};
// browse file selected
_this.$form.on('change', '[name=' + name + '_file]', function (evt) {
var files = evt.target.files; // FileList object
_this.setValue(name, isMultiple ? files : files[0]);
_this.trigger('change', {
name: name,
value: isMultiple ? files : files[0]
});
});
//SUBMIT
_this.$form.on('submit', function () {
var typePicker = $(this).find('[name=' + name + '_picker]');
var val = typePicker.val(); //many
var showUrlInput = val === 'URL';
var showGSEInput = val === 'GEO Datasets';
var showPreInput = val === 'Saved on server datasets';
if (showGSEInput) geo_dispatcher($(this));
if (showUrlInput) URL_dispatcher($(this));
if (showPreInput) PRE_dispatcher($(this));
});
} else {
type = type == null ? 'text' : type;
if (type === 'div') {
html.push('<div name="' + name + '" id="' + id + '"');
} else {
html.push('<input style="' + style + '" type="' + type
+ '" class="form-control" name="' + name + '" id="'
+ id + '"');
}
if (value != null) {
html.push(' value="' + value + '"');
}
if (field.placeholder) {
html.push(' placeholder="' + field.placeholder + '"');
}
if (field.min != null) {
html.push(' min="' + field.min + '"');
}
if (field.max != null) {
html.push(' max="' + field.max + '"');
}
if (field.step) {
html.push(' step="' + field.step + '"');
}
if (required) {
html.push(' required');
}
if (disabled) {
html.push(' disabled');
}
if (field.readonly) {
html.push(' readonly');
}
if (field.autocomplete != null) {
html.push(' autocomplete="' + field.autocomplete + '"');
}
html.push('>');
if (type === 'div') {
html.push('</div>');
}
}
if (help !== undefined) {
html.push('<span data-name="' + name + '_help" class="help-block">');
html.push(help);
html.push('</span>');
}
return endingDiv;
},
append: function (fields) {
var html = [];
var _this = this;
var isArray = phantasus.Util.isArray(fields);
if (!isArray) {
fields = [fields];
}
html.push('<div class="form-group">');
var endingDiv = false;
_.each(fields, function (field, index) {
endingDiv || _this._append(html, field, index === 0);
});
html.push('</div>');
if (endingDiv) {
html.push('</div>');
}
var $div = $(html.join(''));
this.$form.append($div);
var checkBoxLists = $div.find('.checkbox-list');
if (checkBoxLists.length > 0) {
var checkBoxIndex = 0;
_.each(fields, function (field) {
// needs to already be in dom
if (field.type === 'checkbox-list') {
var list = new phantasus.CheckBoxList({
responsive: false,
$el: $(checkBoxLists[checkBoxIndex]),
items: field.options
});
$(checkBoxLists[checkBoxIndex]).data(
'phantasus.checkbox-list', list);
checkBoxIndex++;
}
});
}
$div.find('.selectpicker').selectpicker({
iconBase: 'fa',
tickIcon: 'fa-check',
style: 'btn-default btn-sm'
});
},
clear: function () {
this.$form.empty();
},
getValue: function (name) {
var $v = this.$form.find('[name=' + name + ']');
if ($v.length === 0) {
$v = this.$form.find('[name=' + name + '_picker]');
}
if ($v.length === 0) {
$v = this.$form.find('[data-name=' + name + ']');
}
return phantasus.FormBuilder.getValue($v);
},
setOptions: function (name, options, selectFirst) {
var $select = this.$form.find('[name=' + name + ']');
var checkBoxList = $select.data('phantasus.checkbox-list');
if (checkBoxList) {
checkBoxList.setItems(options);
} else {
var html = [];
var selection = $select.val();
_.each(options, function (choice) {
var isChoiceObject = _.isObject(choice)
&& choice.value !== undefined;
if (choice && choice.divider) {
html.push('<option data-divider="true"></option>');
} else {
html.push('<option value="');
var optionValue = isChoiceObject ? choice.value : choice;
var optionText = isChoiceObject ? choice.name : choice;
html.push(optionValue);
html.push('"');
html.push('>');
html.push(optionText);
html.push('</option>');
}
});
$select.html(html.join(''));
$select.val(selection);
if (selectFirst && $select.val() == null) {
if ($select[0].options.length > 0) {
$select.val($select[0].options[0].value);
}
}
if ($select.hasClass('selectpicker')) {
$select.selectpicker('refresh');
$select.selectpicker('render');
}
}
},
find: function (name) {
return this.$form.find('[name=' + name + ']');
},
setHelpText: function (name, value) {
var v = this.$form.find('[data-name=' + name + '_help]');
v.html(value);
},
setValue: function (name, value) {
var v = this.$form.find('[name=' + name + ']');
if (v.length === 0) {
v = this.$form.find('[name=' + name + '_picker]');
if (v.data('type') === 'file') {
v.val(value);
v.selectpicker('render');
v.data('files', value);
return;
}
}
var type = v.attr('type');
var list = v.data('phantasus.checkbox-list');
if (list) {
list.setValue(value);
} else {
if (type === 'radio') {
v.filter('[value=' + value + ']').prop('checked', true);
} else if (type === 'checkbox') {
v.prop('checked', value);
} else {
v.val(value);
}
if (v.hasClass('selectpicker')) {
v.selectpicker('render');
}
}
},
setVisible: function (name, visible) {
var $div = this.$form.find('[name=' + name + ']')
.parents('.form-group');
if (visible) {
$div.show();
} else {
$div.hide();
}
},
remove: function (name) {
var $div = this.$form.find('[name=' + name + ']')
.parents('.form-group');
$div.remove();
},
setEnabled: function (name, enabled) {
var $div = this.$form.find('[name=' + name + ']');
$div.attr('disabled', !enabled);
if (!enabled) {
$div.parents('.form-group').find('label').addClass('text-muted');
} else {
$div.parents('.form-group').find('label').removeClass('text-muted');
}
}
};
phantasus.Util.extend(phantasus.FormBuilder, phantasus.Events);
phantasus.GradientColorSupplier = function () {
phantasus.AbstractColorSupplier.call(this);
this._updateScale();
};
phantasus.GradientColorSupplier.prototype = {
createInstance: function () {
return new phantasus.GradientColorSupplier();
},
getColor: function (row, column, value) {
if (isNaN(value)) {
return this.missingColor;
}
var min = this.min;
var max = this.max;
var colors = this.colors;
if (value <= min) {
return colors[0];
} else if (value >= max) {
return colors[colors.length - 1];
}
var fraction = phantasus.SteppedColorSupplier.linearScale(value, min,
max, 0, 100) / 100;
return this.colorScale(fraction);
},
setFractions: function (options) {
phantasus.AbstractColorSupplier.prototype.setFractions.call(this,
options);
this._updateScale();
},
_updateScale: function () {
this.colorScale = d3.scale.linear().domain(this.fractions).range(
this.colors).clamp(true);
}
};
phantasus.Util.extend(phantasus.GradientColorSupplier,
phantasus.AbstractColorSupplier);
phantasus.Grid = function (options) {
this.options = options;
var _this = this;
var grid;
this.items = options.items;
/**
* Maps from model index to view index. Note that not all model indices are
* contained in the map because they might have been filtered from the view.
*/
this.modelToView = null;
/** view order in model space */
this.viewOrder = null;
function getItemColumnValue(item, column) {
return column.getter(item);
}
this.filter = new phantasus.CombinedGridFilter();
var model = {
getLength: function () {
return _this.viewOrder != null ? _this.viewOrder.length
: _this.items.length;
},
getItem: function (index) {
return _this.items[_this.viewOrder != null ? _this.viewOrder[index]
: index];
}
};
this.$el = options.$el;
var gridOptions = $.extend({}, {
select: true,
headerRowHeight: 0,
showHeaderRow: false,
multiColumnSort: true,
multiSelect: false,
topPanelHeight: 0,
enableColumnReorder: false,
enableTextSelectionOnCells: true,
forceFitColumns: true,
dataItemColumnValueExtractor: getItemColumnValue,
defaultFormatter: function (row, cell, value, columnDef, dataContext) {
if (_.isNumber(value)) {
return phantasus.Util.nf(value);
} else if (phantasus.Util.isArray(value)) {
var s = [];
for (var i = 0, length = value.length; i < length; i++) {
if (i > 0) {
s.push(', ');
}
var val = value[i];
s.push(value[i]);
}
return s.join('');
} else {
return value;
}
}
}, options.gridOptions || {});
grid = new Slick.Grid(options.$el, model, options.columns, gridOptions);
this.grid = grid;
grid.registerPlugin(new phantasus.AutoTooltips2());
grid.onCellChange.subscribe(function (e, args) {
_this.trigger('edit', args);
});
if (gridOptions.select) {
grid.setSelectionModel(new Slick.RowSelectionModel({
selectActiveRow: true,
multiSelect: gridOptions.multiSelect
}));
grid.getSelectionModel().onSelectedRangesChanged.subscribe(function (e) {
var nitems = grid.getDataLength();
_this.trigger('selectionChanged', {
selectedRows: grid.getSelectedRows().filter(function (row) {
return row >= 0 && row <= nitems;
})
});
});
}
grid.onSort.subscribe(function (e, args) {
_this.sortCols = args.sortCols;
_this._updateMappings();
grid.invalidate();
});
options.$el.on('click', function (e) {
var cell = grid.getCellFromEvent(e);
if (cell) {
_this.trigger('click', {
row: cell.row,
target: e.target
});
}
});
options.$el.on('dblclick', function (e) {
var cell = grid.getCellFromEvent(e);
if (cell) {
_this.trigger('dblclick', {
row: cell.row,
target: e.target
});
}
});
if (options.sort) {
var gridSortColumns = [];
var gridColumns = grid.getColumns();
var sortCols = [];
options.sort.forEach(function (c) {
var column = null;
for (var i = 0; i < gridColumns.length; i++) {
if (gridColumns[i].name === c.name) {
column = gridColumns[i];
break;
}
}
if (column != null) {
gridSortColumns.push({
columnId: column.id,
sortAsc: c.sortAsc
});
} else {
// console.log(c.name + ' not found.');
}
});
this.setSortColumns(gridSortColumns);
}
this.grid.invalidate();
};
phantasus.Grid.prototype = {
columnsAutosized: false,
setSortColumns: function (gridSortColumns) {
this.grid.setSortColumns(gridSortColumns);
this.sortCols = [];
for (var i = 0; i < gridSortColumns.length; i++) {
var column = this.grid.getColumns()[this.grid.getColumnIndex(gridSortColumns[i].columnId)];
if (column == null) {
throw 'Unable to find column ' + gridSortColumns[i];
}
this.sortCols.push({
sortCol: column,
sortAsc: gridSortColumns[i].sortAsc
});
}
this._updateMappings();
this.grid.invalidate();
},
setColumns: function (columns) {
this.grid.setColumns(columns);
this.grid.resizeCanvas();
this.grid.invalidate();
},
getColumns: function () {
return this.grid.getColumns();
},
getSelectedRows: function () {
var nitems = this.grid.getDataLength();
return this.grid.getSelectedRows().filter(function (row) {
return row >= 0 && row <= nitems;
});
},
getSelectedItems: function () {
var rows = this.grid.getSelectedRows();
var selection = [];
for (var i = 0, nrows = rows.length; i < nrows; i++) {
selection.push(this.items[this.convertViewIndexToModel(rows[i])]);
}
return selection;
},
getSelectedItem: function () {
var rows = this.grid.getSelectedRows();
if (rows.length === 1) {
return this.items[this.convertViewIndexToModel(rows[0])];
}
return null;
},
/**
* Gets the sorted, visible items
*/
getItems: function () {
var items = [];
for (var i = 0, length = this.getFilteredItemCount(); i < length; i++) {
items.push(this.items[this.convertViewIndexToModel(i)]);
}
return items;
},
getAllItemCount: function () {
return this.items.length;
},
getAllItems: function () {
return this.items;
},
getFilteredItemCount: function () {
return this.viewOrder ? this.viewOrder.length : this.items.length;
},
redraw: function () {
this.grid.invalidate();
},
redrawRows: function (rows) {
this.grid.invalidateRows(rows);
this.grid.render();
},
setItems: function (items) {
// clear the selection
this.items = items;
if (this.grid.getSelectionModel()) {
this.grid.setSelectedRows([]);
}
this.setFilter(this.filter);
this.maybeAutoResizeColumns();
},
maybeAutoResizeColumns: function () {
if (!this.columnsAutosized) {
this.autosizeColumns();
}
},
convertModelIndexToView: function (modelIndex) {
if (this.modelToView !== null) {
var index = this.modelToView.get(modelIndex);
return index !== undefined ? index : -1;
}
return modelIndex;
},
convertViewIndexToModel: function (viewIndex) {
return this.viewOrder != null ? (viewIndex < this.viewOrder.length
&& viewIndex >= 0 ? this.viewOrder[viewIndex] : -1) : viewIndex;
},
_updateMappings: function () {
var selectedViewIndices = this.grid.getSelectionModel() != null ? this.grid
.getSelectedRows()
: null;
var selectedModelIndices = [];
if (selectedViewIndices) {
for (var i = 0, length = selectedViewIndices.length; i < length; i++) {
selectedModelIndices.push(this
.convertViewIndexToModel(selectedViewIndices[i]));
}
}
this.viewOrder = null;
if (this.filter != null) {
this.filter.init();
if (!this.filter.isEmpty()) {
this.viewOrder = [];
for (var i = 0, length = this.items.length; i < length; i++) {
if (this.filter.accept(this.items[i])) {
this.viewOrder.push(i);
}
}
}
}
var cols = this.sortCols;
if (cols && cols.length > 0) {
if (this.viewOrder == null) {
this.viewOrder = [];
for (var i = 0, length = this.items.length; i < length; i++) {
this.viewOrder.push(i);
}
}
var ncols = cols.length;
var items = this.items;
// nulls always go at end
this.viewOrder.sort(function (index1, index2) {
for (var i = 0; i < ncols; i++) {
var getter = cols[i].sortCol.getter;
var comparator = cols[i].sortAsc ? phantasus.SortKey.ASCENDING_COMPARATOR : phantasus.SortKey.DESCENDING_COMPARATOR;
var value1 = getter(items[index1]);
var value2 = getter(items[index2]);
var result = comparator(value1, value2);
if (result !== 0) {
return result;
}
}
return 0;
});
}
if (this.viewOrder != null) {
this.modelToView = new phantasus.Map();
for (var i = 0, length = this.viewOrder.length; i < length; i++) {
this.modelToView.set(this.viewOrder[i], i);
}
} else {
this.modelToView = null;
}
if (this.grid.getSelectionModel() != null) {
var newSelectedViewIndices = [];
for (var i = 0, length = selectedModelIndices.length; i < length; i++) {
var index = this
.convertModelIndexToView(selectedModelIndices[i]);
if (index !== undefined) {
newSelectedViewIndices.push(index);
}
}
this.grid.setSelectedRows(newSelectedViewIndices);
}
},
setSelectedRows: function (rows) {
this.grid.setSelectedRows(rows);
},
setFilter: function (filter) {
this.filter = filter;
this._updateMappings();
this.grid.invalidate();
this.trigger('filter');
},
getFilter: function () {
return this.filter;
},
autosizeColumns: function () {
var columns = this.grid.getColumns();
var items = this.getItems();
if (!items || items.length === 0 || !columns || columns.length === 0) {
return;
}
var gridWidth = this.options.$el.width() - 30;
if (gridWidth <= 0) {
return;
}
this.columnsAutosized = true;
if (columns.length > -1) {
var div = document.createElement('div');
document.body.appendChild(div);
var $d = $(div);
$d.css({
position: 'absolute',
left: -1000,
top: -1000
});
var $row = $('<div class="slick-table">'
+ '<div class="ui-state-default slick-header-column slick-header-sortable ui-sortable-handle"></div>'
+ '<div class="ui-widget-content slick-row"><div class="slick-cell selected"></div></div>'
+ '</div>');
var $cell = $row.find('.slick-cell');
var $header = $row.find('.slick-header-column');
$row.appendTo($d);
var maxWidth = Math.min(parseInt(gridWidth / 2), 400);
var getColumnWidth = function (column) {
var w = $header.html(column.name).outerWidth() + 13; // leave space for sort indicator
if (column.prototypeValue) {
$cell.html(column.prototypeValue);
w = Math.max($cell.outerWidth(), w);
} else {
for (var i = 0, nrows = Math.min(items.length, 10); i < nrows; i++) {
var html = column.formatter(i, null, column
.getter(items[i]), column, items[i]);
var $html = $(html);
$html.find('.slick-cell-wrapper').attr('class', '');
$cell.html($html);
w = Math.max($cell.outerWidth(), w);
}
}
column.width = parseInt(Math.min(maxWidth, w));
};
var totalWidth = 0;
for (var i = 0; i < columns.length; i++) {
getColumnWidth(columns[i]);
totalWidth += columns[i].width;
}
if (totalWidth < gridWidth) {
// grow columns
// var delta = parseInt((gridWidth - totalWidth) / columns.length);
// for (var i = 0; i < columns.length; i++) {
// //columns[i].width += delta;
// }
} else if (totalWidth > gridWidth) {
// shrink
//columns[columns.length - 1].width -= (totalWidth - gridWidth);
// shrink last column
}
$d.remove();
this.grid.resizeCanvas();
}
}
};
phantasus.Util.extend(phantasus.Grid, phantasus.Events);
/**
* AutoTooltips2 plugin to show/hide tooltips when columns are too narrow to fit
* content.
*
* @constructor
*/
phantasus.AutoTooltips2 = function (options) {
var _grid;
var _self = this;
var tip;
/**
* Initialize plugin.
*/
function init(grid) {
_grid = grid;
$(_grid.getCanvasNode()).on('mouseover', '.slick-row', showToolTip);
$(_grid.getCanvasNode()).on('mouseout', '.slick-row', hideToolTip);
$(_grid.getCanvasNode()).on('mouseup', hideAll);
// $(_grid.getContainerNode()).on('mouseover', '.slick-header-column',
// showHeaderToolTip);
// $(_grid.getContainerNode()).on('mouseout', '.slick-header-column',
// hideHeaderToolTip);
}
/**
* Destroy plugin.
*/
function destroy() {
$(_grid.getCanvasNode()).off('mouseover', showToolTip);
$(_grid.getCanvasNode()).off('mouseout', hideToolTip);
$(_grid.getCanvasNode()).off('mouseup', hideAll);
// $(_grid.getContainerNode()).off('mouseover', '.slick-header-column',
// showHeaderToolTip);
// $(_grid.getContainerNode()).off('mouseout', '.slick-header-column',
// hideHeaderToolTip);
}
/**
* Handle mouse entering grid cell to add/remove tooltip.
*
* @param {jQuery.Event}
* e - The event
*/
function hideToolTip(e) {
var cell = _grid.getCellFromEvent(e);
if (cell) {
var $node = $(_grid.getCellNode(cell.row, cell.cell));
if ($node.data('bs.tooltip')) {
$node.tooltip('hide');
}
}
}
function hideAll() {
$(_grid.getCanvasNode()).find('[data-original-title]').attr(
'data-original-title', '').tooltip('hide');
}
function hideHeaderToolTip(e) {
var $node = $(e.target);
if ($node.data('bs.tooltip')) {
$node.tooltip('hide');
}
}
function showHeaderToolTip(e) {
var show = false;
var $node = $(e.target);
if (($node[0].scrollWidth > $node[0].offsetWidth)) {
show = true;
var $name = $node.find('.slick-column-name');
if (!$node.data('bs.tooltip')) {
$node.tooltip({
placement: 'auto',
html: true,
container: 'body',
trigger: 'manual'
});
}
$node.attr('data-original-title', $name.text());
if (show) {
$node.tooltip('show');
} else {
$node.tooltip('hide');
}
}
}
function showToolTip(e) {
var cell = _grid.getCellFromEvent(e);
if (cell) {
var $node = $(_grid.getCellNode(cell.row, cell.cell));
var text = '';
var c = _grid.getColumns()[cell.cell];
var show = false;
var $checkNode = $node.find('.slick-cell-wrapper');
if (c.alwaysShowTooltip
|| ($checkNode[0].scrollWidth > $checkNode[0].offsetWidth)) {
var item = _grid.getDataItem(cell.row);
text = c.tooltip(item, c.getter(item));
show = true;
}
$node.attr('data-original-title', text);
var hasTip = $node.data('bs.tooltip');
if (!hasTip) {
$node.tooltip({
placement: 'auto',
html: true,
container: 'body',
trigger: 'manual'
});
}
if (show) {
$node.tooltip('show');
} else if (hasTip) {
$node.tooltip('hide');
}
}
}
/**
* Handle mouse entering header cell to add/remove tooltip.
*
* @param {jQuery.Event}
* e - The event
* @param {object}
* args.column - The column definition
*/
function handleHeaderMouseEnter(e, args) {
var column = args.column, $node = $(e.target).closest(
'.slick-header-column');
if (!column.toolTip) {
$node.attr('title',
($node.innerWidth() < $node[0].scrollWidth) ? column.name
: '');
}
}
// Public API
$.extend(this, {
'init': init,
'destroy': destroy
});
};
phantasus.CombinedGridFilter = function () {
this.filters = [];
};
phantasus.CombinedGridFilter.prototype = {
add: function (filter) {
this.filters.push(filter);
},
getFilters: function () {
return this.filters;
},
get: function (index) {
return this.filters[index];
},
set: function (index, f) {
this.filters[index] = f;
},
init: function () {
for (var i = 0; i < this.filters.length; i++) {
this.filters[i].init();
}
this.activeFilters = this.filters.filter(function (f) {
return !f.isEmpty();
});
this.nActiveFilters = this.activeFilters.length;
},
accept: function (item) {
for (var i = 0; i < this.nActiveFilters; i++) {
if (!this.activeFilters[i].accept(item)) {
return false;
}
}
return true;
},
isEmpty: function () {
return this.activeFilters.length === 0;
}
};
phantasus.HeatMapColorSchemeChooser = function (options) {
var _this = this;
this.$div = $('<div></div>');
this.currentValue = null;
this.legend = new phantasus.LegendWithStops();
this.colorScheme = options.colorScheme || new phantasus.HeatMapColorScheme(new phantasus.Project(new phantasus.Dataset({
rows: 0,
columns: 0
})));
this.legend.on('added', function (e) {
var fractions = _this.colorScheme.getFractions();
fractions.push(e.fraction);
var colors = _this.colorScheme.getColors();
colors.push('black');
_this.colorScheme.setFractions({
fractions: fractions,
colors: colors
});
var newIndex = _this.getFractionIndex(e.fraction, 'black');
_this.setSelectedIndex(newIndex);
_this.fireChanged();
}).on('selectedIndex', function (e) {
_this.setSelectedIndex(e.selectedIndex);
}).on('delete', function (index) {
_this.deleteSelectedStop();
}).on(
'moved',
function (e) {
var fraction = e.fraction;
var fractions = _this.colorScheme.getFractions();
fractions[_this.legend.selectedIndex] = fraction;
var color = _this.colorScheme.getColors()[_this.legend.selectedIndex];
_this.colorScheme.setFractions({
fractions: fractions,
colors: _this.colorScheme.getColors()
});
_this.legend.selectedIndex = _this.getFractionIndex(e.fraction, color);
var fractionToValue = d3.scale.linear().domain([0, 1])
.range(
[_this.colorScheme.getMin(),
_this.colorScheme.getMax()])
.clamp(true);
_this.formBuilder.setValue('selected_value',
fractionToValue(fractions[_this.legend.selectedIndex]));
_this.fireChanged();
});
var $row = $('<div></div>');
$row.css('height', '50px').css('width', '300px').css('margin-left', 'auto')
.css('margin-right', 'auto');
$row.appendTo(this.$div);
$(this.legend.canvas).appendTo($row);
var formBuilder = new phantasus.FormBuilder();
var items = [];
items = items.concat({
name: 'selected_color',
type: 'color',
style: 'max-width: 50px;'
}, {
name: 'selected_value',
type: 'text',
style: 'max-width: 100px;'
}, [{
name: 'delete',
type: 'button',
value: 'Delete Selected Color Stop',
}, {
name: 'add',
type: 'button',
value: 'Add Color Stop'
}], {
name: 'minimum',
type: 'text',
style: 'max-width: 100px;'
}, {
name: 'maximum',
type: 'text',
style: 'max-width: 100px;'
});
if (options.showRelative) {
items = items.concat({
name: 'relative_color_scheme',
type: 'checkbox',
help: 'A relative color scheme uses the minimum and maximum values in each row' +
' to convert values to colors'
});
items = items.concat({
name: 'transform_values',
type: 'select',
value: 0,
options: [{
name: 'None',
value: 0
}, {
name: 'Subtract row mean, divide by row standard deviation',
value: phantasus.AbstractColorSupplier.Z_SCORE
}, {
name: 'Subtract row median, divide by row median absolute deviation',
value: phantasus.AbstractColorSupplier.ROBUST_Z_SCORE
}]
});
}
items = items.concat({
name: 'missing_color',
type: 'color',
style: 'max-width: 50px;'
});
items
.push({
name: 'stepped_colors',
type: 'checkbox',
value: false,
help: 'Intervals include left end point and exclude right end point, except for the highest interval'
});
_.each(items, function (item) {
formBuilder.append(item);
});
this.getFractionIndex = function (fraction, color) {
var fractions = _this.colorScheme.getFractions();
var colors = _this.colorScheme.getColors();
for (var i = 0, len = fractions.length; i < len; i++) {
if (fractions[i] === fraction && colors[i] === color) {
return i;
}
}
return -1;
};
this.$div.append(formBuilder.$form);
formBuilder.$form.find('[name^=selected],[name=delete]').prop('disabled',
true);
formBuilder.$form.find('[name=add]').on('click', function (e) {
var fractions = _this.colorScheme.getFractions();
var val = 0.5;
while (val >= 0 && _.indexOf(fractions, val) !== -1) {
val -= 0.1;
}
val = Math.max(0, val);
fractions.push(val);
var colors = _this.colorScheme.getColors();
colors.push('black');
_this.colorScheme.setFractions({
fractions: fractions,
colors: colors
});
var newIndex = _this.getFractionIndex(e.fraction, 'black');
_this.setSelectedIndex(newIndex);
_this.fireChanged();
});
formBuilder.$form.find('[name=delete]').on('click', function (e) {
_this.deleteSelectedStop();
});
formBuilder.$form.find('[name=transform_values]').on('change', function (e) {
_this.colorScheme.setTransformValues(parseInt(formBuilder.getValue('transform_values')));
_this.fireChanged();
});
formBuilder.$form.on('keyup', '[name=selected_value]', _.debounce(function (e) {
var val = parseFloat($(this).val());
if (!isNaN(val)) {
_this.setSelectedValue(val);
_this.fireChanged();
}
}, 100));
formBuilder.$form.on('change', '[name=selected_color]', function (e) {
var colors = _this.colorScheme.getColors();
colors[_this.legend.selectedIndex] = $(this).val();
_this.colorScheme.setFractions({
fractions: _this.colorScheme.getFractions(),
colors: colors
});
_this.fireChanged();
});
formBuilder.$form.on('change', '[name=missing_color]', function (e) {
var color = $(this).val();
_this.colorScheme.setMissingColor(color);
_this.fireChanged(false);
});
formBuilder.$form.on('change', '[name=stepped_colors]', function (e) {
_this.colorScheme.setStepped($(this).prop('checked'));
_this.fireChanged();
});
formBuilder.$form.on('keyup', '[name=minimum]', _.debounce(function (e) {
var val = parseFloat($(this).val());
if (!isNaN(val)) {
_this.colorScheme.setMin(val);
_this.setSelectedIndex(_this.legend.selectedIndex);
_this.fireChanged(false);
}
}, 100));
formBuilder.$form.on('keyup', '[name=maximum]', _.debounce(function (e) {
var val = parseFloat($(this).val());
if (!isNaN(val)) {
_this.colorScheme.setMax(val);
_this.setSelectedIndex(_this.legend.selectedIndex);
_this.fireChanged(false);
}
}, 100));
formBuilder.$form
.on(
'change',
'[name=relative_color_scheme]',
_
.throttle(
function (e) {
_this.legend.selectedIndex = -1;
// FIXME set fixed min and max
var scalingMode = $(this).prop('checked') ? phantasus.HeatMapColorScheme.ScalingMode.RELATIVE
: phantasus.HeatMapColorScheme.ScalingMode.FIXED;
_this.colorScheme
.setScalingMode(scalingMode);
_this.setColorScheme(_this.colorScheme);
_this.fireChanged();
}, 100));
this.formBuilder = formBuilder;
// selection: delete, color, value
// general: add, min, max, relative or global
};
phantasus.HeatMapColorSchemeChooser.prototype = {
deleteSelectedStop: function () {
var fractions = this.colorScheme.getFractions();
fractions.splice(this.legend.selectedIndex, 1);
var colors = this.colorScheme.getColors();
colors.splice(this.legend.selectedIndex, 1);
this.colorScheme.setFractions({
fractions: fractions,
colors: colors
});
this.formBuilder.$form.find('[name^=selected],[name=delete]').prop(
'disabled', true);
this.legend.setSelectedIndex(-1);
this.fireChanged();
},
setSelectedValue: function (val) {
var valueToFraction = d3.scale.linear().domain(
[this.colorScheme.getMin(), this.colorScheme.getMax()])
.range([0, 1]).clamp(true);
var fractions = this.colorScheme.getFractions();
var fraction = valueToFraction(val);
fractions[this.legend.selectedIndex] = fraction;
var color = this.colorScheme.getColors()[this.legend.selectedIndex];
this.colorScheme.setFractions({
fractions: fractions,
colors: this.colorScheme.getColors()
});
this.legend.selectedIndex = this.getFractionIndex(fraction, color);
},
setSelectedIndex: function (index) {
var fractions = this.colorScheme.getFractions();
if (index >= fractions.length) {
index = -1;
}
this.legend.setSelectedIndex(index);
var formBuilder = this.formBuilder;
formBuilder.$form.find('[name^=selected],[name=delete]').prop(
'disabled', this.legend.selectedIndex === -1);
if (this.legend.selectedIndex !== -1) {
var fractionToValue = d3.scale.linear().domain([0, 1]).range(
[this.colorScheme.getMin(), this.colorScheme.getMax()])
.clamp(true);
formBuilder.setValue('selected_value',
fractionToValue(fractions[this.legend.selectedIndex]));
var context = this.legend.canvas.getContext('2d');
var colors = this.colorScheme.getColors();
context.fillStyle = colors[this.legend.selectedIndex];
formBuilder.setValue('selected_color', context.fillStyle);
} else {
formBuilder.setValue('selected_value', '');
}
this.draw();
},
setMinMax: function () {
if (this.colorScheme.getScalingMode() === phantasus.HeatMapColorScheme.ScalingMode.RELATIVE) {
this.colorScheme.setMin(0);
this.colorScheme.setMax(1);
}
},
dispose: function () {
this.off('change');
this.legend.destroy();
this.formBuilder.$form.off('keyup', 'input');
this.formBuilder.$form.off('change', '[name=relative_color_scheme]');
},
restoreCurrentValue: function () {
if (this.colorScheme.setCurrentValue) {
this.colorScheme.setCurrentValue(this.currentValue);
}
},
setCurrentValue: function (value) {
this.currentValue = value;
if (this.colorScheme && this.colorScheme.setCurrentValue) {
this.colorScheme.setCurrentValue(this.currentValue);
}
this.setColorScheme(this.colorScheme);
},
setColorScheme: function (colorScheme) {
this.colorScheme = colorScheme;
this.setMinMax();
if (colorScheme.setCurrentValue) {
colorScheme.setCurrentValue(this.currentValue);
}
this.formBuilder
.setValue(
'relative_color_scheme',
colorScheme.getScalingMode() === phantasus.HeatMapColorScheme.ScalingMode.RELATIVE ? true
: false);
this.formBuilder.setValue('transform_values', colorScheme.getTransformValues());
this.formBuilder.setEnabled('transform_values', colorScheme.getScalingMode() !== phantasus.HeatMapColorScheme.ScalingMode.RELATIVE);
this.formBuilder.$form
.find('[name=minimum],[name=maximum]')
.prop(
'disabled',
colorScheme.getScalingMode() === phantasus.HeatMapColorScheme.ScalingMode.RELATIVE);
this.formBuilder.setValue('minimum', this.colorScheme.getMin());
this.formBuilder.setValue('maximum', this.colorScheme.getMax());
this.formBuilder.setValue('stepped_colors', this.colorScheme
.isStepped());
this.formBuilder.setValue('missing_color', this.colorScheme
.getMissingColor());
this.draw();
},
getFractionToStopPix: function () {
return d3.scale.linear().clamp(true).domain([0, 1]).range(
[this.legend.border,
this.legend.getUnscaledWidth() - this.legend.border]);
},
fireChanged: function (noreset) {
this.trigger('change');
if (noreset !== false) {
this.setColorScheme(this.colorScheme);
}
},
draw: function () {
var colorScheme = this.colorScheme;
if (colorScheme.getScalingMode() === phantasus.HeatMapColorScheme.ScalingMode.RELATIVE) {
colorScheme.setMin(0);
colorScheme.setMax(1);
}
var fractions = colorScheme.getFractions();
var colors = colorScheme.getColors();
var fractionToStopPix = this.getFractionToStopPix();
this.legend.draw(fractions, colors, colorScheme.isStepped(),
fractionToStopPix);
}
};
phantasus.Util.extend(phantasus.HeatMapColorSchemeChooser, phantasus.Events);
phantasus.HeatMapColorSchemeLegend = function (heatMap, $keyContent) {
var colorScheme = heatMap.heatmap.getColorScheme();
var colorByValues = colorScheme.getColorByValues();
var totalHeight;
$keyContent.empty();
var ntracks = colorByValues.length;
colorByValues
.forEach(function (value) {
if (value != null || ntracks === 1) {
if (value != 'null') { // values are stored as string
var $label = $('<div style="overflow:hidden;text-overflow:' +
' ellipsis;width:250px;max-width:250px;">'
+ value + '</div>');
$keyContent.append($label);
totalHeight += $label.height();
}
var trackLegend = new phantasus.ColorSupplierLegend(
colorScheme, value);
$(trackLegend.canvas).css('position', '');
trackLegend.repaint();
trackLegend.on('selectionChanged', function () {
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
});
$keyContent.append($(trackLegend.canvas));
totalHeight += trackLegend.getUnscaledHeight();
}
});
if (heatMap.options.$key) {
$keyContent.append(heatMap.options.$key);
totalHeight += heatMap.options.$key.height();
}
var $edit = $('<div style="padding-left:4px; display:inline;"><a data-name="options"' +
' href="#">Edit</a></div>');
$edit.find('[data-name=options]').on('click', function (e) {
e.preventDefault();
heatMap.showOptions();
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'options'
});
});
totalHeight += $edit.height();
$keyContent.append($edit);
$keyContent.css({
'text-overflow': 'ellipsis',
overflow: 'hidden',
width: 250 + 'px',
height: totalHeight + 'px'
});
};
phantasus.HeatMapColorSchemeLegend.drawColorScheme = function (context,
colorScheme, width, printing, hideText, legendHeight) {
if (!legendHeight) {
legendHeight = 12;
}
context.font = '11px ' + phantasus.CanvasUtil.getFontFamily(context);
var names = colorScheme.getNames();
var hasNames = names != null;
// if hasNames that we draw vertically to ensure space for names
if (hasNames) {
phantasus.HeatMapColorSchemeLegend.drawColorSchemeVertically(context,
colorScheme, colorScheme.getHiddenValues(), printing);
} else {
phantasus.HeatMapColorSchemeLegend.draw(context, colorScheme
.getFractions(), colorScheme.getColors(), width, legendHeight,
colorScheme.isStepped());
context.strokeStyle = 'LightGrey';
context.strokeRect(0, 0, width, legendHeight);
if (hideText) {
return;
}
var map = d3.scale.linear().domain([0, 1]).range([0, width]).clamp(
true);
var fractionToValue = d3.scale.linear().domain([0, 1]).range(
[colorScheme.getMin(), colorScheme.getMax()]).clamp(true);
context.textAlign = 'center';
context.textBaseline = 'top';
context.fillStyle = 'black';
if (colorScheme.getScalingMode() === phantasus.HeatMapColorScheme.ScalingMode.RELATIVE) {
context.fillText('row min', 0, 14);
context.fillText('row max', width, legendHeight + 2);
} else {
var fractions = colorScheme.getFractions();
var lastTextPixEnd = -1;
// draw from left to middle and then from right to middle to avoid
// text overlap
var halfway = parseInt(fractions.length / 2);
for (var i = 0; i < halfway; i++) {
var pix = map(fractions[i]);
var text = '';
if (hasNames) {
text = names[i] != '' ? (names[i] + ' ('
+ fractionToValue(fractions[i]) + ')') : names[i];
} else {
text = phantasus.Util.nf(fractionToValue(fractions[i]));
}
var textWidth = context.measureText(text).width;
if (pix > lastTextPixEnd) {
context.fillText(text, pix, legendHeight + 2);
}
lastTextPixEnd = pix + textWidth / 2;
}
var lastTextPixStart = 10000;
for (var i = fractions.length - 1; i >= halfway; i--) {
var pix = map(fractions[i]);
var text = '';
if (hasNames) {
text = names[i] != '' ? (names[i] + ' ('
+ fractionToValue(fractions[i]) + ')') : names[i];
} else {
text = phantasus.Util.nf(fractionToValue(fractions[i]));
}
var textWidth = context.measureText(text).width;
var textPixEnd = pix + textWidth / 2;
if (textPixEnd < lastTextPixStart) {
context.fillText(text, pix, legendHeight + 2);
lastTextPixStart = pix - textWidth / 2;
}
}
}
}
};
phantasus.HeatMapColorSchemeLegend.drawColorSchemeVertically = function (context,
colorScheme, hiddenValues, printing) {
var fractionToValue = d3.scale.linear().domain([0, 1]).range(
[colorScheme.getMin(), colorScheme.getMax()]).clamp(true);
context.textAlign = 'left';
context.textBaseline = 'top';
context.fillStyle = 'black';
var fractions = colorScheme.getFractions();
var colors = colorScheme.getColors();
var names = colorScheme.getNames();
context.strokeStyle = 'LightGrey';
var xpix = 0;
var ypix = 0;
context.font = '12px ' + phantasus.CanvasUtil.getFontFamily(context);
for (var i = 0; i < colors.length; i++) {
var name = names[i];
if (name != null) {
context.fillStyle = colors[i];
context.fillRect(xpix, ypix, 12, 12);
context.strokeRect(xpix, ypix, 12, 12);
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
if (hiddenValues && !printing) {
var value = fractionToValue(fractions[i]);
context.font = '12px FontAwesome';
if (!hiddenValues.has(value)) {
context.fillText('\uf00c', -14, ypix); // checked
}
// else {
// context.fillText("\uf096", -14, ypix); // unchecked
// }
context.font = '12px ' + phantasus.CanvasUtil.getFontFamily(context);
}
context.fillText(name, xpix + 16, ypix);
}
ypix += 14;
}
};
phantasus.HeatMapColorSchemeLegend.draw = function (context, fractions, colors,
width, height, stepped) {
if (!stepped) {
var gradient = context.createLinearGradient(0, 0, width, height);
for (var i = 0, length = fractions.length; i < length; i++) {
gradient.addColorStop(fractions[i], colors[i]);
}
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
} else {
// intervals include left end point, exclude right end point, except for
// the highest interval
// TODO right-most endpoint is not shown
var map = d3.scale.linear().domain([0, 1]).range([0, width]).clamp(
true);
for (var i = 0, length = fractions.length; i < length; i++) {
context.fillStyle = colors[i];
var x1 = map(fractions[i]);
var x2 = i === length - 1 ? width : map(fractions[i + 1]);
context.fillRect(Math.min(x1, x2), 0, Math.abs(x2 - x1), height);
}
}
};
phantasus.ColorSupplierLegend = function (colorScheme, value) {
phantasus.AbstractCanvas.call(this, false);
var _this = this;
this.value = value;
this.colorScheme = colorScheme;
colorScheme.setCurrentValue(value);
var hiddenValues = colorScheme.getHiddenValues();
var names = colorScheme.getNames();
var hasNames = names != null;
var legendHeight = hasNames ? names.length * 14 : 30;
var bounds = {
width: 250,
height: legendHeight
};
this.hasNames = hasNames;
this.setBounds(bounds);
if (hasNames && hiddenValues) {
$(this.canvas)
.on(
'click',
function (e) {
e.preventDefault();
e.stopPropagation();
var clickedRow = Math
.floor((e.clientY - _this.canvas
.getBoundingClientRect().top) / 14);
var fractionToValue = d3.scale.linear().domain(
[0, 1]).range(
[colorScheme.getMin(),
colorScheme.getMax()]).clamp(true);
var fractions = colorScheme.getFractions();
var value = fractionToValue(fractions[clickedRow]);
if (!hiddenValues.has(value)) {
hiddenValues.add(value);
} else {
hiddenValues.remove(value);
}
_this.trigger('selectionChanged');
_this.repaint();
});
}
};
phantasus.ColorSupplierLegend.prototype = {
draw: function (clip, context) {
var colorScheme = this.colorScheme;
colorScheme.setCurrentValue(this.value);
// context.fillStyle = 'white';
// context.fillRect(0, 0, this.getUnscaledWidth(), this
// .getUnscaledHeight());
context.translate(this.hasNames ? 14
: (this.getUnscaledWidth() - 200) / 2, 0);
phantasus.HeatMapColorSchemeLegend.drawColorScheme(context, colorScheme,
200);
}
};
phantasus.Util.extend(phantasus.ColorSupplierLegend, phantasus.Events);
phantasus.Util.extend(phantasus.ColorSupplierLegend,
phantasus.AbstractCanvas);
/**
* @param type
* Either relative or fixed.
* @param stops
* An array of objects with value and color
*/
phantasus.HeatMapColorScheme = function (project, scheme) {
this.project = project;
var _this = this;
this.separateColorSchemeForRowMetadataField = null;
this.rowValueToColorSupplier = {};
this.value = null;
if (scheme) {
if (scheme.valueToColorScheme) { // json representation
this.fromJSON(scheme);
} else {
this.rowValueToColorSupplier[null] = this.fromJSON(scheme);
this.currentColorSupplier = this.rowValueToColorSupplier[this.value];
}
}
project
.on(
'rowFilterChanged columnFilterChanged rowSortOrderChanged columnSortOrderChanged datasetChanged',
function () {
_this.projectUpdated();
});
this.projectUpdated();
};
phantasus.HeatMapColorScheme.Predefined = {};
phantasus.HeatMapColorScheme.Predefined.CN = function () {
return {
scalingMode: 'fixed',
values: [-2, -0.1, 0.1, 2],
colors: ['#0000ff', '#ffffff', '#ffffff', '#ff0000']
};
};
phantasus.HeatMapColorScheme.Predefined.BINARY = function () {
return {
scalingMode: 'fixed',
values: [0, 1],
colors: ['#ffffff', 'black']
};
};
phantasus.HeatMapColorScheme.Predefined.RELATIVE = function () {
return {
scalingMode: 'relative'
};
};
phantasus.HeatMapColorScheme.Predefined.MAF = function () {
// coMut plot colors
return {
scalingMode: 'fixed',
stepped: true,
values: [0, 1, 2, 3, 4, 5, 6, 7],
names: ['', 'Synonymous', 'In Frame Indel', 'Other Non-Synonymous', 'Missense', 'Splice Site', 'Frame Shift', 'Nonsense'],
colors: ['#ffffff', '#4daf4a', '#ffff33', '#a65628', '#377eb8', '#984ea3', '#ff7f00', '#e41a1c']
};
};
// phantasus.HeatMapColorScheme.Predefined.MAF_NEW = function() {
// // Synonymous 1
// //In_frame_Indel 2
// //Other_non_syn. 3
// //Missense 4
// //Splice_Site 5
// //Frame_Shift 6
// //Nonsense 7
// return {
// type : 'fixed',
// stepped : true,
// map : [ {
// value : 0,
// color : 'rgb(' + [ 255, 255, 255 ].join(',') + ')',
// name : ''
// }, {
// value : 1,
// color : 'rgb(' + [ 255, 255, 179 ].join(',') + ')',
// name : 'Silent'
// }, {
// value : 2,
// color : 'rgb(' + [ 69, 117, 180 ].join(',') + ')',
// name : 'In Frame Indel'
// }, {
// value : 3,
// color : 'rgb(' + [ 247, 182, 210 ].join(',') + ')',
// name : 'Other Non-Synonymous'
// }, {
// value : 4,
// color : 'rgb(' + [ 1, 133, 113 ].join(',') + ')',
// name : 'Missense'
// }, {
// value : 5,
// color : 'rgb(' + [ 253, 180, 98 ].join(',') + ')',
// name : 'Splice Site'
// }, {
// value : 6,
// color : 'rgb(' + [ 140, 81, 10 ].join(',') + ')',
// name : 'Frame Shift'
// }, {
// value : 7,
// color : 'rgb(' + [ 123, 50, 148 ].join(',') + ')',
// name : 'Nonsense'
// } ]
// };
// };
phantasus.HeatMapColorScheme.Predefined.ZS = function () {
return {
scalingMode: 'fixed',
values: [-10, -2, 2, 10],
colors: ['#0000ff', '#ffffff', '#ffffff', '#ff0000']
};
};
phantasus.HeatMapColorScheme.ScalingMode = {
RELATIVE: 0,
FIXED: 1
};
phantasus.HeatMapConditions = function () {
this.array = [];
// each condition is a object with: seriesName (series is old deprecated field), shape, color and
// accept(val) function
};
phantasus.HeatMapConditions.prototype = {
insert: function (index, c) {
this.array.splice(index, 0, c);
},
add: function (c) {
this.array.push(c);
},
getConditions: function () {
return this.array;
},
remove: function (index) {
this.array.splice(index, 1);
},
copy: function () {
var c = new phantasus.HeatMapConditions();
this.array.forEach(function (cond) {
c.array.push(_.clone(cond));
});
return c;
}
};
phantasus.HeatMapColorScheme.prototype = {
getColors: function () {
return this.currentColorSupplier.getColors();
},
setMissingColor: function (color) {
this.currentColorSupplier.setMissingColor(color);
},
getHiddenValues: function () {
return this.currentColorSupplier.getHiddenValues ? this.currentColorSupplier
.getHiddenValues()
: null;
},
getMissingColor: function () {
return this.currentColorSupplier.getMissingColor();
},
getScalingMode: function () {
return this.currentColorSupplier.getScalingMode();
},
getSizer: function () {
return this.currentColorSupplier.getSizer();
},
getConditions: function () {
return this.currentColorSupplier.getConditions();
},
setScalingMode: function (scalingMode) {
this.currentColorSupplier.setScalingMode(scalingMode);
},
getFractions: function () {
return this.currentColorSupplier.getFractions();
},
getNames: function () {
return this.currentColorSupplier.getNames();
},
getMin: function () {
return this.currentColorSupplier.getMin();
},
getMax: function () {
return this.currentColorSupplier.getMax();
},
setMin: function (min) {
this.currentColorSupplier.setMin(min);
},
setMax: function (max) {
this.currentColorSupplier.setMax(max);
},
isStepped: function () {
return this.currentColorSupplier.isStepped();
},
setFractions: function (options) {
this.currentColorSupplier.setFractions(options);
},
setTransformValues: function (options) {
this.currentColorSupplier.setTransformValues(options);
this.cachedRowStats.cachedRow = -1;
},
getTransformValues: function () {
return this.currentColorSupplier.getTransformValues();
},
setStepped: function (stepped) {
var oldColorSupplier = this.currentColorSupplier;
var newColorSupplier = stepped ? new phantasus.SteppedColorSupplier()
: new phantasus.GradientColorSupplier();
newColorSupplier.sizer = oldColorSupplier.getSizer();
newColorSupplier.array = oldColorSupplier.getConditions();
newColorSupplier.setScalingMode(oldColorSupplier.getScalingMode());
newColorSupplier.setMin(oldColorSupplier.getMin());
newColorSupplier.setMax(oldColorSupplier.getMax());
newColorSupplier.setFractions({
fractions: oldColorSupplier.getFractions(),
colors: oldColorSupplier.getColors()
});
this.currentColorSupplier = newColorSupplier;
this.rowValueToColorSupplier[this.value] = this.currentColorSupplier;
},
toJSON: function () {
var json = {};
var _this = this;
if (this.separateColorSchemeForRowMetadataField != null) {
json.separateColorSchemeForRowMetadataField = this.separateColorSchemeForRowMetadataField;
}
json.valueToColorScheme = {};
_.each(_.keys(this.rowValueToColorSupplier), function (key) {
// save each scheme
json.valueToColorScheme[key] = phantasus.AbstractColorSupplier.toJSON(_this.rowValueToColorSupplier[key]);
});
return json;
},
fromJSON: function (json) {
var _this = this;
if (json.separateColorSchemeForRowMetadataField) {
this.separateColorSchemeForRowMetadataField = json.separateColorSchemeForRowMetadataField;
this.vector = this.project.getSortedFilteredDataset()
.getRowMetadata().getByName(
this.separateColorSchemeForRowMetadataField);
}
this.rowValueToColorSupplier = {};
var obj = json.valueToColorScheme || json.colorSchemes;
if (obj == null) {
var colorSupplier = phantasus.AbstractColorSupplier
.fromJSON(json);
_this.rowValueToColorSupplier['null'] = colorSupplier;
} else {
_.each(_.keys(obj), function (key) {
var colorSupplier = phantasus.AbstractColorSupplier
.fromJSON(obj[key]);
_this.rowValueToColorSupplier[key] = colorSupplier;
});
}
this._ensureColorSupplierExists();
},
copy: function (project) {
var _this = this;
var c = new phantasus.HeatMapColorScheme(project);
c.separateColorSchemeForRowMetadataField = this.separateColorSchemeForRowMetadataField;
if (c.separateColorSchemeForRowMetadataField != null) {
c.vector = project.getSortedFilteredDataset().getRowMetadata()
.getByName(c.separateColorSchemeForRowMetadataField);
}
if (c.vector == null) {
c.separateColorSchemeForRowMetadataField = null;
}
_.each(_.keys(this.rowValueToColorSupplier), function (key) {
c.rowValueToColorSupplier[key] = _this.rowValueToColorSupplier[key]
.copy();
});
c.value = this.value;
c.currentColorSupplier = c.rowValueToColorSupplier[c.value];
return c;
},
setSeparateColorSchemeForRowMetadataField: function (separateColorSchemeForRowMetadataField) {
if (separateColorSchemeForRowMetadataField != this.separateColorSchemeForRowMetadataField) {
this.separateColorSchemeForRowMetadataField = separateColorSchemeForRowMetadataField;
this.vector = this.project.getSortedFilteredDataset()
.getRowMetadata().getByName(
separateColorSchemeForRowMetadataField);
var _this = this;
_.each(_.keys(this.rowValueToColorSupplier), function (key) {
// remove old color schemes
delete _this.rowValueToColorSupplier[key];
});
}
},
getProject: function () {
return this.project;
},
getSeparateColorSchemeForRowMetadataField: function () {
return this.separateColorSchemeForRowMetadataField;
},
getColorByValues: function () {
return _.keys(this.rowValueToColorSupplier);
},
projectUpdated: function () {
var dataset = this.project.getSortedFilteredDataset();
if (this.separateColorSchemeForRowMetadataField != null) {
this.vector = this.project.getSortedFilteredDataset()
.getRowMetadata().getByName(
this.separateColorSchemeForRowMetadataField);
}
this.cachedRowStats = new phantasus.RowStats(dataset);
},
setColorSupplierForCurrentValue: function (colorSupplier) {
this.rowValueToColorSupplier[this.value] = colorSupplier;
this.currentColorSupplier = colorSupplier;
},
setCurrentValue: function (value) {
this.value = value;
this._ensureColorSupplierExists();
},
isSizeBy: function () {
this.currentColorSupplier.isSizeBy();
},
getCurrentColorSupplier: function () {
return this.currentColorSupplier;
},
getColor: function (row, column, val) {
if (this.vector !== undefined) {
var tmp = this.vector.getValue(row);
if (this.value !== tmp) {
this.value = tmp;
this._ensureColorSupplierExists();
}
}
if (this.currentColorSupplier.getScalingMode() === phantasus.HeatMapColorScheme.ScalingMode.RELATIVE) {
if (this.cachedRowStats.maybeUpdateRelative(row)) {
this.currentColorSupplier
.setMin(this.cachedRowStats.rowCachedMin);
this.currentColorSupplier
.setMax(this.cachedRowStats.rowCachedMax);
}
} else if (this.currentColorSupplier.getTransformValues() && this.cachedRowStats.cachedRow !== row) {
this.cachedRowStats.cacheTransformValues(row, this.currentColorSupplier.getTransformValues());
val = (val - this.cachedRowStats.rowCachedMean) / this.cachedRowStats.rowCachedStandardDeviation;
}
return this.currentColorSupplier.getColor(row, column, val);
},
/**
* @private
*/
_ensureColorSupplierExists: function () {
this.currentColorSupplier = this.rowValueToColorSupplier[this.value];
if (this.currentColorSupplier === undefined) {
var cs = phantasus.AbstractColorSupplier.fromJSON({
scalingMode: 'relative'
});
this.rowValueToColorSupplier[this.value] = cs;
this.currentColorSupplier = cs;
}
}
};
phantasus.RowStats = function (dataset) {
this.datasetRowView = new phantasus.DatasetRowView(dataset);
this.cachedRow = -1;
this.rowCachedMax = 0;
this.rowCachedMin = 0;
this.rowCachedStandardDeviation = -1;
this.rowCachedMean = -1;
};
phantasus.RowStats.prototype = {
cacheTransformValues: function (row, transform) {
var meanFunction = transform === phantasus.AbstractColorSupplier.Z_SCORE ? phantasus.Mean : phantasus.Median;
var stdevFunction = transform === phantasus.AbstractColorSupplier.Z_SCORE ? phantasus.StandardDeviation : phantasus.MAD;
this.datasetRowView.setIndex(row);
this.rowCachedMean = meanFunction(this.datasetRowView);
this.rowCachedStandardDeviation = stdevFunction(this.datasetRowView, this.rowCachedMean);
},
maybeUpdateRelative: function (row) {
if (this.cachedRow !== row) {
this.cachedRow = row;
this.datasetRowView.setIndex(row);
this.rowCachedMax = -Number.MAX_VALUE;
this.rowCachedMin = Number.MAX_VALUE;
for (var j = 0, ncols = this.datasetRowView.size(); j < ncols; j++) {
var d = this.datasetRowView.getValue(j);
if (!isNaN(d)) {
this.rowCachedMax = d > this.rowCachedMax ? d
: this.rowCachedMax;
this.rowCachedMin = d < this.rowCachedMin ? d
: this.rowCachedMin;
}
}
if (this.rowCachedMin === this.rowCachedMax) {
this.rowCachedMax = this.rowCachedMax*2;
this.rowCachedMin = 0;
if (this.rowCachedMax < this.rowCachedMin) {
var a = this.rowCachedMax;
this.rowCachedMax = this.rowCachedMin;
this.rowCachedMin = a;
}
}
return true;
}
return false;
}
};
phantasus.HeatMapSynchronizer = function () {
this.controllers = [];
};
phantasus.HeatMapSynchronizer.prototype = {
firing: false,
getProject: function () {
return this.controllers[0].getProject();
},
zoom: function () {
this.controllers[0].zoom.apply(this.controllers[0], arguments);
},
setTrackVisible: function () {
this.controllers[0].setTrackVisible.apply(this.controllers[0],
arguments);
},
revalidate: function () {
this.controllers[0].revalidate.apply(this.controllers[0], arguments);
},
add: function (heatMap) {
var that = this;
this.controllers.push(heatMap);
// setQuickSearchField, setTrackVisible, removeTrack, updateDataset, zoom, moveTrack, resizeTrack, paintAll, fitToWindow, revalidate, setToolTip, setMousePosition
heatMap.on('change', function (event) {
if (!that.firing) {
var source = event.source;
var method = event.name;
that.firing = true;
_.each(that.controllers, function (c) {
if (c !== source) {
c[method].apply(c, event.arguments);
}
});
that.firing = false;
}
});
}
};
phantasus.HeatMapElementCanvas = function (project) {
phantasus.AbstractCanvas.call(this, true);
var _this = this;
this.colorScheme = null;
this.project = project;
this.dataset = null;
this.columnPositions = new phantasus.Positions();
this.rowPositions = new phantasus.Positions();
this.lastPosition = {
left: -1,
right: -1,
top: -1,
bottom: -1
};
// drag to select rows and columns
this.selectionBox = null;
this.selectedRowElements = [];
this.selectedColumnElements = [];
project.getElementSelectionModel().on('selectionChanged', function (e) {
_this.repaint();
});
this.gridColor = phantasus.HeatMapElementCanvas.GRID_COLOR;
this.gridThickness = 0.1;
this.elementDrawCallback = null;
this.drawCallback = null;
this.drawValuesFormat = phantasus.Util.createNumberFormat('.5g');
};
phantasus.HeatMapElementCanvas.GRID_COLOR = '#808080';
phantasus.HeatMapElementCanvas.prototype = {
drawGrid: true,
drawValues: false,
setPropertiesFromParent: function (parentHeatMapElementCanvas) {
this.drawGrid = parentHeatMapElementCanvas.drawGrid;
this.gridThickness = parentHeatMapElementCanvas.gridThickness;
this.gridColor = parentHeatMapElementCanvas.gridColor;
this.drawValues = parentHeatMapElementCanvas.drawValues;
},
updateRowSelectionCache: function (repaint) {
this.selectedRowElements = phantasus.HeatMapElementCanvas.getSelectedSpans(this.project.getRowSelectionModel().getViewIndices());
if (repaint) {
this.repaint();
}
},
updateColumnSelectionCache: function (repaint) {
this.selectedColumnElements = phantasus.HeatMapElementCanvas.getSelectedSpans(this.project.getColumnSelectionModel().getViewIndices());
if (repaint) {
this.repaint();
}
},
setGridColor: function (gridColor) {
this.gridColor = gridColor;
},
getGridColor: function () {
return this.gridColor;
},
setGridThickness: function (gridThickness) {
this.gridThickness = gridThickness;
},
getGridThickness: function () {
return this.gridThickness;
},
getColorScheme: function () {
return this.colorScheme;
},
isDrawGrid: function () {
return this.drawGrid;
},
setDrawGrid: function (drawGrid) {
this.drawGrid = drawGrid;
},
getDrawValuesFormat: function () {
return this.drawValuesFormat;
},
setDrawValuesFormat: function (f) {
if (typeof f === 'object') { // convert to function
f = phantasus.Util.createNumberFormat(f.pattern);
}
this.drawValuesFormat = f;
},
setDrawValues: function (drawValues) {
this.drawValues = drawValues;
},
isDrawValues: function () {
return this.drawValues;
},
setColorScheme: function (colorScheme) {
this.colorScheme = colorScheme;
},
setDataset: function (dataset) {
this.dataset = dataset;
this.columnPositions.setLength(this.dataset.getColumnCount());
this.rowPositions.setLength(this.dataset.getRowCount());
this.updateRowSelectionCache(false);
this.updateColumnSelectionCache(false);
},
getColumnPositions: function () {
return this.columnPositions;
},
getRowPositions: function () {
return this.rowPositions;
},
getPreferredSize: function (context) {
var w = Math.ceil(this.columnPositions.getPosition(this.columnPositions
.getLength() - 1)
+ this.columnPositions.getItemSize(this.columnPositions
.getLength() - 1));
var h = Math.ceil(this.rowPositions.getPosition(this.rowPositions
.getLength() - 1)
+ this.rowPositions
.getItemSize(this.rowPositions.getLength() - 1));
return {
width: w,
height: h
};
},
prePaint: function (clip, context) {
var lastPosition = this.lastPosition;
var columnPositions = this.columnPositions;
var rowPositions = this.rowPositions;
var left = phantasus.Positions.getLeft(clip, columnPositions);
var right = phantasus.Positions.getRight(clip, columnPositions);
var top = phantasus.Positions.getTop(clip, rowPositions);
var bottom = phantasus.Positions.getBottom(clip, rowPositions);
if (this.invalid || left !== lastPosition.left
|| right !== lastPosition.right || top !== lastPosition.top
|| bottom !== lastPosition.bottom) {
lastPosition.right = right;
lastPosition.left = left;
lastPosition.top = top;
lastPosition.bottom = bottom;
this.invalid = true;
}
},
postPaint: function (clip, context) {
// draw mouse over stuff
phantasus.CanvasUtil.resetTransform(context);
var project = this.project;
context.strokeStyle = 'Grey';
context.lineWidth = 1;
var rowPositions = this.getRowPositions();
var columnPositions = this.getColumnPositions();
if (project.getHoverColumnIndex() >= 0
|| project.getHoverRowIndex() >= 0) {
var height = rowPositions
.getItemSize(project.getHoverColumnIndex());
var width = columnPositions.getItemSize(project
.getHoverColumnIndex());
var y = (project.getHoverRowIndex() === -1 ? rowPositions
.getPosition(rowPositions.getLength() - 1) : rowPositions
.getPosition(project.getHoverRowIndex()));
var x = (project.getHoverColumnIndex() === -1 ? columnPositions
.getPosition(0) : columnPositions.getPosition(project
.getHoverColumnIndex()));
if (project.getHoverColumnIndex() !== -1) {
// thin rectangle down entire column
context.strokeRect(x - clip.x, 0, width, this
.getUnscaledHeight());
}
if (project.getHoverRowIndex() !== -1) {
// thin rectangle across entire row
context.strokeRect(0, y - clip.y, this.getUnscaledWidth(),
height);
}
if (project.getHoverColumnIndex() !== -1
&& project.getHoverRowIndex() !== -1) {
context.strokeStyle = 'black';
context.lineWidth = 3;
context.strokeRect(x - clip.x + 1.5, y - clip.y + 1.5,
width - 1.5, height - 1.5);
if (project.isSymmetric()) {
var y2 = rowPositions.getPosition(project.getHoverColumnIndex());
var x2 = columnPositions.getPosition(project.getHoverRowIndex());
context.strokeRect(x2 - clip.x + 1.5, y2 - clip.y + 1.5,
width - 1.5, height - 1.5);
}
}
}
var left = phantasus.Positions.getLeft(clip, columnPositions);
var right = phantasus.Positions.getRight(clip, columnPositions);
var top = phantasus.Positions.getTop(clip, rowPositions);
var bottom = phantasus.Positions.getBottom(clip, rowPositions);
context.strokeStyle = 'rgb(0,0,0)';
context.lineWidth = 2;
// context.fillRect(0, 0, this.canvas.width, this.canvas.height);
context.translate(-clip.x, -clip.y);
var selectedElements = project.getElementSelectionModel()
.getViewIndices();
if (selectedElements != null) {
selectedElements.forEach(function (id) {
var rowIndex = id.getArray()[0];
var columnIndex = id.getArray()[1];
if (rowIndex >= top && rowIndex < bottom && columnIndex >= left
&& columnIndex < right) {
var rowSize = rowPositions.getItemSize(rowIndex);
var py = rowPositions.getPosition(rowIndex);
var columnSize = columnPositions.getItemSize(columnIndex);
var px = columnPositions.getPosition(columnIndex);
context.strokeRect(px + 1.5, py + 1.5, columnSize - 1.5,
rowSize - 1.5);
}
});
}
// draw selection bounding boxes
// context.strokeStyle = 'rgb(182,213,253)';
context.strokeStyle = 'rgb(60,60,60)';
var selectedRowElements = this.selectedRowElements;
var selectedColumnElements = this.selectedColumnElements;
if (!(selectedRowElements.length === 0 &&
selectedColumnElements.length === 0)) {
if (selectedRowElements.length === 0) {
selectedRowElements = [[top, bottom - 1]];
}
if (selectedColumnElements.length === 0) {
selectedColumnElements = [[left, right - 1]];
}
}
var nrows = selectedRowElements.length;
var ncols = selectedColumnElements.length;
if (nrows !== 0 || ncols !== 0) {
for (var i = 0; i < nrows; i++) {
var r = selectedRowElements[i];
var y1 = rowPositions.getPosition(r[0]);
var y2 = rowPositions.getPosition(r[1]) + rowPositions.getItemSize(i);
for (var j = 0; j < ncols; j++) {
var c = selectedColumnElements[j];
var x1 = columnPositions.getPosition(c[0]);
var x2 = columnPositions.getPosition(c[1]) + columnPositions.getItemSize(j);
if (y2 - y1 >= 4) {
context.strokeRect(x1, y1, x2 - x1, y2 - y1);
}
}
}
}
if (this.selectionBox) {
context.strokeStyle = 'rgb(0,0,0)';
context.lineWidth = 2;
if (context.setLineDash) {
context.setLineDash([5]);
}
var x1 = columnPositions.getPosition(this.selectionBox.x[0]);
var x2 = columnPositions.getPosition(this.selectionBox.x[1]);
if (x2 < x1) {
var tmp = x1;
x1 = x2;
x2 = tmp + columnPositions.getItemSize(this.selectionBox.x[0]);
} else {
x2 += columnPositions.getItemSize(this.selectionBox.x[1]);
}
var y1 = rowPositions.getPosition(this.selectionBox.y[0]);
var y2 = rowPositions.getPosition(this.selectionBox.y[1]);
if (y2 < y1) {
var tmp = y1;
y1 = y2;
y2 = tmp + rowPositions.getItemSize(this.selectionBox.y[0]);
} else {
y2 += rowPositions.getItemSize(this.selectionBox.y[1]);
}
context.strokeRect(x1, y1, x2 - x1, y2 - y1);
if (context.setLineDash) {
context.setLineDash([]);
}
context.lineWidth = 1;
}
},
setElementDrawCallback: function (elementDrawCallback) {
this.elementDrawCallback = elementDrawCallback;
},
setSelectionBox: function (selectionBox) {
this.selectionBox = selectionBox;
},
setDrawCallback: function (drawCallback) {
this.drawCallback = drawCallback;
},
draw: function (clip, context) {
var columnPositions = this.columnPositions;
var rowPositions = this.rowPositions;
var left = phantasus.Positions.getLeft(clip, columnPositions);
var right = phantasus.Positions.getRight(clip, columnPositions);
var top = phantasus.Positions.getTop(clip, rowPositions);
var bottom = phantasus.Positions.getBottom(clip, rowPositions);
context.translate(-clip.x, -clip.y);
this._draw({
left: left,
right: right,
top: top,
bottom: bottom,
context: context
});
context.translate(clip.x, clip.y);
if (this.drawCallback) {
this.drawCallback({
clip: clip,
context: context
});
}
},
_draw: function (options) {
var left = options.left;
var right = options.right;
var top = options.top;
var bottom = options.bottom;
var context = options.context;
var fontFamily = phantasus.CanvasUtil.getFontFamily(context);
var columnPositions = this.columnPositions;
var rowPositions = this.rowPositions;
//if (rowPositions.getSize() < 1 || columnPositions.getSize() < 1) {
//force sub-pixel rendering
phantasus.CanvasUtil.forceSubPixelRendering(context);
//}
context.textAlign = 'center';
context.textBaseline = 'middle';
var dataset = this.dataset;
var colorScheme = this.colorScheme;
var drawGrid = this.drawGrid;
var elementDrawCallback = this.elementDrawCallback;
var hasElementDrawCallback = elementDrawCallback != null;
var drawValues = this.drawValues && columnPositions.getSize() > 7 && rowPositions.getSize() > 7;
var nf;
if (drawValues) {
nf = this.drawValuesFormat;
var fontSize = columnPositions.getSize();
context.font = fontSize + 'px ' + fontFamily;
var textWidth = context.measureText('-99.9').width;
fontSize = ( (columnPositions.getSize() - 1) / textWidth) * fontSize;
fontSize = Math.min(fontSize, 17);
context.font = fontSize + 'px ' + phantasus.CanvasUtil.getFontFamily(context);
}
var seriesNameToIndex = {};
for (var i = 0; i < dataset.getSeriesCount(); i++) {
seriesNameToIndex[dataset.getName(i)] = i;
}
var sizer;
var sizeBySeriesName;
var sizeBySeriesIndex;
var conditions;
var conditionSeriesIndices;
var sizeFractionRemapper = d3.scale.linear().domain([0, 1]).range([0.2, 1]);
for (var row = top; row < bottom; row++) {
var rowSize = rowPositions.getItemSize(row);
var py = rowPositions.getPosition(row);
for (var column = left; column < right; column++) {
var columnSize = columnPositions.getItemSize(column);
var px = columnPositions.getPosition(column);
var value = dataset.getValue(row, column);
context.fillStyle = colorScheme.getColor(row, column, value);
if (column === left) { // check if the color scheme for this
// row is sizing
sizer = colorScheme.getSizer();
sizeBySeriesName = sizer.getSeriesName();
sizeBySeriesIndex = sizeBySeriesName != null ? seriesNameToIndex[sizeBySeriesName]
: undefined;
conditionSeriesIndices = [];
conditions = colorScheme.getConditions().getConditions();
for (var ci = 0, nconditions = conditions.length; ci < nconditions; ci++) {
conditionSeriesIndices
.push(seriesNameToIndex[conditions[ci].seriesName]);
}
}
var yoffset = 0;
var xoffset = 0;
var cellRowSize = rowSize;
var cellColumnSize = columnSize;
if (sizeBySeriesIndex !== undefined) {
var sizeByValue = dataset.getValue(row, column,
sizeBySeriesIndex);
if (!isNaN(sizeByValue)) {
var sizeFraction = sizeFractionRemapper(sizer.valueToFraction(sizeByValue)); // remap 0-1 to 0.2-1
cellRowSize = cellRowSize * sizeFraction;
yoffset = (rowSize - cellRowSize) / 2;
cellColumnSize = cellColumnSize * sizeFraction;
xoffset = (columnSize - cellColumnSize) / 2;
}
}
if (conditions.length > 0) {
var condition = null;
for (var ci = 0, nconditions = conditions.length; ci < nconditions; ci++) {
var cond = conditions[ci];
var condValue = dataset.getValue(row, column,
conditionSeriesIndices[ci]);
if (!isNaN(condValue) && cond.accept(condValue)) {
condition = cond;
break;
}
}
if (condition !== null) {
if (condition.shape != null) {
if (condition.inheritColor) {
if (sizeBySeriesIndex === undefined) {
xoffset = 1;
yoffset = 1;
cellRowSize -= 2;
cellColumnSize -= 2;
}
var x = px + xoffset + cellRowSize / 2;
var y = py + yoffset + cellColumnSize / 2;
phantasus.CanvasUtil.drawShape(context, condition.shape,
x, y, Math.min(cellColumnSize, cellRowSize) / 2, true);
} else { // e.g. filled circle on top of heat map
context.fillRect(px + xoffset, py + yoffset, cellColumnSize,
cellRowSize);
// x and y are at center
var x = px + xoffset + cellRowSize / 2;
var y = py + yoffset + cellColumnSize / 2;
context.fillStyle = condition.color;
phantasus.CanvasUtil.drawShape(context, condition.shape,
x, y, Math.min(cellColumnSize, cellRowSize) / 4, true);
}
} else {
context.fillRect(px + xoffset, py + yoffset, cellColumnSize,
cellRowSize);
}
} else {
context.fillRect(px + xoffset, py + yoffset, cellColumnSize,
cellRowSize);
}
} else {
context.fillRect(px + xoffset, py + yoffset, cellColumnSize, cellRowSize);
}
if (drawValues && cellColumnSize > 7 && cellRowSize > 7 && !isNaN(value)) {
context.fillStyle = 'rgb(0,0,0)';
context.fillText(nf(value), px + xoffset + cellColumnSize / 2, py + yoffset + cellRowSize / 2, cellColumnSize);
}
if (hasElementDrawCallback) {
elementDrawCallback(context, dataset, row, column, px, py,
columnSize, rowSize);
}
}
}
if (drawGrid && rowPositions.getSize() > 10 && columnPositions.getSize() > 10) {
context.strokeStyle = this.gridColor;
context.lineWidth = this.gridThickness;
context.beginPath();
for (var row = top; row < bottom; row++) {
var rowSize = rowPositions.getItemSize(row);
var py = rowPositions.getPosition(row);
for (var column = left; column < right; column++) {
var columnSize = columnPositions.getItemSize(column);
var px = columnPositions.getPosition(column);
var grid = columnSize > 10 && rowSize > 10;
if (grid) {
context.rect(px, py, columnSize, rowSize);
}
}
}
context.stroke();
}
context.lineWidth = 1;
}
};
phantasus.Util.extend(phantasus.HeatMapElementCanvas, phantasus.AbstractCanvas);
phantasus.HeatMapElementCanvas.getSelectedSpans = function (set) {
var array = [];
if (set.size() > 0) {
var index = 0;
var start = index;
var viewIndices = set.values();
viewIndices.sort(function (a, b) {
return (a === b ? 0 : (a < b ? -1 : 1));
});
var length = viewIndices.length;
while (index < length) {
var prior = index === 0 ? viewIndices[0] : viewIndices[index - 1];
var current = viewIndices[index];
if ((current - prior) > 1) {
array.push([viewIndices[start], viewIndices[index - 1]]);
start = index;
}
index++;
}
if (start == 0) {
array.push([viewIndices[0], viewIndices[viewIndices.length - 1]]);
} else {
array.push([viewIndices[start], viewIndices[index - 1]]);
}
}
return array;
};
phantasus.KeyboardCharMap = [
'', // [0]
'', // [1]
'', // [2]
'CANCEL', // [3]
'', // [4]
'', // [5]
'HELP', // [6]
'', // [7]
'BACKSPACE', // [8]
'TAB', // [9]
'', // [10]
'', // [11]
'CLEAR', // [12]
'ENTER', // [13]
'ENTER_SPECIAL', // [14]
'', // [15]
'SHIFT', // [16]
'CONTROL', // [17]
'ALT', // [18]
'PAUSE', // [19]
'CAPS_LOCK', // [20]
'KANA', // [21]
'EISU', // [22]
'JUNJA', // [23]
'FINAL', // [24]
'HANJA', // [25]
'', // [26]
'Escape', // [27]
'CONVERT', // [28]
'NONCONVERT', // [29]
'ACCEPT', // [30]
'MODECHANGE', // [31]
'Space', // [32]
'Page Up', // [33]
'Page Down', // [34]
'End', // [35]
'Home', // [36]
'Left', // [37]
'Up', // [38]
'Right', // [39]
'Down', // [40]
'SELECT', // [41]
'PRINT', // [42]
'EXECUTE', // [43]
'PRINTSCREEN', // [44]
'INSERT', // [45]
'Delete', // [46]
'', // [47]
'0', // [48]
'1', // [49]
'2', // [50]
'3', // [51]
'4', // [52]
'5', // [53]
'6', // [54]
'7', // [55]
'8', // [56]
'9', // [57]
'COLON', // [58]
'SEMICOLON', // [59]
'LESS_THAN', // [60]
'Equals', // [61]
'GREATER_THAN', // [62]
'QUESTION_MARK', // [63]
'AT', // [64]
'A', // [65]
'B', // [66]
'C', // [67]
'D', // [68]
'E', // [69]
'F', // [70]
'G', // [71]
'H', // [72]
'I', // [73]
'J', // [74]
'K', // [75]
'L', // [76]
'M', // [77]
'N', // [78]
'O', // [79]
'P', // [80]
'Q', // [81]
'R', // [82]
'S', // [83]
'T', // [84]
'U', // [85]
'V', // [86]
'W', // [87]
'X', // [88]
'Y', // [89]
'Z', // [90]
'OS_KEY', // [91] Windows Key (Windows) or Command Key (Mac)
'', // [92]
'CONTEXT_MENU', // [93]
'', // [94]
'SLEEP', // [95]
'0', // [96]
'1', // [97]
'2', // [98]
'3', // [99]
'4', // [100]
'5', // [101]
'6', // [102]
'7', // [103]
'8', // [104]
'9', // [105]
'MULTIPLY', // [106]
'+', // [107]
'SEPARATOR', // [108]
'SUBTRACT', // [109]
'DECIMAL', // [110]
'DIVIDE', // [111]
'F1', // [112]
'F2', // [113]
'F3', // [114]
'F4', // [115]
'F5', // [116]
'F6', // [117]
'F7', // [118]
'F8', // [119]
'F9', // [120]
'F10', // [121]
'F11', // [122]
'F12', // [123]
'F13', // [124]
'F14', // [125]
'F15', // [126]
'F16', // [127]
'F17', // [128]
'F18', // [129]
'F19', // [130]
'F20', // [131]
'F21', // [132]
'F22', // [133]
'F23', // [134]
'F24', // [135]
'', // [136]
'', // [137]
'', // [138]
'', // [139]
'', // [140]
'', // [141]
'', // [142]
'', // [143]
'NUM_LOCK', // [144]
'SCROLL_LOCK', // [145]
'WIN_OEM_FJ_JISHO', // [146]
'WIN_OEM_FJ_MASSHOU', // [147]
'WIN_OEM_FJ_TOUROKU', // [148]
'WIN_OEM_FJ_LOYA', // [149]
'WIN_OEM_FJ_ROYA', // [150]
'', // [151]
'', // [152]
'', // [153]
'', // [154]
'', // [155]
'', // [156]
'', // [157]
'', // [158]
'', // [159]
'CIRCUMFLEX', // [160]
'EXCLAMATION', // [161]
'DOUBLE_QUOTE', // [162]
'HASH', // [163]
'DOLLAR', // [164]
'PERCENT', // [165]
'AMPERSAND', // [166]
'UNDERSCORE', // [167]
'OPEN_PAREN', // [168]
'CLOSE_PAREN', // [169]
'ASTERISK', // [170]
'Plus', // [171]
'PIPE', // [172]
'-', // [173]
'OPEN_CURLY_BRACKET', // [174]
'CLOSE_CURLY_BRACKET', // [175]
'TILDE', // [176]
'', // [177]
'', // [178]
'', // [179]
'', // [180]
'VOLUME_MUTE', // [181]
'VOLUME_DOWN', // [182]
'VOLUME_UP', // [183]
'', // [184]
'', // [185]
'SEMICOLON', // [186]
'EQUALS', // [187]
'COMMA', // [188]
'MINUS', // [189]
'PERIOD', // [190]
'/', // [191]
'BACK_QUOTE', // [192]
'', // [193]
'', // [194]
'', // [195]
'', // [196]
'', // [197]
'', // [198]
'', // [199]
'', // [200]
'', // [201]
'', // [202]
'', // [203]
'', // [204]
'', // [205]
'', // [206]
'', // [207]
'', // [208]
'', // [209]
'', // [210]
'', // [211]
'', // [212]
'', // [213]
'', // [214]
'', // [215]
'', // [216]
'', // [217]
'', // [218]
'OPEN_BRACKET', // [219]
'BACK_SLASH', // [220]
'CLOSE_BRACKET', // [221]
'QUOTE', // [222]
'', // [223]
'META', // [224]
'ALTGR', // [225]
'', // [226]
'WIN_ICO_HELP', // [227]
'WIN_ICO_00', // [228]
'', // [229]
'WIN_ICO_CLEAR', // [230]
'', // [231]
'', // [232]
'WIN_OEM_RESET', // [233]
'WIN_OEM_JUMP', // [234]
'WIN_OEM_PA1', // [235]
'WIN_OEM_PA2', // [236]
'WIN_OEM_PA3', // [237]
'WIN_OEM_WSCTRL', // [238]
'WIN_OEM_CUSEL', // [239]
'WIN_OEM_ATTN', // [240]
'WIN_OEM_FINISH', // [241]
'WIN_OEM_COPY', // [242]
'WIN_OEM_AUTO', // [243]
'WIN_OEM_ENLW', // [244]
'WIN_OEM_BACKTAB', // [245]
'ATTN', // [246]
'CRSEL', // [247]
'EXSEL', // [248]
'EREOF', // [249]
'PLAY', // [250]
'ZOOM', // [251]
'', // [252]
'PA1', // [253]
'WIN_OEM_CLEAR', // [254]
'' // [255]
];
phantasus.HeatMapKeyListener = function (heatMap) {
var allActions = heatMap.getActionManager().getActions();
var actions = allActions.filter(function (a) {
return a.cb != null && a.which != null;
});
allActions.sort(function (a, b) {
a = a.name.toLowerCase();
b = b.name.toLowerCase();
return (a === b ? 0 : (a < b ? -1 : 1));
});
var keydown = function (e) {
var tagName = e.target.tagName;
var found = false;
var commandKey = phantasus.Util.IS_MAC ? e.metaKey : e.ctrlKey;
var altKey = e.altKey;
var shiftKey = e.shiftKey;
var which = e.which;
var isInputField = (tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA');
var acceptOptions = {
isInputField: isInputField,
heatMap: heatMap
};
var shortcutMatches = function (sc) {
if (sc.which.indexOf(which) !== -1 && (sc.commandKey === undefined || commandKey === sc.commandKey) && (sc.shiftKey === undefined || shiftKey === sc.shiftKey) &&
(sc.accept == undefined || sc.accept(acceptOptions))) {
sc.cb({heatMap: heatMap});
return true;
}
};
if (!isInputField) {
for (var i = 0, n = actions.length; i < n; i++) {
var sc = actions[i];
if (shortcutMatches(sc)) {
found = true;
break;
}
}
} else { // only search global shortcuts
for (var i = 0, n = actions.length; i < n; i++) {
var sc = actions[i];
if (sc.global && shortcutMatches(sc)) {
found = true;
break;
}
}
}
if (found) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return false;
}
};
var $keyelement = heatMap.$tabPanel;
$keyelement.on('keydown', keydown);
$keyelement.on('dragover.phantasus dragenter.phantasus', function (e) {
e.preventDefault();
e.stopPropagation();
}).on(
'drop.phantasus',
function (e) {
if (heatMap.options.menu.File && heatMap.options.menu.File.indexOf('Open') !== -1 && e.originalEvent.dataTransfer
&& e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
var files = e.originalEvent.dataTransfer.files;
for (var i = 0; i < files.length; i++) {
phantasus.HeatMap.showTool(new phantasus.OpenFileTool({
file: files[i]
}), heatMap);
}
}
});
$keyelement.on('paste.phantasus',
function (e) {
if (heatMap.options.toolbar.openFile) {
var tagName = e.target.tagName;
if (tagName == 'INPUT' || tagName == 'SELECT'
|| tagName == 'TEXTAREA') {
return;
}
var text = e.originalEvent.clipboardData.getData('text/plain');
if (text != null && text.length > 0) {
e.preventDefault();
e.stopPropagation();
var blob = new Blob([text]);
var url = window.URL.createObjectURL(blob);
phantasus.HeatMap.showTool(new phantasus.OpenFileTool({
file: url
}), heatMap);
}
}
});
$keyelement.on('mousewheel', function (e) {
var scrolly = e.deltaY * e.deltaFactor;
var scrollx = e.deltaX * e.deltaFactor;
var stop = false;
if (e.altKey) {
heatMap.zoom(scrolly > 0, {
rows: true,
columns: true
});
stop = true;
} else {
if (scrolly !== 0) {
var scrollTop = heatMap.scrollTop();
if (heatMap.scrollTop(scrollTop - scrolly) !== scrollTop) {
stop = true;
}
}
if (scrollx !== 0) {
var scrollLeft = heatMap.scrollLeft();
if (heatMap.scrollLeft(scrollLeft + scrollx) !== scrollLeft) {
stop = true;
}
}
}
if (stop && heatMap.options.standalone) {
e.preventDefault();
e.stopPropagation();
}
});
function shortcutToString(sc) {
var s = ['<b>'];
if (sc.commandKey) {
s.push(phantasus.Util.COMMAND_KEY);
}
if (sc.shiftKey) {
s.push('Shift+');
}
if (sc.which) {
s.push(phantasus.KeyboardCharMap[sc.which[0]]);
}
s.push('</b>');
return s.join('');
}
this.showKeyMapReference = function () {
var html = [];
html.push('<table class="table table-condensed">');
allActions.forEach(function (sc) {
html.push('<tr><td>');
html.push(shortcutToString(sc));
html.push('</td><td>');
if (sc.icon) {
html.push('<span class="' + sc.icon + '"></span> ');
}
html.push(sc.name);
html.push('</td></tr>');
});
html.push('</table>');
phantasus.FormBuilder.showInModal({
title: 'Keymap Shortcuts',
html: html.join(''),
focus: document.activeElement
});
};
};
phantasus.HeatMapOptions = function (heatMap) {
var items = [
{
name: 'color_by',
required: true,
help: 'Use a different color scheme for distinct row annotation values',
type: 'select',
options: ['(None)'].concat(phantasus.MetadataUtil
.getMetadataNames(heatMap.getProject()
.getFullDataset().getRowMetadata())),
value: heatMap.heatmap.getColorScheme()
.getSeparateColorSchemeForRowMetadataField()
}, {
name: 'color_by_value',
required: true,
type: 'select',
options: []
}];
items.push({
name: 'size_by',
required: true,
type: 'select',
options: ['(None)'].concat(phantasus.DatasetUtil
.getSeriesNames(heatMap.getProject().getFullDataset()))
});
items.push({
name: 'size_by_minimum',
title: 'Size by minimum',
required: true,
type: 'text',
style: 'max-width: 100px;'
});
items.push({
name: 'size_by_maximum',
title: 'Size by maximum',
required: true,
type: 'text',
style: 'max-width: 100px;'
});
items.push({
name: 'conditional_rendering',
required: true,
type: 'button'
});
items.push({type: 'separator'});
var createColorSchemeOptions = function () {
var colorSchemeOptions = [
{
name: 'relative',
value: 'relative'
}, {
name: 'binary',
value: 'binary'
}, {
name: 'MAF',
value: 'MAF'
}, {
name: 'fixed (-1.5, -0.1, 0.1, 1.5)',
value: 'cn'
}];
var savedColorSchemeKeys = [];
if (localStorage.getItem('phantasus-colorScheme') != null) {
savedColorSchemeKeys = _.keys(JSON.parse(localStorage.getItem('phantasus-colorScheme')));
}
if (savedColorSchemeKeys.length > 0) {
colorSchemeOptions.push({divider: true});
colorSchemeOptions = colorSchemeOptions.concat(savedColorSchemeKeys);
}
colorSchemeOptions.push({divider: true});
colorSchemeOptions.push('My Computer...');
return colorSchemeOptions;
};
items.push([
{
name: 'saved_color_scheme',
required: true,
type: 'bootstrap-select',
options: createColorSchemeOptions()
}, {name: 'load_color_scheme', type: 'button'}, {name: 'delete_color_scheme', type: 'button'}]);
items.push({
name: 'save_color_scheme',
type: 'button'
});
var displayItems = [
{
disabled: heatMap.getProject().getFullDataset().getColumnCount() !== heatMap.getProject().getFullDataset().getRowCount(),
name: 'link_rows_and_columns',
help: 'For square matrices',
required: true,
type: 'checkbox',
style: 'max-width: 100px;',
value: heatMap.getProject().isSymmetric()
},
{
name: 'show_row_number',
required: true,
type: 'checkbox',
value: heatMap.isShowRowNumber()
},
{
name: 'show_grid',
required: true,
type: 'checkbox',
value: heatMap.heatmap.isDrawGrid()
},
{
name: 'grid_thickness',
required: true,
type: 'text',
style: 'max-width: 100px;',
value: phantasus.Util.nf(heatMap.heatmap.getGridThickness())
},
{
name: 'grid_color',
required: true,
type: 'color',
style: 'max-width: 50px;',
value: heatMap.heatmap.getGridColor()
},
{
name: 'row_size',
required: true,
type: 'text',
style: 'max-width: 100px;',
value: phantasus.Util.nf(heatMap.heatmap.getRowPositions()
.getSize())
},
{
name: 'column_size',
required: true,
type: 'text',
style: 'max-width: 100px;',
value: phantasus.Util.nf(heatMap.heatmap
.getColumnPositions().getSize())
}, {
name: 'show_values',
required: true,
type: 'checkbox',
value: heatMap.heatmap.isDrawValues()
}, {
name: 'number_of_fraction_digits',
required: true,
type: 'number',
min: 0,
step: 1,
style: 'max-width: 100px;',
value: phantasus.Util.getNumberFormatPatternFractionDigits(heatMap.heatmap.getDrawValuesFormat().toJSON().pattern)
}];
if (heatMap.rowDendrogram) {
displayItems
.push({
name: 'row_dendrogram_line_thickness',
required: true,
type: 'text',
style: 'max-width: 100px;',
value: phantasus.Util
.nf(heatMap.rowDendrogram ? heatMap.rowDendrogram.lineWidth
: 1)
});
}
if (heatMap.columnDendrogram) {
displayItems
.push({
name: 'column_dendrogram_line_thickness',
required: true,
type: 'text',
style: 'max-width: 100px;',
value: phantasus.Util
.nf(heatMap.columnDendrogram ? heatMap.columnDendrogram.lineWidth
: 1)
});
}
displayItems.push({
name: 'info_window',
required: true,
type: 'select',
style: 'max-width:130px;',
options: [
{
name: 'Fixed To Top',
value: 0
}, {
name: 'New Window',
value: 1
}],
value: heatMap.tooltipMode
});
displayItems.push({
name: 'inline_tooltip',
required: true,
type: 'checkbox',
value: heatMap.options.inlineTooltip
});
var colorSchemeFormBuilder = new phantasus.FormBuilder();
_.each(items, function (item) {
colorSchemeFormBuilder.append(item);
});
var displayFormBuilder = new phantasus.FormBuilder();
_.each(displayItems, function (item) {
displayFormBuilder.append(item);
});
var colorSchemeChooser = new phantasus.HeatMapColorSchemeChooser({
showRelative: true,
colorScheme: heatMap.heatmap
.getColorScheme()
});
var updatingSizer = false;
function colorSchemeChooserUpdated() {
if (heatMap.heatmap.getColorScheme().getSizer
&& heatMap.heatmap.getColorScheme().getSizer() != null) {
colorSchemeFormBuilder.setValue('size_by', heatMap.heatmap
.getColorScheme().getSizer().getSeriesName());
colorSchemeFormBuilder.setEnabled('size_by_minimum',
heatMap.heatmap.getColorScheme().getSizer()
.getSeriesName() != null);
colorSchemeFormBuilder.setEnabled('size_by_maximum',
heatMap.heatmap.getColorScheme().getSizer()
.getSeriesName() != null);
if (!updatingSizer) {
colorSchemeFormBuilder.setValue('size_by_minimum',
heatMap.heatmap.getColorScheme().getSizer().getMin());
colorSchemeFormBuilder.setValue('size_by_maximum',
heatMap.heatmap.getColorScheme().getSizer().getMax());
}
}
}
colorSchemeChooser.on('change', function () {
colorSchemeChooserUpdated();
// repaint the heat map when color scheme changes
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
colorSchemeChooser.restoreCurrentValue();
});
function createMetadataField(isColumns) {
var options = [];
var value = {};
_.each(heatMap.getVisibleTrackNames(isColumns), function (name) {
value[name] = true;
});
_.each(phantasus.MetadataUtil.getMetadataNames(isColumns ? heatMap
.getProject().getFullDataset().getColumnMetadata() : heatMap
.getProject().getFullDataset().getRowMetadata()),
function (name) {
options.push(name);
});
var field = {
type: 'bootstrap-select',
search: options.length > 10,
name: isColumns ? 'column_annotations' : 'row_annotations',
multiple: true,
value: value,
options: options,
toggle: true
};
return field;
}
var annotationsBuilder = new phantasus.FormBuilder();
annotationsBuilder.append(createMetadataField(false));
annotationsBuilder.append(createMetadataField(true));
function annotationsListener($select, isColumns) {
var names = [];
_.each(heatMap.getVisibleTrackNames(isColumns), function (name) {
names.push(name);
});
var values = $select.val();
var selectedNow = _.difference(values, names);
var unselectedNow = _.difference(names, values);
var tracks = [];
_.each(selectedNow, function (name) {
tracks.push({
name: name,
isColumns: isColumns,
visible: true
});
});
_.each(unselectedNow, function (name) {
tracks.push({
name: name,
isColumns: isColumns,
visible: false
});
});
heatMap.setTrackVisibility(tracks);
colorSchemeChooser.restoreCurrentValue();
}
var $ca = annotationsBuilder.$form.find('[name=column_annotations]');
$ca.on('change', function (e) {
annotationsListener($(this), true);
});
var $ra = annotationsBuilder.$form.find('[name=row_annotations]');
$ra.on('change', function (e) {
annotationsListener($(this), false);
});
var annotationOptionsTabId = _.uniqueId('phantasus');
var heatMapOptionsTabId = _.uniqueId('phantasus');
var displayOptionsTabId = _.uniqueId('phantasus');
var $metadataDiv = $('<div class="tab-pane" id="' + annotationOptionsTabId
+ '"></div>');
$metadataDiv.append($(annotationsBuilder.$form));
var $heatMapDiv = $('<div class="tab-pane active" id="'
+ heatMapOptionsTabId + '"></div>');
$heatMapDiv.append(colorSchemeChooser.$div);
$heatMapDiv.append($(colorSchemeFormBuilder.$form));
var $displayDiv = $('<div class="tab-pane" id="' + displayOptionsTabId
+ '"></div>');
$displayDiv.append($(displayFormBuilder.$form));
displayFormBuilder.setEnabled('grid_thickness', heatMap.heatmap.isDrawGrid());
displayFormBuilder.setEnabled('grid_color', heatMap.heatmap.isDrawGrid());
displayFormBuilder.$form.find('[name=show_grid]').on('click', function (e) {
var grid = $(this).prop('checked');
displayFormBuilder.setEnabled('grid_thickness', grid);
displayFormBuilder.setEnabled('grid_color', grid);
heatMap.heatmap.setDrawGrid(grid);
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
});
var $fractionDigits = displayFormBuilder.$form.find('[name=number_of_fraction_digits]');
displayFormBuilder.$form.find('[name=show_values]').on('click', function (e) {
var drawValues = $(this).prop('checked');
heatMap.heatmap.setDrawValues(drawValues);
// $fractionDigits.prop('disabled', !drawValues);
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
});
$fractionDigits.on(
'keyup input', _.debounce(
function () {
var n = parseInt($(this)
.val());
if (n >= 0) {
heatMap.heatmap.setDrawValuesFormat(phantasus.Util.createNumberFormat('.' + n + 'f'));
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
}
}, 100));
displayFormBuilder.$form.find('[name=inline_tooltip]').on('click',
function (e) {
heatMap.options.inlineTooltip = $(this).prop('checked');
});
displayFormBuilder.$form.find('[name=grid_color]').on(
'change',
function (e) {
var value = $(this).val();
heatMap.heatmap.setGridColor(value);
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
});
displayFormBuilder.$form.find('[name=grid_thickness]').on(
'keyup',
_.debounce(function (e) {
var value = parseFloat($(this).val());
if (!isNaN(value)) {
heatMap.heatmap.setGridThickness(value);
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
}
}, 100));
displayFormBuilder.$form.find('[name=row_size]').on(
'keyup',
_.debounce(function (e) {
var value = parseFloat($(this).val());
if (!isNaN(value)) {
heatMap.heatmap.getRowPositions().setSize(
value);
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
}
}, 100));
displayFormBuilder.$form.find('[name=info_window]').on('change',
function (e) {
heatMap.setTooltipMode(parseInt($(this).val()));
});
displayFormBuilder.find('link_rows_and_columns').on('click',
function (e) {
var checked = $(this).prop('checked');
if (checked) {
heatMap.getProject().setSymmetric(heatMap);
} else {
heatMap.getProject().setSymmetric(null);
}
});
displayFormBuilder.find('show_row_number').on('click',
function (e) {
var checked = $(this).prop('checked');
heatMap.setShowRowNumber(checked);
heatMap.revalidate();
});
var $colorByValue = colorSchemeFormBuilder.$form
.find('[name=color_by_value]');
var separateSchemesField = heatMap.heatmap.getColorScheme()
.getSeparateColorSchemeForRowMetadataField();
if (separateSchemesField != null) {
var v = heatMap.project.getFullDataset().getRowMetadata()
.getByName(separateSchemesField);
if (v != null) {
$colorByValue.html(phantasus.Util.createOptions(phantasus.VectorUtil
.createValueToIndexMap(
v).keys()));
}
}
if (separateSchemesField != null) {
colorSchemeChooser.setCurrentValue($colorByValue.val());
}
if (heatMap.heatmap.getColorScheme().getSizer
&& heatMap.heatmap.getColorScheme().getSizer() != null
&& heatMap.heatmap.getColorScheme().getSizer().getSeriesName()) {
colorSchemeFormBuilder.setValue('size_by', heatMap.heatmap
.getColorScheme().getSizer().getSeriesName());
}
colorSchemeFormBuilder.$form.find('[name=size_by]')
.on(
'change',
function (e) {
var series = $(this).val();
if (series == '(None)') {
series = null;
}
colorSchemeChooser.colorScheme.getSizer()
.setSeriesName(series);
colorSchemeChooser.fireChanged();
});
colorSchemeFormBuilder.$form.find('[name=size_by_minimum]').on(
'keyup',
_.debounce(function (e) {
updatingSizer = true;
colorSchemeChooser.colorScheme.getSizer().setMin(
parseFloat($(this).val()));
colorSchemeChooser.fireChanged(true);
updatingSizer = false;
}, 100));
colorSchemeFormBuilder.$form.find('[name=size_by_maximum]').on(
'keyup',
_.debounce(function (e) {
updatingSizer = true;
colorSchemeChooser.colorScheme.getSizer().setMax(
parseFloat($(this).val()));
colorSchemeChooser.fireChanged(true);
updatingSizer = false;
}, 100));
colorSchemeFormBuilder.$form
.find('[name=conditional_rendering]')
.on(
'click',
function (e) {
e.preventDefault();
var conditionalRenderingUI = new phantasus.ConditionalRenderingUI(
heatMap);
phantasus.FormBuilder.showInModal({
title: 'Conditional Rendering',
html: conditionalRenderingUI.$div,
close: 'Close',
modalClass: 'phantasus-sub-modal'
});
});
colorSchemeFormBuilder.find('save_color_scheme').on('click', function (e) {
e.preventDefault();
// prompt to save to file or local storage
var saveColorSchemeFormBuilder = new phantasus.FormBuilder();
saveColorSchemeFormBuilder.append({name: 'save_to', type: 'radio', value: 'Browser Storage', options: ['Browser Storage', 'File']});
saveColorSchemeFormBuilder.append({name: 'color_scheme_name', type: 'text'});
saveColorSchemeFormBuilder.append({name: 'file_name', type: 'text'});
saveColorSchemeFormBuilder.setVisible('file_name', false);
saveColorSchemeFormBuilder.find('save_to').on('change', function () {
var isBrowser = $(this).val() === 'Browser Storage';
saveColorSchemeFormBuilder.setVisible('file_name', !isBrowser);
saveColorSchemeFormBuilder.setVisible('color_scheme_name', isBrowser);
});
phantasus.FormBuilder.showOkCancel({
title: 'Save Color Scheme',
ok: true,
cancel: true,
draggable: true,
content: saveColorSchemeFormBuilder.$form,
appendTo: heatMap.getContentEl(),
align: 'right',
okCallback: function () {
var colorSchemeText = JSON.stringify(heatMap.heatmap.getColorScheme().toJSON());
if (saveColorSchemeFormBuilder.getValue('save_to') === 'Browser Storage') {
var name = saveColorSchemeFormBuilder.getValue('color_scheme_name').trim();
if (name === '') {
name = 'my color scheme';
}
var colorSchemeObject = localStorage.getItem('phantasus-colorScheme');
if (colorSchemeObject == null) {
colorSchemeObject = {};
} else {
colorSchemeObject = JSON.parse(colorSchemeObject);
}
colorSchemeObject[name] = colorSchemeText;
localStorage.setItem('phantasus-colorScheme', JSON.stringify(colorSchemeObject));
colorSchemeFormBuilder.setOptions('saved_color_scheme', createColorSchemeOptions());
} else {
var name = saveColorSchemeFormBuilder.getValue('file_name').trim();
if (name === '') {
name = 'color_scheme.json';
}
var blob = new Blob([colorSchemeText], {
type: 'application/json'
});
saveAs(blob, name);
}
},
focus: heatMap.getFocusEl()
});
});
colorSchemeFormBuilder.setEnabled('delete_color_scheme', false);
colorSchemeFormBuilder.find('delete_color_scheme').on('click', function () {
var key = colorSchemeFormBuilder.getValue('saved_color_scheme');
var savedColorSchemes = JSON.parse(localStorage.getItem('phantasus-colorScheme'));
delete savedColorSchemes[key];
localStorage.setItem('phantasus-colorScheme', JSON.stringify(savedColorSchemes));
colorSchemeFormBuilder.setOptions('saved_color_scheme', createColorSchemeOptions());
});
colorSchemeFormBuilder.find('saved_color_scheme').on('change', function () {
colorSchemeFormBuilder.setEnabled('delete_color_scheme', ['relative', 'cn', 'MAF', 'binary', 'My Computer...'].indexOf(
colorSchemeFormBuilder.getValue('saved_color_scheme')) === -1);
});
colorSchemeFormBuilder.find('load_color_scheme').on('click',
function (e) {
var val = colorSchemeFormBuilder.getValue('saved_color_scheme');
var repaint = true;
if (val === 'relative') {
heatMap.heatmap
.getColorScheme()
.setColorSupplierForCurrentValue(
phantasus.AbstractColorSupplier.fromJSON(phantasus.HeatMapColorScheme.Predefined
.RELATIVE()));
} else if (val === 'cn') {
heatMap.heatmap
.getColorScheme()
.setColorSupplierForCurrentValue(
phantasus.AbstractColorSupplier.fromJSON(phantasus.HeatMapColorScheme.Predefined
.CN()));
} else if (val === 'MAF') {
heatMap.heatmap
.getColorScheme()
.setColorSupplierForCurrentValue(
phantasus.AbstractColorSupplier.fromJSON(phantasus.HeatMapColorScheme.Predefined
.MAF()));
} else if (val === 'binary') {
heatMap.heatmap
.getColorScheme()
.setColorSupplierForCurrentValue(
phantasus.AbstractColorSupplier.fromJSON(phantasus.HeatMapColorScheme.Predefined
.BINARY()));
} else if (val === 'My Computer...') {
repaint = false;
var $file = $('<input style="display:none;" type="file">');
$file.appendTo(heatMap.getContentEl());
$file.click();
$file.on('change', function (evt) {
var files = evt.target.files;
phantasus.Util.getText(evt.target.files[0]).done(
function (text) {
var json = JSON.parse($.trim(text));
heatMap.heatmap.getColorScheme().fromJSON(json);
colorSchemeChooser
.setColorScheme(heatMap.heatmap
.getColorScheme());
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
}).fail(function () {
phantasus.FormBuilder.showInModal({
title: 'Error',
html: 'Unable to read color scheme.'
});
}).always(function () {
$file.remove();
});
});
} else {
var savedColorSchemes = JSON.parse(localStorage.getItem('phantasus-colorScheme'));
var scheme = JSON.parse(savedColorSchemes[val]);
heatMap.heatmap.getColorScheme().fromJSON(scheme);
// saved in local storage
}
if (repaint) {
colorSchemeChooser
.setColorScheme(heatMap.heatmap
.getColorScheme());
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
}
colorSchemeChooser.restoreCurrentValue();
});
colorSchemeFormBuilder.$form
.find('[name=color_by]')
.on(
'change',
function (e) {
var colorByField = $(this).val();
if (colorByField == '(None)') {
colorByField = null;
}
var colorByValue = null;
heatMap.heatmap.getColorScheme()
.setSeparateColorSchemeForRowMetadataField(
colorByField);
if (colorByField != null) {
$colorByValue
.html(phantasus.Util
.createOptions(phantasus.VectorUtil
.createValueToIndexMap(
heatMap.project
.getFullDataset()
.getRowMetadata()
.getByName(
colorByField))
.keys()));
colorByValue = $colorByValue.val();
} else {
$colorByValue.html('');
}
heatMap.heatmap.getColorScheme().setCurrentValue(
colorByValue);
colorSchemeChooser.setCurrentValue(colorByValue);
heatMap.heatmap.setInvalid(true);
heatMap.heatmap.repaint();
colorSchemeChooser.setColorScheme(heatMap.heatmap
.getColorScheme());
});
$colorByValue.on('change', function (e) {
if (heatMap.heatmap.getColorScheme()
.getSeparateColorSchemeForRowMetadataField() == null) {
colorSchemeChooser.setCurrentValue(null);
heatMap.heatmap.getColorScheme().setCurrentValue(null);
colorSchemeChooser.setColorScheme(heatMap.heatmap
.getColorScheme());
} else {
colorSchemeChooser.setCurrentValue($colorByValue.val());
colorSchemeChooser.setColorScheme(heatMap.heatmap
.getColorScheme());
}
});
displayFormBuilder.$form.find('[name=column_size]').on(
'keyup',
_.debounce(function (e) {
heatMap.heatmap.getColumnPositions().setSize(
parseFloat($(this).val()));
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
}, 100));
displayFormBuilder.$form.find('[name=row_gap_size]').on('keyup',
_.debounce(function (e) {
heatMap.rowGapSize = parseFloat($(this).val());
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
}, 100));
displayFormBuilder.$form.find('[name=column_gap_size]').on('keyup',
_.debounce(function (e) {
heatMap.columnGapSize = parseFloat($(this).val());
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
}, 100));
displayFormBuilder.$form.find('[name=squish_factor]').on('keyup',
_.debounce(function (e) {
var f = parseFloat($(this).val());
heatMap.heatmap.getColumnPositions().setSquishFactor(f);
heatMap.heatmap.getRowPositions().setSquishFactor(f);
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
}, 100));
displayFormBuilder.$form.find('[name=row_dendrogram_line_thickness]').on(
'keyup', _.debounce(function (e) {
heatMap.rowDendrogram.lineWidth = parseFloat($(this).val());
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
}, 100));
displayFormBuilder.$form.find('[name=column_dendrogram_line_thickness]')
.on(
'keyup',
_.debounce(function (e) {
heatMap.columnDendrogram.lineWidth = parseFloat($(
this).val());
heatMap.revalidate();
colorSchemeChooser.restoreCurrentValue();
}, 100));
var $tab = $('<div class="tab-content"></div>');
$metadataDiv.appendTo($tab);
$heatMapDiv.appendTo($tab);
$displayDiv.appendTo($tab);
var $div = $('<div></div>');
var $ul = $('<ul class="nav nav-tabs" role="tablist">' + '<li><a href="#'
+ annotationOptionsTabId
+ '" role="tab" data-toggle="tab">Annotations</a></li>'
+ '<li><a href="#' + heatMapOptionsTabId
+ '" role="tab" data-toggle="tab">Color Scheme</a></li>'
+ '<li><a href="#' + displayOptionsTabId
+ '" role="tab" data-toggle="tab">Display</a></li>' + '</ul>');
$ul.appendTo($div);
$tab.appendTo($div);
// set current scheme
colorSchemeChooser.setColorScheme(heatMap.heatmap.getColorScheme());
colorSchemeChooserUpdated();
$ul.find('[role=tab]:eq(1)').tab('show');
phantasus.FormBuilder.showInModal({
title: 'Options',
html: $div,
close: 'Close',
focus: heatMap.getFocusEl(),
onClose: function () {
$div.find('input').off('keyup');
$ca.off('change');
$ra.off('change');
$div.remove();
colorSchemeChooser.dispose();
}
});
};
phantasus.HeatMapSizer = function () {
this.seriesName = null;
this.sizeByScale = d3.scale.linear().domain([this.min, this.max])
.range([0, 1]).clamp(true);
};
phantasus.HeatMapSizer.prototype = {
min: 0,
max: 1,
copy: function () {
var sizer = new phantasus.HeatMapSizer();
sizer.seriesName = this.seriesName;
sizer.min = this.min;
sizer.max = this.max;
sizer.sizeByScale = this.sizeByScale.copy();
return sizer;
},
valueToFraction: function (value) {
return this.sizeByScale(value);
},
setMin: function (min) {
this.min = min;
this.sizeByScale = d3.scale.linear().domain([this.min, this.max])
.range([0, 1]).clamp(true);
},
setMax: function (max) {
this.max = max;
this.sizeByScale = d3.scale.linear().domain([this.min, this.max])
.range([0, 1]).clamp(true);
},
getMin: function () {
return this.min;
},
getMax: function () {
return this.max;
},
getSeriesName: function () {
return this.seriesName;
},
setSeriesName: function (name) {
this.seriesName = name;
}
};
phantasus.HeatMapToolBar = function (heatMap) {
this.heatMap = heatMap;
this.rowSearchResultModelIndices = [];
this.columnSearchResultModelIndices = [];
var _this = this;
var layout = ['<div class="hidden-print">'];
layout.push('<div data-name="toolbar"></div>');
layout.push(
'<div data-name="tip" style="white-space:nowrap; border-top: thin solid #e7e7e7;margin-bottom:2px;height: 14px; font-size: 10px;overflow:hidden;"></div>');
layout.push('</div>');
var $el = $(layout.join(''));
var searchHtml = [];
var $searchForm = $(
'<form style="display:inline-block;margin-right:14px;" name="searchForm"' +
' class="form' +
' form-inline' +
' form-compact"' +
' role="search"></form>');
$searchForm.on('submit', function (e) {
e.preventDefault();
});
// toogle search buttons
searchHtml.push('<div title="Toggle' +
' Search (' + phantasus.Util.COMMAND_KEY + '/)" class="btn-group"' +
' data-toggle="buttons">');
searchHtml.push('<label class="btn btn-default btn-xxs">');
searchHtml.push(
'<input data-search="rows" type="radio" autocomplete="off" name="searchToggle"' +
' type="button"> Rows');
searchHtml.push('</label>');
searchHtml.push('<label class="btn btn-default btn-xxs">');
searchHtml.push(
'<input data-search="columns" type="radio" autocomplete="off" name="searchToggle"> Columns');
searchHtml.push('</label>');
searchHtml.push('<label class="btn btn-default btn-xxs">');
searchHtml.push(
'<input data-search="values" type="radio" autocomplete="off" name="searchToggle">' +
' Values');
searchHtml.push('</label>');
searchHtml.push('<label class="btn btn-default btn-xxs">');
searchHtml.push(
'<input data-search="rowDendrogram" type="radio" autocomplete="off"' +
' name="searchToggle"> Row Dendrogram');
searchHtml.push('</label>');
searchHtml.push('<label class="btn btn-default btn-xxs">');
searchHtml.push(
'<input data-search="columnDendrogram" type="radio" autocomplete="off"' +
' name="searchToggle"> Column Dendrogram');
searchHtml.push('</label>');
searchHtml.push('</div>');
function createSearchOptionsMenu() {
searchHtml.push('<div style="display:inline-block;" class="dropdown">');
searchHtml.push(
'<button type="button" class="btn btn-default btn-xxs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <span class="fa fa-caret-down"></span></button>');
searchHtml.push('<ul data-name="searchOptions" class="dropdown-menu">');
searchHtml.push(
'<li><a data-group="matchMode" data-name="exact" href="#"><span' +
' data-type="toggle"' +
' class="dropdown-checkbox fa fa-check"></span>Exact' +
' Match</a></li>');
searchHtml.push(
'<li><a data-group="matchMode" data-name="contains" href="#"><span' +
' data-type="toggle"' +
' ></span>Contains</a></li>');
searchHtml.push('<li role="separator" class="divider"></li>');
searchHtml.push(
'<li><a data-group="searchMode" data-name="matchAny" href="#"><span' +
' data-type="toggle"' +
' class="dropdown-checkbox fa fa-check"></span>Match Any Search Term</a></li>');
searchHtml.push(
'<li><a data-group="searchMode" data-name="matchAll" href="#"><span' +
' data-type="toggle"></span>Match All Search Terms</a></li>');
searchHtml.push('<li role="separator" class="divider"></li>');
searchHtml.push('<li><a data-name="searchHelp" href="#">Help</a></li>');
searchHtml.push('</ul>');
searchHtml.push('</div>');
}
function createSearchMenu(dataName, navigation) {
searchHtml.push(
'<div style="display:inline-block;" data-name="' + dataName + '">');
searchHtml.push('<div class="form-group">');
searchHtml.push(
'<input type="text" class="form-control input-sm" autocomplete="off"' +
' name="search">');
searchHtml.push('</div>');
searchHtml.push('<div class="form-group">');
searchHtml.push(
'<span data-name="searchResultsWrapper" style="display:none;">');
searchHtml.push(
'<span style="font-size:12px;" data-name="searchResults"></span>');
if (navigation) {
searchHtml.push(
'<button name="previousMatch" type="button" class="btn btn-default btn-xxs" data-toggle="tooltip" title="Previous"><i class="fa fa-chevron-up"></i></button>');
searchHtml.push(
'<button name="nextMatch" type="button" class="btn btn-default btn-xxs" data-toggle="tooltip" title="Next"><i class="fa fa-chevron-down"></i></button>');
searchHtml.push(
'<button name="matchesToTop" type="button" class="btn btn-default btn-xxs" data-toggle="tooltip" title="Matches To Top"><i class="fa fa-level-up"></i></button>');
}
searchHtml.push('</span>');
searchHtml.push('</div>');
searchHtml.push('</div>');
searchHtml.push('</div>');
}
if (heatMap.options.toolbar.searchRows ||
heatMap.options.toolbar.searchColumns ||
heatMap.options.toolbar.searchValues) {
createSearchOptionsMenu();
}
if (heatMap.options.toolbar.searchRows) {
createSearchMenu('searchRowsGroup', true);
}
if (heatMap.options.toolbar.searchColumns) {
createSearchMenu('searchColumnsGroup', true);
}
if (heatMap.options.toolbar.searchValues) {
createSearchMenu('searchValuesGroup', false);
}
createSearchMenu('searchRowDendrogramGroup', false);
createSearchMenu('searchColumnDendrogramGroup', false);
// dimensions
if (heatMap.options.toolbar.dimensions) {
searchHtml.push('<div class="form-group">');
searchHtml.push(
'<h6 style="display: inline; margin-left:10px;" data-name="dim"></h6>');
searchHtml.push(
'<h6 style="display: inline; margin-left:10px;" data-name="selection"></h6>');
searchHtml.push('</div>');
}
var $menus = $(
'<div style="display: inline-block;margin-right:14px;"></div>');
function createMenu(menuName, actions, minWidth) {
if (!minWidth) {
minWidth = '0px';
}
var menu = [];
var dropdownId = _.uniqueId('phantasus');
menu.push('<div class="dropdown phantasus-menu">');
menu.push(
'<a class="dropdown-toggle phantasus-black-link phantasus-black-link-background" type="button"' +
' id="' + dropdownId +
'" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">');
menu.push(menuName);
menu.push('</a>');
menu.push('<ul style="min-width:' + minWidth +
';" class="dropdown-menu" aria-labelledby="' + dropdownId + '">');
var addSimpleAction = function (action) {
menu.push('<li>');
menu.push(
'<a class="phantasus-menu-item" data-action="' + action.name +
'" href="#">');
menu.push(action.name);
if (action.ellipsis) {
menu.push('...');
}
if (action.icon) {
menu.push('<span class="' + action.icon +
' phantasus-menu-item-icon"></span> ');
}
if (action.which) {
menu.push('<span class="pull-right">');
if (action.commandKey) {
menu.push(phantasus.Util.COMMAND_KEY);
}
if (action.shiftKey) {
menu.push('Shift+');
}
menu.push(phantasus.KeyboardCharMap[action.which[0]]);
menu.push('</span>');
}
menu.push('</a>');
menu.push('</li>');
};
var addActionWithChildren = function (action) {
menu.push('<li class="dropdown-submenu">');
menu.push('<a class="phantasus-menu-item dummy" tabindex="-1" href="#">');
menu.push(action.name);
if (action.icon) {
menu.push('<span class="' + action.icon +
' phantasus-menu-item-icon"></span> ');
}
menu.push('</a>');
menu.push('<ul class="dropdown-menu">');
action.children.forEach(defaultActionAdder);
menu.push('</ul>');
menu.push('</li>');
};
var defaultActionAdder = function (name) {
if (name == null) {
menu.push('<li role="separator" class="divider"></li>');
} else {
var action = heatMap.getActionManager().getAction(name);
if (action == null) {
return;
}
action.children ?
addActionWithChildren(action) :
addSimpleAction(action);
}
};
actions.forEach(defaultActionAdder);
menu.push('</ul>');
menu.push('</div>');
$(menu.join('')).appendTo($menus);
}
//// console.log("HeatMapToolbar ::", "heatMap:", heatMap, "heatMap.options:", heatMap.options);
if (heatMap.options.menu) {
if (heatMap.options.menu.File) {
createMenu('File', heatMap.options.menu.File, '240px');
}
if (heatMap.options.menu.View) {
createMenu('Edit', heatMap.options.menu.Edit);
}
if (heatMap.options.menu.View) {
createMenu('View', heatMap.options.menu.View, '170px');
}
if (heatMap.options.menu.Tools) {
createMenu('Tools', heatMap.options.menu.Tools);
}
if (heatMap.options.menu.Help) {
createMenu('Help', heatMap.options.menu.Help, '220px');
}
}
$(searchHtml.join('')).appendTo($searchForm);
var $lineOneColumn = $el.find('[data-name=toolbar]');
$menus.appendTo($lineOneColumn);
$searchForm.appendTo($lineOneColumn);
var toolbarHtml = ['<div style="display: inline;">'];
toolbarHtml.push('<div class="phantasus-button-divider"></div>');
// zoom
if (heatMap.options.toolbar.zoom) {
var dropdownId = _.uniqueId('phantasus');
toolbarHtml.push('<div style="display:inline-block;" class="dropdown">');
toolbarHtml.push(
'<a class="dropdown-toggle phantasus-black-link" type="button" id="' +
dropdownId +
'" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">');
// toolbarHtml.push('<input style="width:2em;height:21px;" id="' + dropdownId + '" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">');
toolbarHtml.push('<button type="button"' +
' class="btn btn-default btn-xxs"><span class="fa' +
' fa-search-plus"></span>');
toolbarHtml.push(
' <span style="font-size: .8em;" class="fa fa-caret-down"></span>');
toolbarHtml.push('</button>');
toolbarHtml.push(
'<ul style="width:200px;" class="dropdown-menu" aria-labelledby="' + dropdownId + '">');
toolbarHtml.push(
'<li><a class="phantasus-menu-item" href="#" data-action="Zoom In">Zoom In<span class="pull-right">+</span></a></li>');
toolbarHtml.push(
'<li><a class="phantasus-menu-item" href="#" data-action="Zoom Out">Zoom' +
' Out<span class="pull-right">-</span></a></li>');
toolbarHtml.push('<li role="separator" class="divider"></li>');
toolbarHtml.push(
'<li><a class="phantasus-menu-item" href="#" data-action="Fit To Window">Fit To Window<span' +
' class="fa' +
' fa-compress phantasus-menu-item-icon"></span><span class="pull-right">' +
phantasus.Util.COMMAND_KEY +
phantasus.KeyboardCharMap[heatMap.getActionManager().getAction('Fit To Window').which[0]] + '</span> </a></li>');
toolbarHtml.push(
'<li><a class="phantasus-menu-item" href="#" data-action="Fit Rows To Window">Fit Rows To Window</a></li>');
toolbarHtml.push(
'<li><a class="phantasus-menu-item" href="#" data-action="Fit Columns To Window">Fit Columns To Window</a></li>');
toolbarHtml.push('<li role="separator" class="divider"></li>');
toolbarHtml.push(
'<li><a class="phantasus-menu-item" href="#" data-action="100%">100%</a></li>');
toolbarHtml.push('</ul>');
toolbarHtml.push('</div>');
}
toolbarHtml.push('<div class="phantasus-button-divider"></div>');
if (heatMap.options.toolbar.sort) {
toolbarHtml.push(
'<button data-toggle="tooltip" title="Sort" name="Sort" type="button" class="btn' +
' btn-default btn-xxs"><span class="fa fa-sort-alpha-asc"></span></button>');
}
if (heatMap.options.toolbar.options) {
toolbarHtml.push(
'<button data-action="Options" data-toggle="tooltip" title="Options" type="button"' +
' class="btn btn-default btn-xxs"><span class="fa fa-cog"></span></button>');
}
toolbarHtml.push('<div class="phantasus-button-divider"></div>');
if (heatMap.options.toolbar.openFile) {
toolbarHtml.push(
'<button data-action="Open File" data-toggle="tooltip" title="Open File ('
+ phantasus.Util.COMMAND_KEY
+
'O)" type="button" class="btn btn-default btn-xxs"><span class="fa fa-folder-open-o"></span></button>');
}
if (heatMap.options.toolbar.saveImage) {
toolbarHtml.push(
'<button data-action="Save Image" data-toggle="tooltip" title="Save Image ('
+ phantasus.Util.COMMAND_KEY
+
'S)" type="button" class="btn btn-default btn-xxs"><span class="fa fa-file-image-o"></span></button>');
}
if (heatMap.options.toolbar.saveDataset) {
toolbarHtml.push(
'<button data-action="Save Dataset" data-toggle="tooltip" title="Save Dataset ('
+ phantasus.Util.COMMAND_KEY
+
'Shift+S)" type="button" class="btn btn-default btn-xxs"><span class="fa fa-floppy-o"></span></button>');
}
if (heatMap.options.toolbar.saveSession) {
toolbarHtml.push(
'<button data-action="Save Session" data-toggle="tooltip" title="Save Session" type="button"' +
' class="btn btn-default btn-xxs"><span class="fa fa-anchor"></span></button>');
}
toolbarHtml.push('<div class="phantasus-button-divider"></div>');
if (heatMap.options.toolbar.filter) {
toolbarHtml.push(
'<button data-action="Filter" data-toggle="tooltip" title="Filter" type="button"' +
' class="btn btn-default btn-xxs"><span class="fa fa-filter"></span></button>');
}
if (heatMap.options.toolbar.chart && typeof echarts !== 'undefined') {
toolbarHtml.push(
'<button data-action="Chart" data-toggle="tooltip" title="Chart" type="button" class="btn' +
' btn-default btn-xxs"><span class="fa fa-line-chart"></span></button>');
}
// legend
if (heatMap.options.toolbar.colorKey) {
toolbarHtml.push('<div class="phantasus-button-divider"></div>');
toolbarHtml.push('<div class="btn-group">');
toolbarHtml.push(
'<button type="button" class="btn btn-default btn-xxs" data-toggle="dropdown"><span title="Color Key" data-toggle="tooltip" class="fa fa-key"></span></button>');
toolbarHtml.push('<ul data-name="key" class="dropdown-menu" role="menu">');
toolbarHtml.push('<li data-name="keyContent"></li>');
toolbarHtml.push('</ul>');
toolbarHtml.push('</div>');
toolbarHtml.push('<div class="phantasus-button-divider"></div>');
}
toolbarHtml.push('</div>');
var $toolbar = $(toolbarHtml.join(''));
$toolbar.find('[data-action]').on('click', function (e) {
e.preventDefault();
heatMap.getActionManager().execute($(this).data('action'));
}).on('blur', function (e) {
if (document.activeElement === document.body) {
heatMap.focus();
}
});
$menus.on('click', 'li > a:not(.dummy)', function (e) {
e.preventDefault();
heatMap.getActionManager().execute($(this).data('action'));
}).on('blur', function (e) {
if (document.activeElement === document.body) {
heatMap.focus();
}
});
if (heatMap.options.toolbar.$customButtons) {
heatMap.options.toolbar.$customButtons.appendTo($toolbar);
}
$toolbar.appendTo($lineOneColumn);
// $hide.appendTo($el.find('[data-name=toggleEl]'));
$el.prependTo(heatMap.$content);
this.$tip = $el.find('[data-name=tip]');
$el.find('[data-toggle="tooltip"]').tooltip({
placement: 'bottom',
container: 'body',
trigger: 'hover'
}).on('click', function () {
$(this).tooltip('hide');
});
var $key = $el.find('[data-name=key]');
var $keyContent = $el.find('[data-name=keyContent]');
$key.dropdown().parent().on('show.bs.dropdown', function () {
new phantasus.HeatMapColorSchemeLegend(heatMap, $keyContent);
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'colorKey'
});
});
var searchHelpHtml = [];
searchHelpHtml.push('<h4>Symbols</h4>');
searchHelpHtml.push('<table class="table table-bordered">');
searchHelpHtml.push('<tr><th>Term</th><th>Description</th></tr>');
searchHelpHtml.push(
'<tr><td><code><strong>*</strong></code></td><td>Quote a search term for an' +
' exact' +
' match. <br' +
' />Example: <code><strong>"root beer"</strong></code></td></tr>');
searchHelpHtml.push(
'<tr><td><code><strong>-</strong></code></td><td>Exclude matches using -' +
' modifier.</td></tr>');
searchHelpHtml.push(
'<tr><td><code><strong>..</strong></code></td><td>Separate numbers by two' +
' periods' +
' without spaces to' +
' see numbers that fall within a range.. <br' +
' />Example: <code><strong>1..10</strong></code></td></tr>');
searchHelpHtml.push(
'<tr><td><code><strong><= < > >= =</strong></code></td><td>Perform a' +
' numeric' +
' search.' +
' <br' +
' />Example: <code><strong>>4</strong></code></td></tr>');
searchHelpHtml.push('</table>');
searchHelpHtml.push('<h4>Search fields</h4>');
searchHelpHtml.push(
'<p>You can restrict your search to any field by typing the field name followed by a colon ":" and then the term you are looking for. For example, to search for matches containing "beer" in the beverage field, you can enter:' +
' <code><strong>beverage:beer</strong></code>');
searchHelpHtml.push(
'Note that searches only include metadata fields that are displayed. You' +
' can search a hidden field by performing a field search.');
// searchHelpHtml.push('<br />Note: The field is only valid for the term that it directly' +
// ' precedes.');
searchHelpHtml.push(
'<p>You can search for an exact list of values by enclosing the list of' +
' values in parentheses. For example: <code><strong>pet:(cat dog)</strong></code>' +
' searches all pets that are either cats or dogs.</p>');
var $searchHelp = $(searchHelpHtml.join(''));
$el.find('[data-name=searchHelp]').on('click', function (e) {
e.preventDefault();
phantasus.FormBuilder.showInModal({
title: 'Search Help',
html: $searchHelp,
appendTo: heatMap.getContentEl(),
focus: heatMap.getFocusEl()
});
});
var $searchRowsGroup = $searchForm.find('[data-name=searchRowsGroup]');
var $searchColumnsGroup = $searchForm.find('[data-name=searchColumnsGroup]');
var $searchValuesGroup = $searchForm.find('[data-name=searchValuesGroup]');
var $searchRowDendrogramGroup = $searchForm.find(
'[data-name=searchRowDendrogramGroup]');
var $searchColumnDendrogramGroup = $searchForm.find(
'[data-name=searchColumnDendrogramGroup]');
this.$searchRowDendrogramGroup = $searchRowDendrogramGroup;
this.$searchColumnDendrogramGroup = $searchColumnDendrogramGroup;
this.matchMode = 'exact';
this.matchAllPredicates = false;
var $searchToggle = $searchForm.find('[name=searchToggle]'); // buttons
var nameToSearchObject = {};
function getSearchElements($group, searchName, cb) {
var obj = {
$group: $group,
$search: $group.find('[name=search]'),
$searchResultsWrapper: $group.find('[data-name=searchResultsWrapper]'),
$searchResults: $group.find('[data-name=searchResults]'),
$previousMatch: $group.find('[name=previousMatch]'),
$nextMatch: $group.find('[name=nextMatch]'),
$matchesToTop: $group.find('[name=matchesToTop]'),
$toggleButton: $searchToggle.filter('[data-search=' + searchName + ']').parent()
};
nameToSearchObject[searchName] = obj;
return obj;
} var $searchOptions = $el.find('[data-name=searchOptions]');
$searchOptions.on('click', 'li > a', function (e) {
e.preventDefault();
var $this = $(this);
var group = $this.data('group');
if (group === 'matchMode') {
_this.matchMode = $this.data('name');
} else {
_this.matchAllPredicates = $this.data('name') === 'matchAll';
}
var $searchField;
if (_this.rowSearchObject.$search.is(':visible')) {
$searchField = _this.rowSearchObject.$search;
} else if (_this.columnSearchObject.$search.is(':visible')) {
$searchField = _this.rowSearchObject.$search;
} else if (_this.rowDendrogramSearchObject.$search.is(':visible')) {
$searchField = _this.rowSearchObject.$search;
} else if (_this.columnDendrogramSearchObject.$search.is(':visible')) {
$searchField = _this.rowSearchObject.$search;
} else if (_this.valueSearchObject.$search.is(':visible')) {
$searchField = _this.rowSearchObject.$search;
}
if ($searchField) {
$searchField.trigger($.Event('keyup', {
keyCode: 13,
which: 13
}));
// trigger search again
}
var $span = $(this).find('span');
if ($span.data('type') === 'toggle') {
$searchOptions.find('[data-group=' + group + '] > [data-type=toggle]').removeClass('dropdown-checkbox' +
' fa' +
' fa-check');
$span.addClass('dropdown-checkbox fa fa-check');
}
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'searchMatchMode'
});
});
this.rowSearchObject = getSearchElements($searchRowsGroup, 'rows',
function () {
_this.search(true);
});
this.columnSearchObject = getSearchElements($searchColumnsGroup, 'columns',
function () {
_this.search(false);
});
this.rowDendrogramSearchObject = getSearchElements($searchRowDendrogramGroup,
'rowDendrogram', function () {
_this.searchDendrogram(false);
});
this.columnDendrogramSearchObject = getSearchElements(
$searchColumnDendrogramGroup, 'columnDendrogram', function () {
_this.searchDendrogram(false);
});
this.valueSearchObject = getSearchElements($searchValuesGroup, 'values',
function () {
searchValues();
});
// set button and search controls visibility
if (!heatMap.options.toolbar.searchRows) {
this.rowSearchObject.$toggleButton.hide();
this.rowSearchObject.$group.css('display', 'none');
}
if (!heatMap.options.toolbar.searchColumns) {
this.columnSearchObject.$toggleButton.hide();
this.columnSearchObject.$group.css('display', 'none');
}
if (!heatMap.options.toolbar.searchValues) {
this.valueSearchObject.$toggleButton.hide();
this.valueSearchObject.$group.css('display', 'none');
}
this.rowDendrogramSearchObject.$toggleButton.hide();
this.rowDendrogramSearchObject.$group.hide();
this.columnDendrogramSearchObject.$toggleButton.hide();
this.columnDendrogramSearchObject.$group.hide();
this.rowDendrogramSearchObject.$searchResultsWrapper.show();
this.columnDendrogramSearchObject.$searchResultsWrapper.show();
this.valueSearchObject.$searchResultsWrapper.show();
this.rowSearchObject.$search.css({
'border-top': '3.8px solid #e6e6e6',
'border-bottom': '3.8px solid #e6e6e6',
width: '240px'
});
this.columnSearchObject.$search.css({
'border-right': '3.8px solid #e6e6e6',
'border-left': '3.8px solid #e6e6e6',
width: '240px'
});
this.$valueSearchResults = $searchValuesGroup.find('[name=searchResults]');
this.$valueTextField = $searchValuesGroup.find('[name=search]');
this.$dimensionsLabel = $el.find('[data-name=dim]');
this.$selectionLabel = $el.find('[data-name=selection]');
$searchToggle.on('change', function (e) {
var search = $(this).data('search');
for (var name in nameToSearchObject) {
var searchObject = nameToSearchObject[name];
if (name === search) {
searchObject.$group.css('display', 'inline-block');
searchObject.$search.focus();
} else {
searchObject.$group.css('display', 'none');
}
}
});
this.toggleSearch = function () {
var $visible = $searchToggle.filter(':visible');
var $checked = $searchToggle.filter(':checked');
var $next = $visible.eq($visible.index($checked) + 1);
if (!$next.length) {
$next = $visible.first();
}
$next.click();
};
for (var i = 0; i < $searchToggle.length; i++) {
var $button = $($searchToggle[i]);
if ($button.parent().css('display') === 'block') {
$button.click();
break;
}
}
heatMap.on('dendrogramAnnotated', function (e) {
if (e.isColumns) { // show buttons
_this.rowDendrogramSearchObject.$toggleButton.show();
} else {
_this.columnDendrogramSearchObject.$toggleButton.show();
}
});
heatMap.on('dendrogramChanged', function (e) {
if (e.isColumns) {
_this.rowDendrogramSearchObject.$group.hide();
_this.rowDendrogramSearchObject.$toggleButton.hide();
} else {
_this.columnDendrogramSearchObject.$group.hide();
_this.columnDendrogramSearchObject.$toggleButton.hide();
}
});
var project = heatMap.getProject();
phantasus.Util.autosuggest({
$el: this.rowSearchObject.$search,
filter: function (terms, cb) {
var indices = [];
var meta = project.getSortedFilteredDataset().getRowMetadata();
heatMap.getVisibleTrackNames(false).forEach(function (name) {
var index = phantasus.MetadataUtil.indexOf(meta, name);
if (index !== -1) {
indices.push(index);
}
});
meta = new phantasus.MetadataModelColumnView(meta, indices);
phantasus.MetadataUtil.autocomplete(meta)(terms, cb);
},
select: function () {
_this.search(true);
}
});
this.rowSearchObject.$search.on('keyup', _.debounce(function (e) {
if (e.which === 13) {
e.preventDefault();
}
_this.search(true);
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'searchRows'
});
}, 500));
phantasus.Util.autosuggest({
$el: this.columnSearchObject.$search,
filter: function (terms, cb) {
var indices = [];
var meta = project.getSortedFilteredDataset().getColumnMetadata();
heatMap.getVisibleTrackNames(true).forEach(function (name) {
var index = phantasus.MetadataUtil.indexOf(meta, name);
if (index !== -1) {
indices.push(index);
}
});
meta = new phantasus.MetadataModelColumnView(meta, indices);
phantasus.MetadataUtil.autocomplete(meta)(terms, cb);
},
select: function () {
_this.search(false);
}
});
this.columnSearchObject.$search.on('keyup', _.debounce(function (e) {
if (e.which === 13) {
e.preventDefault();
}
_this.search(false);
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'searchColumns'
});
}, 500));
// dendrogram search
phantasus.Util.autosuggest({
$el: this.rowDendrogramSearchObject.$search,
filter: function (tokens, cb) {
var d = heatMap.getDendrogram(false);
if (!d.searchTerms) {
cb([]);
} else {
var token = tokens != null && tokens.length > 0
? tokens[tokens.selectionStartIndex]
: '';
token = $.trim(token);
if (token === '') {
cb([]);
} else {
phantasus.Util.autocompleteArrayMatcher(token, cb, d.searchTerms, null,
10);
}
}
},
select: function () {
_this.searchDendrogram(false);
}
});
this.rowDendrogramSearchObject.$search.on('keyup', _.debounce(function (e) {
if (e.which === 13) {
e.preventDefault();
}
_this.searchDendrogram(false);
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'searchRowDendrogram'
});
}, 500));
phantasus.Util.autosuggest({
$el: this.columnDendrogramSearchObject.$search,
filter: function (tokens, cb) {
var d = heatMap.getDendrogram(true);
if (!d.searchTerms) {
cb([]);
} else {
var token = tokens != null && tokens.length > 0
? tokens[tokens.selectionStartIndex]
: '';
token = $.trim(token);
if (token === '') {
cb([]);
} else {
phantasus.Util.autocompleteArrayMatcher(token, cb, d.searchTerms, null,
10);
}
}
},
select: function () {
_this.searchDendrogram(true);
}
});
this.columnDendrogramSearchObject.$search.on('keyup',
_.debounce(function (e) {
if (e.which === 13) {
e.preventDefault();
}
_this.searchDendrogram(true);
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'searchColumnDendrogram'
});
}, 500));
function searchValues() {
var $searchResultsLabel = _this.$valueSearchResults;
var text = $.trim(_this.$valueTextField.val());
if (text === '') {
$searchResultsLabel.html('');
project.getElementSelectionModel().setViewIndices(null);
} else {
var viewIndices = phantasus.DatasetUtil.searchValues({
dataset: project.getSortedFilteredDataset(),
text: text,
matchAllPredicates: _this.matchAllPredicates,
defaultMatchMode: _this.matchMode
});
project.getElementSelectionModel().setViewIndices(viewIndices);
$searchResultsLabel.html(viewIndices.size() + ' match'
+ (viewIndices.size() === 1 ? '' : 'es'));
}
}
phantasus.Util.autosuggest({
$el: this.$valueTextField,
filter: function (terms, cb) {
phantasus.DatasetUtil.autocompleteValues(
project.getSortedFilteredDataset())(terms, cb);
},
select: function () {
searchValues();
}
});
this.$valueTextField.on('keyup', _.debounce(function (e) {
if (e.which === 13) {
_this.$valueTextField.autocomplete('close');
e.preventDefault();
}
searchValues();
}, 500));
this.toggleControls = function () {
if ($lineOneColumn.css('display') === 'none') {
$lineOneColumn.css('display', '');
_this.rowSearchObject.$search.focus();
} else {
$lineOneColumn.css('display', 'none');
$(_this.heatMap.heatmap.canvas).focus();
}
};
this.$el = $el;
var updateFilterStatus = function () {
if (heatMap.getProject().getRowFilter().isEnabled()
|| heatMap.getProject().getColumnFilter().isEnabled()) {
_this.$el.find('[name=filterButton]').addClass('btn-primary');
} else {
_this.$el.find('[name=filterButton]').removeClass('btn-primary');
}
};
updateFilterStatus();
this.columnSearchObject.$matchesToTop.on(
'click',
function (e) {
e.preventDefault();
var $this = $(this);
$this.toggleClass('btn-primary');
_this.setSelectionOnTop({
isColumns: true,
isOnTop: $this.hasClass('btn-primary'),
updateButtonStatus: false
});
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'columnMatchesToTop'
});
});
this.rowSearchObject.$matchesToTop.on(
'click',
function (e) {
e.preventDefault();
var $this = $(this);
$this.toggleClass('btn-primary');
_this.setSelectionOnTop({
isColumns: false,
isOnTop: $this.hasClass('btn-primary'),
updateButtonStatus: false
});
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'rowMatchesToTop'
});
});
project.on('rowSortOrderChanged.phantasus', function (e) {
if (_this.searching) {
return;
}
_this._updateSearchIndices(false);
_this.rowSearchObject.$matchesToTop.removeClass('btn-primary');
});
project.on('columnSortOrderChanged.phantasus', function (e) {
if (_this.searching) {
return;
}
_this._updateSearchIndices(true);
_this.columnSearchObject.$matchesToTop.removeClass('btn-primary');
});
heatMap.getProject().on('rowFilterChanged.phantasus', function (e) {
_this.search(true);
updateFilterStatus();
});
heatMap.getProject().on('columnFilterChanged.phantasus', function (e) {
_this.search(false);
updateFilterStatus();
});
heatMap.getProject().on('datasetChanged.phantasus', function () {
_this.search(true);
_this.search(false);
updateFilterStatus();
});
heatMap.getProject().getRowSelectionModel().on(
'selectionChanged.phantasus', function () {
_this.updateSelectionLabel();
});
heatMap.getProject().getColumnSelectionModel().on(
'selectionChanged.phantasus', function () {
_this.updateSelectionLabel();
});
this.rowSearchResultViewIndicesSorted = null;
this.currentRowSearchIndex = 0;
this.columnSearchResultViewIndicesSorted = null;
this.currentColumnSearchIndex = -1;
this.columnSearchObject.$previousMatch.on(
'click',
function () {
_this.currentColumnSearchIndex--;
if (_this.currentColumnSearchIndex < 0) {
_this.currentColumnSearchIndex = _this.columnSearchResultViewIndicesSorted.length -
1;
}
heatMap.scrollLeft(
heatMap.getHeatMapElementComponent().getColumnPositions().getPosition(
_this.columnSearchResultViewIndicesSorted[_this.currentColumnSearchIndex]));
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'previousColumnMatch'
});
});
this.rowSearchObject.$previousMatch.on(
'click',
function () {
_this.currentRowSearchIndex--;
if (_this.currentRowSearchIndex < 0) {
_this.currentRowSearchIndex = _this.rowSearchResultViewIndicesSorted.length -
1;
}
heatMap.scrollTop(
heatMap.getHeatMapElementComponent().getRowPositions().getPosition(
_this.rowSearchResultViewIndicesSorted[_this.currentRowSearchIndex]));
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'previousRowMatch'
});
});
this.columnSearchObject.$nextMatch.on('click', function () {
_this.next(true);
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'nextColumnMatch'
});
});
this.rowSearchObject.$nextMatch.on('click', function () {
_this.next(false);
phantasus.Util.trackEvent({
eventCategory: 'ToolBar',
eventAction: 'nextRowMatch'
});
});
this.updateDimensionsLabel();
this.updateSelectionLabel();
}
;
phantasus.HeatMapToolBar.HIGHLIGHT_SEARCH_MODE = 0;
phantasus.HeatMapToolBar.FILTER_SEARCH_MODE = 1;
phantasus.HeatMapToolBar.MATCHES_TO_TOP_SEARCH_MODE = 2;
phantasus.HeatMapToolBar.SELECT_MATCHES_SEARCH_MODE = 3;
phantasus.HeatMapToolBar.prototype = {
quickColumnFilter: false,
searching: false,
rowSearchMode: phantasus.HeatMapToolBar.SELECT_MATCHES_SEARCH_MODE,
columnSearchMode: phantasus.HeatMapToolBar.SELECT_MATCHES_SEARCH_MODE,
_updateSearchIndices: function (isColumns) {
var project = this.heatMap.getProject();
if (isColumns) {
var viewIndices = [];
var modelIndices = this.columnSearchResultModelIndices;
for (var i = 0, length = modelIndices.length; i < length; i++) {
var index = project.convertModelColumnIndexToView(modelIndices[i]);
if (index !== -1) {
viewIndices.push(index);
}
}
viewIndices.sort(function (a, b) {
return a < b ? -1 : 1;
});
this.columnSearchResultViewIndicesSorted = viewIndices;
this.currentColumnSearchIndex = -1;
} else {
var viewIndices = [];
var modelIndices = this.rowSearchResultModelIndices;
for (var i = 0, length = modelIndices.length; i < length; i++) {
var index = project.convertModelRowIndexToView(modelIndices[i]);
if (index !== -1) {
viewIndices.push(index);
}
}
viewIndices.sort(function (a, b) {
return a < b ? -1 : 1;
});
this.rowSearchResultViewIndicesSorted = viewIndices;
this.currentRowSearchIndex = -1;
}
},
next: function (isColumns) {
var heatMap = this.heatMap;
if (isColumns) {
this.currentColumnSearchIndex++;
if (this.currentColumnSearchIndex >=
this.columnSearchResultViewIndicesSorted.length) {
this.currentColumnSearchIndex = 0;
}
heatMap.scrollLeft(
heatMap.getHeatMapElementComponent().getColumnPositions().getPosition(
this.columnSearchResultViewIndicesSorted[this.currentColumnSearchIndex]));
} else {
this.currentRowSearchIndex++;
if (this.currentRowSearchIndex >=
this.rowSearchResultViewIndicesSorted.length) {
this.currentRowSearchIndex = 0;
}
heatMap.scrollTop(
heatMap.getHeatMapElementComponent().getRowPositions().getPosition(
this.rowSearchResultViewIndicesSorted[this.currentRowSearchIndex]));
}
},
getSearchField: function (type) {
if (type === phantasus.HeatMapToolBar.COLUMN_SEARCH_FIELD) {
return this.columnSearchObject.$search;
} else if (type === phantasus.HeatMapToolBar.ROW_SEARCH_FIELD) {
return this.rowSearchObject.$search;
} else if (type ===
phantasus.HeatMapToolBar.COLUMN_DENDROGRAM_SEARCH_FIELD) {
return this.columnDendrogramSearchObject.$search;
} else if (type === phantasus.HeatMapToolBar.ROW_DENDROGRAM_SEARCH_FIELD) {
return this.rowDendrogramSearchObject.$search;
}
},
setSearchText: function (options) {
var $tf = options.isColumns ? this.columnSearchObject.$search
: this.rowSearchObject.$search;
var existing = options.append ? $.trim($tf.val()) : '';
if (existing !== '') {
existing += ' ';
}
if (options.onTop) {
options.isColumns ? this.columnSearchObject.$matchesToTop.addClass(
'btn-primary') : this.rowSearchObject.$matchesToTop.addClass(
'btn-primary');
}
$tf.val(existing + options.text);
this.search(!options.isColumns);
if (options.scrollTo) {
this.next(options.isColumns);
// click next
}
},
updateDimensionsLabel: function () {
var p = this.heatMap.getProject();
var d = p.getFullDataset();
var f = p.getSortedFilteredDataset();
var text = [];
if (f.getRowCount() !== d.getRowCount()) {
text.push('<b>');
text.push(phantasus.Util.intFormat(f.getRowCount()));
text.push('/');
text.push(phantasus.Util.intFormat(d.getRowCount()));
text.push('</b>');
} else {
text.push(phantasus.Util.intFormat(f.getRowCount()));
}
text.push(' rows by ');
if (f.getColumnCount() !== d.getColumnCount()) {
text.push('<b>');
text.push(phantasus.Util.intFormat(f.getColumnCount()));
text.push('/');
text.push(phantasus.Util.intFormat(d.getColumnCount()));
text.push('</b>');
} else {
text.push(phantasus.Util.intFormat(f.getColumnCount()));
}
text.push(' columns');
this.$dimensionsLabel.html(text.join(''));
},
updateSelectionLabel: function () {
var nc = this.heatMap.getProject().getColumnSelectionModel().count();
var nr = this.heatMap.getProject().getRowSelectionModel().count();
var text = [];
text.push(phantasus.Util.intFormat(nr) + ' row');
if (nr !== 1) {
text.push('s');
}
text.push(', ');
text.push(phantasus.Util.intFormat(nc) + ' column');
if (nc !== 1) {
text.push('s');
}
text.push(' selected');
this.$selectionLabel.html(text.join(''));
},
searchDendrogram: function (isColumns) {
var searchObject = isColumns
? this.columnDendrogramSearchObject
: this.rowDendrogramSearchObject;
var text = $.trim(searchObject.$search.val());
var dendrogram = isColumns ? this.heatMap.columnDendrogram
: this.heatMap.rowDendrogram;
var $searchResults = searchObject.$searchResults;
var matches = phantasus.DendrogramUtil.search({
rootNode: dendrogram.tree.rootNode,
text: text,
matchAllPredicates: this.matchAllPredicates,
defaultMatchMode: this.matchMode
});
if (matches === -1) {
$searchResults.html('');
} else {
$searchResults.html(matches + ' match'
+ (matches === 1 ? '' : 'es'));
}
if (matches <= 0) {
var positions = isColumns ? this.heatMap.getHeatMapElementComponent().getColumnPositions()
: this.heatMap.getHeatMapElementComponent().getRowPositions();
positions.setSquishedIndices(null);
if (isColumns) {
this.heatMap.getProject().setGroupColumns([], true);
} else {
this.heatMap.getProject().setGroupRows([], true);
}
positions.setSize(isColumns ? this.heatMap.getFitColumnSize()
: this.heatMap.getFitRowSize());
} else {
phantasus.DendrogramUtil.squishNonSearchedNodes(this.heatMap,
isColumns);
}
this.heatMap.updateDataset(); // need to update spaces for group
// by
this.heatMap.revalidate();
},
search: function (isRows) {
this.searching = true;
var isMatchesOnTop = isRows ? this.rowSearchObject.$matchesToTop.hasClass(
'btn-primary') : this.columnSearchObject.$matchesToTop.hasClass(
'btn-primary');
var heatMap = this.heatMap;
var project = heatMap.getProject();
var sortKeys = isRows
? project.getRowSortKeys()
: project.getColumnSortKeys();
sortKeys = sortKeys.filter(function (key) {
return !(key instanceof phantasus.MatchesOnTopSortKey &&
key.toString() === 'matches on top');
});
var dataset = project.getSortedFilteredDataset();
var $searchResultsLabel = isRows
? this.rowSearchObject.$searchResults
: this.columnSearchObject.$searchResults;
var searchText = !isRows
? $.trim(this.columnSearchObject.$search.val())
: $.trim(this.rowSearchObject.$search.val());
var metadata = isRows
? dataset.getRowMetadata()
: dataset.getColumnMetadata();
var visibleIndices = [];
heatMap.getVisibleTrackNames(!isRows).forEach(function (name) {
var index = phantasus.MetadataUtil.indexOf(metadata, name);
if (index !== -1) {
visibleIndices.push(index);
}
});
var fullModel = metadata;
metadata = new phantasus.MetadataModelColumnView(metadata,
visibleIndices);
var searchResultViewIndices = phantasus.MetadataUtil.search({
model: metadata,
fullModel: fullModel,
text: searchText,
isColumns: !isRows,
matchAllPredicates: this.matchAllPredicates,
defaultMatchMode: this.matchMode
});
if (searchText === '') {
$searchResultsLabel.html('');
if (isRows) {
this.rowSearchObject.$searchResultsWrapper.hide();
} else {
this.columnSearchObject.$searchResultsWrapper.hide();
}
} else {
$searchResultsLabel.html(searchResultViewIndices.length + ' match'
+ (searchResultViewIndices.length === 1 ? '' : 'es'));
if (isRows) {
this.rowSearchObject.$searchResultsWrapper.show();
} else {
this.columnSearchObject.$searchResultsWrapper.show();
}
}
var searchResultsModelIndices = [];
if (searchResultViewIndices != null) {
for (var i = 0, length = searchResultViewIndices.length; i <
length; i++) {
var viewIndex = searchResultViewIndices[i];
searchResultsModelIndices.push(isRows
? project.convertViewRowIndexToModel(viewIndex)
: project.convertViewColumnIndexToModel(viewIndex));
}
}
if (searchResultViewIndices !== null && isMatchesOnTop) {
var key = new phantasus.MatchesOnTopSortKey(project,
searchResultsModelIndices, 'matches on top', !isRows);
// keep other sort keys
searchResultViewIndices = key.indices; // matching indices
// are now on top
// add to beginning of sort keys
sortKeys.splice(0, 0, key);
if (isRows) {
project.setRowSortKeys(sortKeys, false);
} else {
project.setColumnSortKeys(sortKeys, false);
}
}
var searchResultsViewIndicesSet = new phantasus.Set();
if (searchResultViewIndices != null) {
for (var i = 0, length = searchResultViewIndices.length; i <
length; i++) {
var viewIndex = searchResultViewIndices[i];
searchResultsViewIndicesSet.add(viewIndex);
}
}
if (searchResultViewIndices == null) {
searchResultViewIndices = [];
}
if (isRows) {
this.rowSearchResultModelIndices = searchResultsModelIndices;
this.rowSearchResultViewIndicesSorted = searchResultViewIndices.sort(
function (a, b) {
return a < b ? -1 : 1;
});
this.currentRowSearchIndex = -1;
} else {
this.columnSearchResultModelIndices = searchResultsModelIndices;
this.columnSearchResultViewIndicesSorted = searchResultViewIndices.sort(
function (a, b) {
return a < b ? -1 : 1;
});
this.currentColumnSearchIndex = -1;
}
// update selection
(!isRows
? project.getColumnSelectionModel()
: project.getRowSelectionModel()).setViewIndices(
searchResultsViewIndicesSet, true);
if (isMatchesOnTop) { // resort
if (isRows) {
project.setRowSortKeys(phantasus.SortKey.keepExistingSortKeys(
sortKeys, project.getRowSortKeys()), true);
} else {
project.setColumnSortKeys(
phantasus.SortKey.keepExistingSortKeys(sortKeys,
project.getColumnSortKeys()), true);
}
}
this.updateDimensionsLabel();
this.updateSelectionLabel();
this.searching = false;
},
isSelectionOnTop: function (isColumns) {
var $btn = isColumns
? this.columnSearchObject.$matchesToTop
: this.rowSearchObject.$matchesToTop;
return $btn.hasClass('btn-primary');
},
setSelectionOnTop: function (options) {
if (options.updateButtonStatus) {
var $btn = options.isColumns
? this.columnSearchObject.$matchesToTop
: this.rowSearchObject.$matchesToTop;
if (options.isOnTop) {
$btn.addClass('btn-primary');
} else {
$btn.removeClass('btn-primary');
}
}
var project = this.heatMap.getProject();
var sortKeys = options.isColumns
? project.getColumnSortKeys()
: project.getRowSortKeys();
// remove existing matches on top key
sortKeys = sortKeys.filter(function (key) {
return !(key instanceof phantasus.MatchesOnTopSortKey &&
key.name === 'matches on top');
});
if (options.isOnTop) { // bring to top
var key = new phantasus.MatchesOnTopSortKey(project,
options.isColumns
? this.columnSearchResultModelIndices
: this.rowSearchResultModelIndices,
'matches on top');
sortKeys.splice(0, 0, key);
if (options.isColumns) {
this.heatMap.scrollLeft(0);
} else {
this.heatMap.scrollTop(0);
}
}
this.searching = true;
if (options.isColumns) {
project.setColumnSortKeys(sortKeys, true);
} else {
project.setRowSortKeys(sortKeys, true);
}
this._updateSearchIndices(options.isColumns);
this.searching = false;
}
};
phantasus.HeatMapToolBar.COLUMN_SEARCH_FIELD = 'column';
phantasus.HeatMapToolBar.ROW_SEARCH_FIELD = 'column';
phantasus.HeatMapToolBar.COLUMN_DENDROGRAM_SEARCH_FIELD = 'column_dendrogram';
phantasus.HeatMapToolBar.ROW_DENDROGRAM_SEARCH_FIELD = 'row_dendrogram';
phantasus.HeatMapTooltipProvider = function (heatMap, rowIndex, columnIndex, options, separator, quick, tipText) {
var dataset = heatMap.project.getSortedFilteredDataset();
if (!quick) {
if (options.value) { // key value pairs for custom tooltip
_.each(options.value, function (pair) {
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(pair.name);
tipText.push(': <b>');
if (_.isArray(pair.value)) {
for (var i = 0; i < pair.value.length; i++) {
if (i > 0) {
tipText.push(', ');
}
tipText.push(pair.value[i]);
}
} else {
tipText.push(pair.value);
}
tipText.push('</b>');
});
}
}
if (rowIndex !== -1 && columnIndex !== -1) {
var tooltipSeriesIndices = options.tooltipSeriesIndices ? options.tooltipSeriesIndices : phantasus.Util.sequ32(dataset.getSeriesCount());
for (var i = 0, nseries = tooltipSeriesIndices.length; i < nseries; i++) {
phantasus.HeatMapTooltipProvider._matrixValueToString(heatMap, dataset,
rowIndex, columnIndex, tooltipSeriesIndices[i], tipText, separator,
options.showSeriesNameInTooltip || i > 0);
if (heatMap.options.symmetric && dataset.getValue(rowIndex, columnIndex, tooltipSeriesIndices[i]) !== dataset.getValue(columnIndex, rowIndex, tooltipSeriesIndices[i])) {
phantasus.HeatMapTooltipProvider._matrixValueToString(heatMap, dataset,
columnIndex, rowIndex, tooltipSeriesIndices[i], tipText, separator, false);
}
}
if (quick) {
var quickRowTracks = heatMap.rowTracks.filter(function (t) {
return t.settings.inlineTooltip;
});
phantasus.HeatMapTooltipProvider._tracksToString(quickRowTracks, dataset.getRowMetadata(), rowIndex, tipText, separator);
phantasus.HeatMapTooltipProvider._tracksToString(heatMap.columnTracks.filter(function (t) {
return t.settings.inlineTooltip;
}), dataset.getColumnMetadata(), columnIndex, tipText, separator);
}
} else if (quick) {
if (rowIndex !== -1) {
phantasus.HeatMapTooltipProvider._tracksToString(heatMap.rowTracks.filter(function (t) {
return t.settings.inlineTooltip && options.name !== t.getName();
}), dataset.getRowMetadata(), rowIndex, tipText, separator);
}
if (columnIndex !== -1) {
phantasus.HeatMapTooltipProvider._tracksToString(heatMap.columnTracks.filter(function (t) {
return t.settings.inlineTooltip && options.name !== t.getName();
}), dataset.getColumnMetadata(), columnIndex, tipText, separator);
}
}
if (!quick) {
if (rowIndex !== -1) {
phantasus.HeatMapTooltipProvider._metadataToString(options,
heatMap.rowTracks, dataset.getRowMetadata(), rowIndex,
tipText, separator);
}
if (columnIndex !== -1) {
phantasus.HeatMapTooltipProvider._metadataToString(options,
heatMap.columnTracks, dataset.getColumnMetadata(),
columnIndex, tipText, separator);
}
} else if (options.name != null) {
var metadata = (rowIndex !== -1 ? dataset.getRowMetadata() : dataset.getColumnMetadata());
var vector = metadata.getByName(options.name);
var track = heatMap.getTrack(options.name, columnIndex !== -1);
var colorByName = track != null ? track.settings.colorByField : null;
var additionalVector = colorByName != null ? metadata
.getByName(colorByName) : null;
phantasus.HeatMapTooltipProvider.vectorToString(vector,
rowIndex !== -1 ? rowIndex : columnIndex, tipText, separator,
additionalVector);
}
var rowNodes = [];
var columnNodes = [];
var selectedRowNodes = [];
var selectedColumnNodes = [];
if (options.rowNodes) {
rowNodes = options.rowNodes;
}
if (options.columnNodes) {
columnNodes = options.columnNodes;
}
if (!quick) {
if (heatMap.rowDendrogram) {
selectedRowNodes = _
.values(heatMap.rowDendrogram.selectedRootNodeIdToNode);
}
if (heatMap.columnDendrogram) {
selectedColumnNodes = _
.values(heatMap.columnDendrogram.selectedRootNodeIdToNode);
}
if (selectedRowNodes.length > 0 && rowNodes.length > 0) {
var nodeIds = {};
_.each(selectedRowNodes, function (n) {
nodeIds[n.id] = true;
});
rowNodes = _.filter(rowNodes, function (n) {
return nodeIds[n.id] === undefined;
});
}
if (selectedColumnNodes.length > 0 && columnNodes.length > 0) {
var nodeIds = {};
_.each(selectedColumnNodes, function (n) {
nodeIds[n.id] = true;
});
columnNodes = _.filter(columnNodes, function (n) {
return nodeIds[n.id] === undefined;
});
}
}
phantasus.HeatMapTooltipProvider._nodesToString(tipText, rowNodes, null, separator);
phantasus.HeatMapTooltipProvider._nodesToString(tipText, columnNodes, null, separator);
if (!quick) {
if (selectedRowNodes.length > 0) {
phantasus.HeatMapTooltipProvider._nodesToString(tipText,
selectedRowNodes, heatMap.rowDendrogram._selectedNodeColor,
separator);
}
if (selectedColumnNodes.length > 0) {
phantasus.HeatMapTooltipProvider._nodesToString(tipText,
selectedColumnNodes,
heatMap.columnDendrogram._selectedNodeColor, separator);
}
}
};
phantasus.HeatMapTooltipProvider._matrixValueToString = function (heatMap, dataset, rowIndex, columnIndex, seriesIndex, tipText, separator, showSeriesNameInTooltip) {
var val = dataset.getValue(rowIndex, columnIndex, seriesIndex);
if (val != null) {
var nf = heatMap.getHeatMapElementComponent().getDrawValuesFormat();
if (val.toObject || !_.isNumber(val)) {
var obj = val.toObject ? val.toObject() : val;
if (phantasus.Util.isArray(obj)) {
var v = phantasus.Util.toString(obj);
if (tipText.length > 0) {
tipText.push(separator);
}
if (showSeriesNameInTooltip) {
tipText.push(dataset.getName(seriesIndex));
tipText.push(': ');
}
tipText.push('<b>');
tipText.push(v);
tipText.push('</b>');
} else {
var keys = _.keys(obj);
if (keys.length === 0) {
var v = phantasus.Util.toString(obj);
if (tipText.length > 0) {
tipText.push(separator);
}
if (showSeriesNameInTooltip) {
tipText.push(dataset.getName(seriesIndex));
tipText.push(': ');
}
tipText.push('<b>');
tipText.push(v);
tipText.push('</b>');
} else {
for (var i = 0, nkeys = keys.length; i < nkeys; i++) {
var key = keys[i];
if (key !== '__v') { // special value key
var objVal = obj[key];
var v;
if (phantasus.Util.isArray(objVal)) {
v = phantasus.Util.arrayToString(objVal, ', ');
} else {
v = phantasus.Util.toString(objVal);
}
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(key);
tipText.push(': <b>');
tipText.push(v);
tipText.push('</b>');
}
}
if (_.isNumber(val)) {
tipText.push(separator);
tipText.push('Value: <b>');
tipText.push(nf(val));
tipText.push('</b>');
}
}
}
} else {
if (tipText.length > 0) {
tipText.push(separator);
}
if (showSeriesNameInTooltip) {
tipText.push(dataset.getName(seriesIndex));
tipText.push(': ');
}
tipText.push('<b>');
tipText.push(nf(val));
tipText.push('</b>');
}
}
};
phantasus.HeatMapTooltipProvider.vectorToString = function (vector, index, tipText, separator, additionalVector) {
var arrayValueToString = function (arrayFieldName, arrayVal) {
if (arrayVal != null) {
if (arrayFieldName != null) {
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(arrayFieldName); // e.g. PC3
}
if (arrayVal.toObject) {
tipText.push(' ');
var obj = arrayVal.toObject();
var keys = _.keys(obj);
_.each(keys, function (key) {
var subVal = obj[key];
if (subVal != null && subVal != '') {
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(key);
tipText.push(': <b>');
tipText.push(phantasus.Util.toString(subVal));
tipText.push('</b>');
}
});
} else {
tipText.push(': <b>');
tipText.push(phantasus.Util.toString(arrayVal));
tipText.push('</b>');
}
}
};
if (vector != null) {
var primaryVal = vector.getValue(index);
if (primaryVal != null && primaryVal !== '') {
var primaryFields = vector.getProperties().get(
phantasus.VectorKeys.FIELDS);
if (primaryFields != null) {
var visibleFieldIndices = vector.getProperties().get(
phantasus.VectorKeys.VISIBLE_FIELDS);
if (visibleFieldIndices === undefined) {
visibleFieldIndices = phantasus.Util
.seq(primaryFields.length);
}
var additionalFieldNames = additionalVector != null ? additionalVector
.getProperties().get(phantasus.VectorKeys.FIELDS)
: null;
var additionalVal = additionalFieldNames != null ? additionalVector
.getValue(index)
: null;
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(vector.getName());
for (var j = 0; j < visibleFieldIndices.length; j++) {
arrayValueToString(primaryFields[visibleFieldIndices[j]],
primaryVal[visibleFieldIndices[j]]);
}
if (additionalVal != null) {
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(additionalVector.getName());
for (var j = 0; j < visibleFieldIndices.length; j++) {
arrayValueToString(
additionalFieldNames[visibleFieldIndices[j]],
additionalVal[visibleFieldIndices[j]]);
}
}
} else if (primaryVal.summary) {
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(vector.getName());
tipText.push(': ');
var obj = primaryVal.summary;
var keys = _.keys(obj);
for (var i = 0, nkeys = keys.length; i < nkeys; i++) {
var key = keys[i];
if (key !== '__v') { // special value key
var objVal = obj[key];
var v;
if (phantasus.Util.isArray(objVal)) {
v = phantasus.Util.arrayToString(objVal, ', ');
} else {
v = phantasus.Util.toString(objVal);
}
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(key);
tipText.push(': <b>');
tipText.push(v);
tipText.push('</b>');
}
}
} else {
if (tipText.length > 0) {
tipText.push(separator);
}
tipText.push(vector.getName());
tipText.push(': <b>');
tipText.push(phantasus.Util.toString(primaryVal));
tipText.push('</b>');
}
}
}
};
phantasus.HeatMapTooltipProvider._tracksToString = function (tracks, metadata, index, tipText, separator) {
for (var i = 0; i < tracks.length; i++) {
phantasus.HeatMapTooltipProvider.vectorToString(metadata.getByName(tracks[i].name), index, tipText,
separator);
}
};
phantasus.HeatMapTooltipProvider._metadataToString = function (options, tracks, metadata, index,
tipText, separator) {
var filtered = [];
for (var i = 0, ntracks = tracks.length; i < ntracks; i++) {
var track = tracks[i];
if ((track.isVisible() && track.isShowTooltip())) {
if (tracks[i].name === options.name) { // show the vector that we're mousing over 1st
filtered.splice(0, 0, track);
} else {
filtered.push(track);
}
}
}
phantasus.HeatMapTooltipProvider._tracksToString(filtered, metadata, index, tipText, separator);
};
phantasus.HeatMapTooltipProvider._nodesToString = function (tipText, nodes, color, separator) {
var renderField = function (name, value) {
if (value != null) {
if (tipText.length > 0) {
tipText.push(separator);
}
if (color) {
tipText.push('<span style="color:' + color + '">');
}
tipText.push(name);
tipText.push(': <b>');
if (_.isArray(value)) {
for (var i = 0; i < value.length; i++) {
if (i > 0) {
tipText.push(', ');
}
tipText.push(phantasus.Util.toString(value[i]));
}
} else {
tipText.push(phantasus.Util.toString(value));
}
tipText.push('</b>');
if (color) {
tipText.push('</span>');
}
}
};
_.each(nodes, function (node) {
if (node.info) {
for (var name in node.info) {
var value = node.info[name];
renderField(name, value);
}
}
renderField('height', node.height);
renderField('depth', node.depth);
var nLeafNodes = 1 + Math.abs(node.maxIndex - node.minIndex);
if (nLeafNodes > 0) {
renderField('# of leaf nodes', nLeafNodes);
// renderField('height', node.height);
}
});
};
phantasus.HeatMapTrackColorLegend = function (tracks, colorModel) {
phantasus.AbstractCanvas.call(this, false);
this.tracks = tracks;
this.colorModel = colorModel;
this.canvas.style.position = '';
};
phantasus.HeatMapTrackColorLegend.prototype = {
getPreferredSize: function () {
var tracks = this.tracks;
var colorModel = this.colorModel;
var xpix = 0;
var ypix = 0;
var maxYPix = 0;
var canvas = this.canvas;
var context = canvas.getContext('2d');
context.font = '12px ' + phantasus.CanvasUtil.getFontFamily(context);
for (var i = 0; i < tracks.length; i++) {
ypix = 0;
var maxWidth = 0;
var vector = tracks[i].getVector(tracks[i].settings.colorByField);
if (vector.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
var map = colorModel.getDiscreteColorScheme(vector);
map.forEach(function (color, key) {
var width = context.measureText(key).width;
if (!isNaN(width)) {
maxWidth = Math.max(maxWidth, width);
}
ypix += 14;
});
} else {
maxWidth = 220;
ypix += 40;
}
maxWidth = Math.max(maxWidth,
context.measureText(vector.getName()).width);
xpix += maxWidth + 10 + 14;
maxYPix = Math.max(maxYPix, ypix);
}
return {
width: xpix,
height: maxYPix > 0 ? (maxYPix + 12) : 0
};
},
draw: function (clip, context) {
var tracks = this.tracks;
var colorModel = this.colorModel;
var xpix = 0;
// legends are placed side by side
for (var i = 0; i < tracks.length; i++) {
var ypix = 0;
var vector = tracks[i].getVector(tracks[i].settings.colorByField);
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
context.font = '12px ' + phantasus.CanvasUtil.getFontFamily(context);
context.textAlign = 'left';
// draw name
context.textBaseline = 'top';
context.fillText(vector.getName(), xpix, ypix);
context.strokeStyle = 'LightGrey';
var maxWidth = 0;
var textWidth = context.measureText(vector.getName()).width;
if (!isNaN(textWidth)) {
maxWidth = Math.max(0, textWidth);
}
ypix += 14;
if (vector.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
var toStringFunction = phantasus.VectorTrack.vectorToString(vector);
var map = colorModel.getDiscreteColorScheme(vector);
var values = map.keys().sort(phantasus.SortKey.ASCENDING_COMPARATOR);
values.forEach(function (key) {
if (key != null) {
key = toStringFunction(key);
var color = colorModel.getMappedValue(vector, key);
var textWidth = context.measureText(key).width;
if (!isNaN(textWidth)) {
maxWidth = Math.max(maxWidth, textWidth);
}
context.fillStyle = color;
var xoffset = 0;
if (tracks[i].isRenderAs(phantasus.VectorTrack.RENDER.COLOR)) {
context.fillRect(xpix, ypix, 12, 12);
context.strokeRect(xpix, ypix, 12, 12);
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
xoffset = 16;
}
context.fillText(key, xpix + xoffset, ypix);
ypix += 14;
}
});
} else {
var scheme = colorModel.getContinuousColorScheme(vector);
context.save();
context.translate(xpix, ypix);
phantasus.HeatMapColorSchemeLegend.drawColorScheme(context,
scheme, 200);
context.restore();
maxWidth = Math.max(maxWidth, 220);
ypix += 40;
}
xpix += maxWidth + 10 + 14; // space between tracks + color chip
}
}
};
phantasus.Util.extend(phantasus.HeatMapTrackColorLegend, phantasus.AbstractCanvas);
phantasus.HeatMapTrackFontLegend = function (tracks, model) {
phantasus.AbstractCanvas.call(this, false);
this.tracks = tracks;
this.model = model;
this.canvas.style.position = '';
};
phantasus.HeatMapTrackFontLegend.prototype = {
getPreferredSize: function () {
var tracks = this.tracks;
var model = this.model;
var canvas = this.canvas;
var context = canvas.getContext('2d');
context.font = '900 12px ' + phantasus.CanvasUtil.getFontFamily(context);
var xpix = 0;
var ypix = 0;
var maxYPix = 0;
for (var i = 0; i < tracks.length; i++) {
ypix = 0;
var maxWidth = 0;
var vector = tracks[i].getVector(tracks[i].settings.fontField);
var map = model.getMap(vector.getName());
map.forEach(function (font, key) {
// skip normal font weight
if (font != null && font.weight != '400') {
var width = context.measureText(key).width;
if (!isNaN(width)) {
maxWidth = Math.max(maxWidth, width);
}
ypix += 14;
}
});
xpix += maxWidth + 6;
maxYPix = Math.max(maxYPix, ypix);
}
return {
width: xpix,
height: maxYPix > 0 ? (maxYPix + 30) : 0
};
},
draw: function (clip, context) {
// draw legends horizontally
var tracks = this.tracks;
var model = this.model;
var xpix = 0;
var ypix = 0;
context.textAlign = 'left';
context.textBaseline = 'top';
context.font = '12px ' + phantasus.CanvasUtil.getFontFamily(context);
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
context.strokeStyle = 'black';
var font = context.font;
for (var i = 0; i < tracks.length; i++) {
ypix = 0;
var maxWidth = 0;
var textVector = tracks[i].getVector();
var fontVector = tracks[i].getVector(tracks[i].settings.fontField);
context.font = font;
context.fillText(textVector.getName(), xpix, ypix); // vector name
maxWidth = Math.max(maxWidth,
context.measureText(textVector.getName()).width);
ypix += 14;
var map = model.getMap(fontVector.getName());
var values = map.keys().sort(phantasus.SortKey.ASCENDING_COMPARATOR);
values.forEach(function (key) {
var mappedFont = model.getMappedValue(fontVector, key);
if (mappedFont != null && mappedFont.weight != '400') {
context.font = mappedFont.weight + ' ' + font;
var width = context.measureText(key).width;
if (!isNaN(width)) {
maxWidth = Math.max(maxWidth, width);
}
context.fillText(key, xpix, ypix);
ypix += 14;
}
});
xpix += maxWidth + 6; // space between tracks
}
}
};
phantasus.Util.extend(phantasus.HeatMapTrackFontLegend, phantasus.AbstractCanvas);
phantasus.HeatMapTrackShapeLegend = function (tracks, shapeModel) {
phantasus.AbstractCanvas.call(this, false);
this.tracks = tracks;
this.shapeModel = shapeModel;
this.canvas.style.position = '';
};
phantasus.HeatMapTrackShapeLegend.prototype = {
getPreferredSize: function () {
var tracks = this.tracks;
var shapeModel = this.shapeModel;
var canvas = this.canvas;
var context = canvas.getContext('2d');
var xpix = 0;
var ypix = 0;
var maxYPix = 0;
for (var i = 0; i < tracks.length; i++) {
ypix = 0;
var maxWidth = 0;
var vector = tracks[i].getVector();
var map = shapeModel.getMap(vector.getName());
map.forEach(function (color, key) {
var width = context.measureText(key).width;
if (!isNaN(width)) {
maxWidth = Math.max(maxWidth, width);
}
ypix += 14;
});
xpix += maxWidth + 24;
maxYPix = Math.max(maxYPix, ypix);
}
return {
width: xpix,
height: maxYPix > 0 ? (maxYPix + 30) : 0
};
},
draw: function (clip, context) {
// draw legends horizontally
var tracks = this.tracks;
var shapeModel = this.shapeModel;
var xpix = 0;
var ypix = 0;
context.textAlign = 'left';
context.textBaseline = 'top';
context.font = '12px ' + phantasus.CanvasUtil.getFontFamily(context);
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
context.strokeStyle = 'black';
for (var i = 0; i < tracks.length; i++) {
ypix = 0;
var maxWidth = 0;
var vector = tracks[i].getVector();
context.fillText(vector.getName(), xpix, ypix);
maxWidth = Math.max(maxWidth,
context.measureText(vector.getName()).width);
ypix += 14;
var map = shapeModel.getMap(vector.getName());
var values = map.keys().sort(phantasus.SortKey.ASCENDING_COMPARATOR);
values.forEach(function (key) {
var shape = shapeModel.getMappedValue(vector, key);
var width = context.measureText(key).width;
if (!isNaN(width)) {
maxWidth = Math.max(maxWidth, width);
}
phantasus.CanvasUtil.drawShape(context, shape, xpix + 8,
ypix + 6, 6);
context.fillText(key, xpix + 16, ypix);
ypix += 14;
});
xpix += maxWidth + 24; // space between columns + shape
}
}
};
phantasus.Util.extend(phantasus.HeatMapTrackShapeLegend, phantasus.AbstractCanvas);
/**
* rows and columns can contain field, renameTo, display, order
*
*/
phantasus.HeatMap = function (options) {
phantasus.Util.loadTrackingCode();
var _this = this;
// don't extend
var dontExtend = ['parent', 'columnDendrogram', 'rowDendrogram'];
var cache = [];
for (var i = 0; i < dontExtend.length; i++) {
var field = dontExtend[i];
cache[i] = options[field];
options[field] = null;
}
options = $
.extend(
true,
{},
{
/*
* The element in which to render to the heat map.
*/
el: null,
/*
* A File or URL to a <a target="_blank"
* href="http://support.lincscloud.org/hc/en-us/articles/202105453-GCT-Gene-Cluster-Text-Format-">GCT
* 1.3</a>, ' + '<a target="_blank"
* href="http://www.broadinstitute.org/cancer/software/genepattern/gp_guides/file-formats/sections/gct">GCT
* 1.2</a>, ' + '<a target="_blank"
* href="https://wiki.nci.nih.gov/display/TCGA/Mutation+Annotation+Format+%28MAF%29+Specification">MAF</a>, ' + '<a
* target="_blank",
* href="http://www.broadinstitute.org/cancer/software/gsea/wiki/index.php/Data_formats#GMT:_Gene_Matrix_Transposed_file_format_.28.2A.gmt.29">GMT</a>, ' + '
* or a tab-delimitted text file. Can also be an array
* of File or URLs in which case the datasets are
* combined by matching on column ids.
*/
dataset: undefined,
/*
*
* @description Array of file, datasetField, fileField,
* and include (optional fields to include
* from file). File can be xlsx file,
* tab-delimitted text file, or gmt file.
* <p>
* <b>Example:</b> Annotate rows matching
* 'name' field in dataset to 'id' field in
* file.
* </p>
* <code>[{file:'https://MY_URL', datasetField:'name', fileField:'id'}]</code>
*/
rowAnnotations: undefined,
/*
* Array of file, datasetField, fileField, and include
* (optional fields to include from file). File can be
* xlsx file, tab-delimitted text file, or gmt file.
*/
columnAnnotations: undefined,
/*
* Array of column metadata names to group the heat map
* by.
*
* <p>
* <b>Example:</b> Group by the type and gender
* metadata field.
* </p>
*
* <code>['type', 'gender']</code>
*/
columnGroupBy: undefined,
/*
* Array of row metadata names to group the heat map by.
*
* <p>
* <b>Example:</b> Group by the gene metadata field.
* </p>
* <code>['gene']</code>
*/
rowGroupBy: undefined,
/*
* Object that describes mapping of values to colors.
* scalingMode can be 'fixed' or 'relative'. Stepped indicates
* whether color scheme is continuous (false) or
* discrete (true).
* <p>
* <b>Example:</b> Use a fixed color scheme with color
* stops at -100, -90, 90, and 100.
* <p>
* <code>{ scalingMode : 'fixed', stepped:false, values : [-100, -90, 90, 100], colors : ['blue', 'white', 'white', 'red'] };</code>
*/
colorScheme: undefined,
/*
* Array of metadata names and sort order. Use 0 for
* ascending and 1 for descending. To sort by values use
* modelIndices.
*
* <p>
* <b>Example:</b> Sort ascending by gene, and then
* descending by stdev
* </p>
* <code>[{field:'gene', order:0}, {field:'stdev',
* order:1}]</code>
*/
rowSortBy: undefined,
/*
* Array of metadata names and sort order. Use 0 for
* ascending and 1 for descending.
*
* <p>
* <b>Example:</b> to sort ascending by gene, and then
* descending by stdev
* </p>
* <code> [{name:'gene',
* order:0}, {name:'stdev', order:1}]</code>
*/
columnSortBy: undefined,
/*
* URL to a dendrogram in <a target="_blank"
* href="https://en.wikipedia.org/wiki/Newick_format">Newick
* format</a>
*/
rowDendrogram: undefined,
/*
* URL to a dendrogram in <a target="_blank"
* href="https://en.wikipedia.org/wiki/Newick_format">Newick
* format</a>
*/
columnDendrogram: undefined,
/*
* Column metadata field in dataset used to match leaf
* node ids in column dendrogram Newick file
*/
columnDendrogramField: 'id',
/*
* Row metadata field in dataset used to match leaf node
* ids in row dendrogram Newick file
*/
rowDendrogramField: 'id',
/*
* Array of objects describing how to display row
* metadata fields. Each object in the array must have
* field, and optionally display, order, and renameTo.
* Field is the metadata field name. Display is a comma
* delimited string that describes how to display a
* metadata field. Options are text, color, stacked_bar,
* bar, highlight, shape, discrete, and continuous.
* Order is a number that indicates the order in which
* the field should appear in the heat map. RenameTo
* allows you to rename a field.
*/
rows: [],
/*
* Array of objects describing how to display column
* metadata fields. Each object in the array must have
* field, and optionally display, order, and renameTo.
* Field is the metadata field name. Display is a comma
* delimited string that describes how to display a
* metadata field. Options are text, color, stacked_bar,
* bar, highlight, shape, discrete, and continuous.
* Order is a number that indicates the order in which
* the field should appear in the heat map. RenameTo
* allows you to rename a field.
*/
columns: [],
/*
* Optional array of tools to run at load time. For
* example: <code>tools : [ {
* name : 'Marker Selection',
* params : {
* field : [ comparisonVector.getName() ],
* class_a : [ 'A' ], class_b : [ 'B' ] }} ]</code>
*/
tools: undefined,
/*
* Optional array of {name:string, values:[]}
*/
rowFilter: undefined,
columnFilter: undefined,
/*
* Whether to auto-hide the tab bar when only one tab is visible
*/
autohideTabBar: false,
/*
* Whether this heat map tab can be closed
*/
closeable: true,
/*
* Whether heat map tab can be renamed
*/
rename: true,
/*
* Heat map row size in pixels or 'fit' to fit heat map to current height.
*/
rowSize: 13,
/*
* Heat map column size in pixels or 'fit' to fit heat map to current width.
*/
columnSize: 13,
rowGapSize: 10,
columnGapSize: 10,
/*
* Whether to draw heat map grid
*/
drawGrid: true,
/*
* Heat map grid color
*/
gridColor: '#808080',
showRowNumber: false,
/*
* Heat map grid thickness in pixels
*/
gridThickness: 0.1,
height: 'window', // set the available height for the
// heat map. If not
// set, it will be determined automatically
width: undefined, // set the available width for the
// heat map. If not
// set, it will be determined automatically
/* Whether to focus this tab */
focus: true,
tooltipMode: 0, // 0=top status bar, 1=dialog, 2=follow
inheritFromParent: true,
inheritFromParentOptions: {
transpose: false
},
/** Callback function to invoke for customizing inline matrix tooltips. */
tooltip: undefined,
structureUrlProvider: undefined,
promises: undefined, // additional promises to wait
// for
// not inherited
renderReady: undefined,
// not inherited
datasetReady: undefined,
// inherited
tabOpened: undefined,
loadedCallback: undefined,
name: undefined,
rowsSortable: true,
columnsSortable: true,
popupEnabled: true,
symmetric: false,
keyboard: true,
dummy: false,
inlineTooltip: true,
// Prevent mousewheel default (stops accidental page back on Mac), but also prevents page
// scrolling
standalone: false,
$loadingImage: phantasus.Util.createLoadingEl(),
menu: {
File: [
'Open',
phantasus.aboutDataset.prototype.toString(),
null,
'Save Image',
'Save Dataset',
'Save Session',
'Get dataset link',
null,
'Close Tab',
null,
'Rename Tab'],
Tools: [
'New Heat Map',
null,
'Annotate',
'Create Calculated Annotation',
'Adjust',
'Collapse',
'Similarity Matrix',
'Transpose',
null,
'Sort/Group',
'Filter',
null,
'Differential expression',
'Clustering',
'Plots',
'Pathway analysis',
'Submit to Shiny GAM',
'DEBUG: Probe Debug Tool',
'DEBUG: Expose project',
phantasus.ReproduceTool.prototype.toString()],
View: ['Zoom In', 'Zoom Out', null, 'Fit To Window', 'Fit Rows To Window', 'Fit Columns To Window', null, '100%', null, 'Options'],
Edit: [
'Copy Selected Dataset',
null,
'Move Selected Rows To Top',
'Annotate Selected Rows',
'Copy Selected Rows',
'Invert' +
' Selected Rows',
'Select All Rows',
'Clear Selected Rows',
null,
'Move Selected Columns To Top',
'Annotate Selected Columns',
'Copy Selected Columns',
'Invert' +
' Selected Columns',
'Select All Columns',
'Clear Selected Columns'],
Help: [
'Search Menus', null, 'Contact', 'Configuration', 'Tutorial', 'Source Code', 'About', null, 'Keyboard' +
' Shortcuts']
},
toolbar: {
dimensions: true,
zoom: true,
searchRows: true,
searchColumns: true,
searchValues: false,
options: true,
saveImage: true,
filter: true,
colorKey: true
}
}, options);
for (var i = 0; i < dontExtend.length; i++) {
var field = dontExtend[i];
options[field] = cache[i];
}
if (options.menu == null) {
options.menu = {};
}
if (options.toolbar == null) {
options.toolbar = {};
}
this.options = options;
this.tooltipProvider = phantasus.HeatMapTooltipProvider;
if (!options.el) {
this.$el = $('<div></div>');
} else {
this.$el = $(options.el);
}
this.rowGapSize = options.rowGapSize;
this.columnGapSize = options.columnGapSize;
this.actionManager = new phantasus.ActionManager();
this.actionManager.heatMap = this;
this.$el.addClass('phantasus');
if (this.options.dataset == null) {
var datasetFormBuilder = new phantasus.FormBuilder();
datasetFormBuilder.append({
name: 'file',
type: 'file'
});
this.options.dataset = $.Deferred();
phantasus.FormBuilder.showOkCancel({
title: 'Dataset',
appendTo: this.getContentEl(),
focus: this.getFocusEl(),
content: datasetFormBuilder.$form,
okCallback: function () {
var file = datasetFormBuilder.getValue('file');
phantasus.DatasetUtil.read(file).done(function (dataset) {
// console.log('now resolving here?');
if (dataset.length && dataset.length > 0) {
_this.options.dataset.resolve(dataset[0]);
_this.setName(dataset[0].seriesNames[0]);
for (var i = 1; i < dataset.length; i++) {
var heatmap = new phantasus.HeatMap({
name: dataset[i].seriesNames[0],
dataset: dataset[i],
parent: _this.heatmap,
symmetric: _this.options.symmetric,
inheritFromParent: false
});
// console.log(i, dataset[i], heatmap);
}
}
else {
_this.options.dataset.resolve(dataset);
}
}).fail(function (err) {
_this.options.dataset.reject(err);
});
},
cancelCallback: function () {
_this.options.dataset.reject('Session cancelled.');
}
});
}
if (this.options.name == null) {
this.options.name = (this.options.dataset.options && this.options.dataset.options.exactName) ? this.options.dataset.options.exactName :
phantasus.Util
.getBaseFileName(phantasus.Util
.getFileName(this.options.dataset.file ? this.options.dataset.file
: this.options.dataset));
}
var isPrimary = this.options.parent == null;
if (this.options.parent == null) {
if (!phantasus.Util.isHeadless()) {
if (this.options.tabManager == null) {
this.tabManager =
new phantasus.TabManager({
landingPage: function () {
if (_this.options.landingPage == null) {
_this.options.landingPage = new phantasus.LandingPage({tabManager: _this.tabManager, autoInit: false});
_this.options.landingPage.$el.prependTo(_this.$el);
}
return _this.options.landingPage;
},
autohideTabBar: this.options.autohideTabBar
});
} else {
this.tabManager = this.options.tabManager;
}
if (this.options.tabManager == null) {
this.tabManager.appendTo(this.$el);
}
}
} else {
if (this.options.inheritFromParent) {
this.popupItems = this.options.parent.popupItems;
if (!this.options.tabOpened) {
this.options.tabOpened = this.options.parent.options.tabOpened;
}
this.options.drawCallback = this.options.parent.options.drawCallback;
}
this.tabManager = this.options.parent.tabManager;
}
this.$content = $('<div></div>');
this.$content.css({
'width': '100%',
'user-select': 'none',
'-webkit-user-select': 'none',
'-webkit-user-drag': 'none',
'-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0)',
'-moz-user-select': 'none',
'-moz-user-drag': 'none',
'-moz-tap-highlight-color': 'rgba(0, 0, 0, 0)',
'-ms-user-select': 'none',
'-ms-user-drag': 'none',
'-ms-tap-highlight-color': 'rgba(0, 0, 0, 0)',
'-o-user-select': 'none',
'-o-user-drag': 'none',
'-o-tap-highlight-color': 'rgba(0, 0, 0, 0)',
'overflow-x': 'visible',
'overflow-y': 'visible'
});
this.$content.on('remove.phantasus', function () {
_this.$content.off('remove.phantasus');
_this.dispose();
});
if (!phantasus.Util.isHeadless()) {
var tab = this.tabManager.add({
$el: this.$content,
closeable: this.options.closeable,
rename: this.options.rename,
title: this.options.name,
object: this,
focus: this.options.focus
});
this.tabId = tab.id;
this.$tabPanel = tab.$panel;
}
if (options.$loadingImage) {
options.$loadingImage.appendTo(this.$content);
}
this.options.dataSource = !options.dataset ? ''
: (options.dataset.file ? options.dataset.file : options.dataset);
this._togglingInfoWindow = false;
var promises = [];
if (options.promises) {
for (var i = 0; i < options.promises.length; i++) {
promises.push(options.promises[i]);
}
}
this.whenLoaded = [];
if (options.rowAnnotations) {
var rowDef = phantasus.DatasetUtil.annotate({
annotations: options.rowAnnotations,
isColumns: false
});
rowDef.done(function (callbacks) {
_this.whenLoaded = _this.whenLoaded.concat(callbacks);
});
promises.push(rowDef);
}
//// console.log("HeatMap creation ::", options.columnAnnotations);
if (options.columnAnnotations) {
var columnDef = phantasus.DatasetUtil.annotate({
annotations: options.columnAnnotations,
isColumns: true
});
columnDef.done(function (callbacks) {
_this.whenLoaded = _this.whenLoaded.concat(callbacks);
});
promises.push(columnDef);
}
if (options.rowDendrogram != null
&& _.isString(options.rowDendrogram)) {
if (options.rowDendrogram[0] === '(') {
_this.options.rowDendrogram = phantasus.DendrogramUtil
.parseNewick(options.rowDendrogram);
} else {
var rowDendrogramDeferred = phantasus.Util
.getText(options.rowDendrogram);
rowDendrogramDeferred.done(function (text) {
_this.options.rowDendrogram = phantasus.DendrogramUtil
.parseNewick(text);
});
promises.push(rowDendrogramDeferred);
}
}
if (options.columnDendrogram != null
&& _.isString(options.columnDendrogram)) {
if (options.columnDendrogram[0] === '(') {
_this.options.columnDendrogram = phantasus.DendrogramUtil.parseNewick(options.columnDendrogram);
} else {
var columnDendrogramDeferred = phantasus.Util.getText(options.columnDendrogram);
columnDendrogramDeferred.done(function (text) {
_this.options.columnDendrogram = phantasus.DendrogramUtil.parseNewick(text);
});
promises.push(columnDendrogramDeferred);
}
}
var heatMapLoaded = function () {
// console.log('heatMapLoaded', _this.options.name);
if (!options.dummy) {
phantasus.DatasetUtil.toESSessionPromise(options.dataset);
}
if (typeof window !== 'undefined') {
$(window).on('orientationchange.phantasus resize.phantasus', _this.resizeListener = function () {
_this.revalidate();
});
}
_this.revalidate();
if (options.loadedCallback) {
options.loadedCallback(_this);
}
if (_this.tabManager) {
if (_this.options.focus) {
_this.tabManager.setActiveTab(tab.id);
_this.focus();
} else if (_this.tabManager.getTabCount() === 1) {
_this.tabManager.setActiveTab(tab.id);
}
}
_this.$el.trigger('heatMapLoaded', _this);
};
if (phantasus.Util.isArray(options.dataset)) {
var d = phantasus.DatasetUtil.readDatasetArray(options.dataset);
d.fail(function (message) {
if (_this.options.$loadingImage) {
_this.options.$loadingImage.remove();
}
if (_this.options.error) {
_this.options.error(message);
}
phantasus.FormBuilder.showInModal({
title: 'Error',
html: message,
appendTo: _this.getContentEl(),
focus: _this.getFocusEl()
});
});
d
.done(function (joined) {
if (_this.options.$loadingImage) {
_this.options.$loadingImage.remove();
}
_this.options.dataset = joined;
_this._init();
if (joined.getRowMetadata().getByName('Source') != null
&& !_this.options.colorScheme) {
_this.heatmap.getColorScheme().setSeparateColorSchemeForRowMetadataField(
'Source');
}
_.each(
options.dataset,
function (option) {
if (option.colorScheme) {
_this.heatmap.getColorScheme().setCurrentValue(
phantasus.Util.getBaseFileName(phantasus.Util.getFileName(option.dataset)));
_this.heatmap.getColorScheme().setColorSupplierForCurrentValue(
phantasus.AbstractColorSupplier.fromJSON(option.colorScheme));
} else {
try {
_this.autoDisplay({
extension: phantasus.Util.getExtension(phantasus.Util.getFileName(option.dataset)),
filename: phantasus.Util.getBaseFileName(phantasus.Util.getFileName(option.dataset))
});
}
catch (x) {
console.log('Autodisplay errror');
}
}
});
heatMapLoaded();
});
} else {
var deferred = options.dataset.file ? phantasus.DatasetUtil.read(
options.dataset.file, options.dataset.options)
: phantasus.DatasetUtil.read(options.dataset);
deferred.done(function (dataset) {
// console.log('resolving here?', dataset);
if (dataset.length && dataset.length > 0) {
_this.options.dataset = dataset[0];
_this.setName(dataset[0].seriesNames[0]);
_this.tabManager.trigger('rename');
for (var i = 1; i < dataset.length; i++) {
var heatmap = new phantasus.HeatMap({
name: dataset[i].seriesNames[0],
dataset: dataset[i],
symmetric: _this.options.symmetric,
inheritFromParent: false
});
console.log(i, dataset[i], heatmap);
}
}
else {
_this.options.dataset = dataset;
}
});
deferred.fail(function (err) {
_this.options.$loadingImage.remove();
var message = [
'Error opening '
+ (options.dataset.file ? phantasus.Util.getFileName(options.dataset.file) : phantasus.Util.getFileName(options.dataset)) + '. '];
if (err.message) {
message.push('<br />Cause: ');
message.push(err.message);
}
if (_this.options.error) {
_this.options.error(message);
}
phantasus.FormBuilder.showInModal({
title: 'Error',
html: message.join('<br/>')
});
_this.tabManager.remove();
});
promises.push(deferred);
var datasetOverlay = null;
if (options.datasetOverlay) {
var d = options.datasetOverlay.file ? phantasus.DatasetUtil.read(
options.datasetOverlay.file, options.datasetOverlay.options)
: phantasus.DatasetUtil.read(options.datasetOverlay);
d.done(function (dataset) {
datasetOverlay = dataset;
});
promises.push(d);
}
$.when.apply($, promises).done(function () {
if (_this.options.$loadingImage) {
_this.options.$loadingImage.remove();
}
if (_this.options.dataset == null) {
return _this.tabManager.remove(_this.tabId);
}
_this._init();
if (datasetOverlay) {
phantasus.DatasetUtil.overlay({
dataset: _this.options.dataset,
newDataset: datasetOverlay,
rowAnnotationName: 'id',
newRowAnnotationName: 'id',
columnAnnotationName: 'id',
newColumnAnnotationName: 'id'
});
}
heatMapLoaded();
});
}
};
phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS = 6;
/**
*
* @param tool A tool instance
* @param heatMap The calling heat map instance
* @param callback Optional callback to invoke when tool is done
*/
phantasus.HeatMap.showTool = function (tool, heatMap, callback) {
if (tool.gui) {
var gui = tool.gui(heatMap.getProject());
var formBuilder = new phantasus.FormBuilder();
_.each(gui, function (item) {
formBuilder.append(item);
});
var tabId = heatMap.getTabManager().getActiveTabId();
if (tool.init) {
tool.init(heatMap.getProject(), formBuilder, {
heatMap: heatMap
});
}
heatMap.trigger('beforeToolShown', {
tool: tool,
formBuilder: formBuilder
});
var okCallback = function () {
var $dialogContent = $('<div><span>' + tool.toString() + '...</span><button class="btn' +
' btn-xs btn-default" style="margin-left:6px;display: none;">Cancel</button></div>');
var value = null;
var $dialog = phantasus.FormBuilder.showInDraggableDiv({
$content: $dialogContent,
appendTo: heatMap.getContentEl(),
width: 'auto'
});
var input = {};
_.each(gui, function (item) {
input[item.name] = formBuilder.getValue(item.name);
});
// give ui a chance to update
setTimeout(function () {
value = tool.execute({
heatMap: heatMap,
project: heatMap.getProject(),
input: input
});
if (value instanceof Worker) {
$dialogContent.find('button').css('display', '').on('click', function () {
value.terminate();
});
value.onerror = function (e) {
value.terminate();
phantasus.FormBuilder.showInModal({
title: 'Error',
html: e,
close: 'Close',
focus: heatMap.getFocusEl(),
appendTo: heatMap.getContentEl()
});
if (e.stack) {
console.log(e.stack);
}
};
var terminate = _.bind(value.terminate, value);
value.terminate = function () {
terminate();
$dialog.remove();
if (callback) {
callback(input);
}
};
} else {
if (value != null && typeof value.done === 'function') { // promise
value.always(function () {
if (callback) {
callback(input);
}
$dialog.remove();
});
} else {
if (callback) {
callback(input);
}
$dialog.remove();
}
}
}, 20);
setTimeout(function () {
// in case an exception was thrown
if (!(value instanceof Worker)) {
$dialog.remove();
}
}, 5000);
};
var $formDiv;
tool.ok = function () {
$formDiv.modal('hide');
okCallback();
};
var guiOptions = $.extend({}, {
ok: true
}, gui.options);
$formDiv = phantasus.FormBuilder.showOkCancel({
title: tool.toString(),
apply: tool.apply,
ok: guiOptions.ok,
cancel: guiOptions.cancel,
size: guiOptions.size,
draggable: true,
content: formBuilder.$form,
appendTo: heatMap.getContentEl(),
align: 'right',
okCallback: okCallback,
focus: heatMap.getFocusEl()
});
}
else { // run headless
tool.execute({
heatMap: heatMap,
project: heatMap.getProject(),
input: {}
});
if (callback) {
callback({});
}
}
};
phantasus.HeatMap.getSpaces = function (groupByKeys, length, gapSize) {
var previousArray = [];
var nkeys = groupByKeys.length;
for (var keyIndex = 0; keyIndex < nkeys; keyIndex++) {
var key = groupByKeys[keyIndex];
previousArray.push(key.getValue(0));
}
var spaces = [];
var sum = 0;
spaces.push(sum);
for (var i = 1; i < length; i++) {
var isEqual = true;
for (var keyIndex = 0; keyIndex < nkeys; keyIndex++) {
var key = groupByKeys[keyIndex];
var comparator = key.getComparator();
var val = key.getValue(i);
var c = comparator(val, previousArray[keyIndex]);
if (c !== 0) { // not equal, add space
isEqual = false;
for (var keyIndex2 = 0; keyIndex2 < nkeys; keyIndex2++) {
previousArray[keyIndex2] = groupByKeys[keyIndex2].getValue(i);
}
break;
}
}
if (!isEqual) {
sum += gapSize;
}
spaces.push(sum);
}
return spaces;
};
phantasus.HeatMap.createGroupBySpaces = function (dataset, groupByKeys, gapSize, isColumns) {
if (groupByKeys.length > 0) {
var nkeys = groupByKeys.length;
for (var keyIndex = 0; keyIndex < nkeys; keyIndex++) {
groupByKeys[keyIndex].init(groupByKeys[keyIndex].isColumns() ? new phantasus.TransposedDatasetView(dataset) : dataset);
}
return phantasus.HeatMap.getSpaces(groupByKeys, isColumns ? dataset.getColumnCount() : dataset.getRowCount(),
gapSize);
}
};
phantasus.HeatMap.isDendrogramVisible = function (project, isColumns) {
var sortKeys = isColumns ? project.getColumnSortKeys() : project.getRowSortKeys();
if (sortKeys.length === 0) {
return true;
}
// var filter = isColumns ? this.project.getColumnFilter()
// : this.project.getRowFilter();
// // TODO compare filters
var size = isColumns ? project.getSortedFilteredDataset().getColumnCount()
: project.getSortedFilteredDataset().getRowCount();
for (var i = 0; i < sortKeys.length; i++) {
if (!sortKeys[i].isPreservesDendrogram() || sortKeys[i].nvisible !== size) {
return false;
}
}
return true;
};
phantasus.HeatMap.prototype = {
updatingScroll: false,
getWhitespaceEl: function () {
return this.$whitespace;
},
getActionManager: function () {
return this.actionManager;
},
autoDisplay: function (options) {
if (options.filename == null) {
options.filename = '';
}
var colorScheme;
if (options.extension === 'segtab' || options.extension === 'seg') {
colorScheme = {
scalingMode: 'fixed',
values: phantasus.HeatMapColorScheme.Predefined.CN().values.map(function (value) {
return Math.pow(2, 1 + value);
}),
colors: phantasus.HeatMapColorScheme.Predefined.CN().colors
};
} else if (options.extension === 'maf') {
colorScheme = phantasus.HeatMapColorScheme.Predefined.MAF();
var rowMutProfile = this.project.getFullDataset().getRowMetadata().getByName('mutation_summary');
var fieldNames = rowMutProfile.getProperties().get(phantasus.VectorKeys.FIELDS);
var useMafColorMap = true;
if (fieldNames.length !== phantasus.MafFileReader.FIELD_NAMES.length) {
useMafColorMap = false;
} else {
for (var i = 0; i < fieldNames.length; i++) {
if (fieldNames[i] !== phantasus.MafFileReader.FIELD_NAMES[i]) {
useMafColorMap = false;
break;
}
}
}
if (!useMafColorMap) {
colorScheme = {
scalingMode: 'fixed',
stepped: true,
values: [0],
colors: ['rgb(255,255,255)']
};
for (var i = 0; i < fieldNames.length; i++) {
colorScheme.values.push(i + 1);
colorScheme.colors.push(phantasus.VectorColorModel.TWENTY_COLORS[i % phantasus.VectorColorModel.TWENTY_COLORS.length]);
colorScheme.names.push(fieldNames[i]);
}
}
var columnMutationSummaryVectors = [];
var columnMutationSummaryNames = ['mutation_summary', 'mutation_summary_selection'];
for (var i = 0; i < columnMutationSummaryNames.length; i++) {
var name = columnMutationSummaryNames[i];
if (this.project.getFullDataset().getColumnMetadata().getByName(name)) {
columnMutationSummaryVectors.push(this.project.getFullDataset().getColumnMetadata().getByName(name));
track = this.getTrack(name, true);
if (track) {
track.settingFromConfig('stacked_bar');
if (name === 'mutation_summary_selection') {
track.settings.autoscaleAlways = true;
}
}
}
}
var track = this.getTrack('mutation_summary', false);
if (track) {
track.settingFromConfig('stacked_bar');
}
for (var i = 1; i < colorScheme.colors.length; i++) {
if (rowMutProfile) {
this.getProject().getRowColorModel().setMappedValue(
rowMutProfile, i - 1, colorScheme.colors[i]);
}
for (var j = 0; j < columnMutationSummaryVectors.length; j++) {
this.getProject().getColumnColorModel().setMappedValue(
columnMutationSummaryVectors[j], i - 1, colorScheme.colors[i]);
}
}
} else if (options.extension === 'gmt') {
colorScheme = phantasus.HeatMapColorScheme.Predefined.BINARY();
} else if (options.filename === 'all_lesions.conf_99'
|| options.filename === 'all_data_by_genes.txt' || options.filename.toLowerCase().indexOf('gistic') !== -1) {
colorScheme = {
scalingMode: 'fixed',
values: [-0.5, 0, 0.5],
colors: ['blue', 'white', 'red']
};
} else if (options.filename.toLowerCase().indexOf('copynumber') !== -1 ||
options.filename.toLowerCase().indexOf('copy number') !== -1) {
colorScheme = {
scalingMode: 'fixed',
values: [-1.5, 0, 1.5],
colors: ['blue', 'white', 'red']
};
} else if (options.filename.toLowerCase().indexOf('achilles') !== -1) {
colorScheme = {
scalingMode: 'fixed',
values: [-3, -1, 1, 3],
colors: ['blue', 'white', 'white', 'red']
};
}
if (colorScheme && options.filename && this.heatmap.getColorScheme()) {
this.heatmap.getColorScheme().setCurrentValue(options.filename);
this.heatmap.getColorScheme().setColorSupplierForCurrentValue(
phantasus.AbstractColorSupplier.fromJSON(colorScheme));
}
return colorScheme;
},
/**
*
* @param sortOrder
* @param isColumns
* Whether sorting based on column selection
* @param append
* Whether to add to existing sort order
*/
sortBasedOnSelection: function (sortOrder, isColumns, append) {
// if isColumns, sort rows
var project = this.project;
var selectionModel = isColumns ? project.getColumnSelectionModel()
: project.getRowSelectionModel();
var modelIndices = selectionModel.toModelIndices();
if (modelIndices.length === 0) {
return;
}
var priorSortKeyIndex = -1;
if (sortOrder == null) {
// toggle sort order?
var existingSortKeys = isColumns ? project.getRowSortKeys()
: project.getColumnSortKeys();
for (var i = 0, length = existingSortKeys.length; i < length; i++) {
var key = existingSortKeys[i];
if (key instanceof phantasus.SortByValuesKey
&& phantasus.Util.arrayEquals(key.modelIndices,
modelIndices)) {
priorSortKeyIndex = i;
if (key.getSortOrder() === phantasus.SortKey.SortOrder.UNSORTED) {
sortOrder = phantasus.SortKey.SortOrder.DESCENDING; // 1st
// click
} else if (key.getSortOrder() === phantasus.SortKey.SortOrder.DESCENDING) {
sortOrder = phantasus.SortKey.SortOrder.ASCENDING; // 2nd
// click
} else if (key.getSortOrder() === phantasus.SortKey.SortOrder.ASCENDING) {
sortOrder = phantasus.SortKey.SortOrder.TOP_N; // 3rd
// click
} else if (key.getSortOrder() === phantasus.SortKey.SortOrder.TOP_N) {
sortOrder = phantasus.SortKey.SortOrder.UNSORTED; // 4th
// click
}
break;
}
}
}
if (sortOrder == null) {
sortOrder = phantasus.SortKey.SortOrder.DESCENDING;
}
var sortKeys;
if (append) {
sortKeys = !isColumns ? project.getColumnSortKeys() : project.getRowSortKeys();
if (priorSortKeyIndex !== -1) {
if (sortOrder === phantasus.SortKey.SortOrder.UNSORTED) {
// remove existing sort key
sortKeys.splice(priorSortKeyIndex, 1);
} else {
sortKeys[priorSortKeyIndex].setSortOrder(sortOrder);
}
} else {
if (sortOrder !== phantasus.SortKey.SortOrder.UNSORTED) {
sortKeys.push(new phantasus.SortByValuesKey(modelIndices,
sortOrder, !isColumns));
}
// add new sort key
}
sortKeys = phantasus.SortKey.keepExistingSortKeys(sortKeys,
!isColumns ? project.getColumnSortKeys() : project.getRowSortKeys());
} else {
var newSortKeys = sortOrder === phantasus.SortKey.SortOrder.UNSORTED ? []
: [
new phantasus.SortByValuesKey(modelIndices, sortOrder,
!isColumns)];
sortKeys = phantasus.SortKey.keepExistingSortKeys(newSortKeys,
!isColumns ? project.getColumnSortKeys() : project.getRowSortKeys());
}
if (!isColumns) { // sort columns by selected rows
project.setColumnSortKeys(sortKeys, true);
this.scrollLeft(0);
} else { // sort rows by selected column
project.setRowSortKeys(sortKeys, true);
this.scrollTop(0);
}
phantasus.Util.trackEvent({
eventCategory: 'Tool',
eventAction: isColumns ? 'sortRowsBasedOnSelection' : 'sortColumnsBasedOnSelection'
});
},
getToolbarElement: function () {
return this.toolbar.$el;
},
getToolbar: function () {
return this.toolbar;
},
setName: function (name) {
this.options.name = name;
if (this.tabId) {
this.tabManager.setTabName(this.tabId, name);
}
},
getName: function () {
return this.options.name;
},
showOptions: function () {
new phantasus.HeatMapOptions(this);
},
getProject: function () {
return this.project;
},
getDendrogram: function (isColumns) {
return isColumns ? this.columnDendrogram : this.rowDendrogram;
},
toJSON: function (options) {
var _this = this;
var json = {};
// color scheme
json.colorScheme = this.heatmap.getColorScheme().toJSON();
json.name = this.options.name;
json.showRowNumber = this.isShowRowNumber();
// annotation shapes
json.rowShapeModel = this.getProject().getRowShapeModel().toJSON(this.getVisibleTracks(false));
json.columnShapeModel = this.getProject().getColumnShapeModel().toJSON(this.getVisibleTracks(true));
// annotation font
json.rowFontModel = this.getProject().getRowFontModel().toJSON(this.getVisibleTracks(false));
json.columnFontModel = this.getProject().getColumnFontModel().toJSON(this.getVisibleTracks(true));
// annotation colors
json.rowColorModel = this.getProject().getRowColorModel().toJSON(this.getVisibleTracks(false));
json.columnColorModel = this.getProject().getColumnColorModel().toJSON(this.getVisibleTracks(true));
// annotation display
json.rows = this.getVisibleTracks(false).map(function (track) {
var size = phantasus.CanvasUtil.getPreferredSize(_this.getTrackHeaderByIndex(_this.getTrackIndex(track.getName(), false), false));
var obj = track.settings;
obj.field = track.getName();
obj.size = {
width: size.widthSet ? size.width : undefined
};
return obj;
});
json.columns = this.getVisibleTracks(true).map(function (track) {
var size = phantasus.CanvasUtil.getPreferredSize(_this.getTrackHeaderByIndex(_this.getTrackIndex(track.getName(), true), true));
var obj = track.settings;
obj.field = track.getName();
obj.size = {
width: size.widthSet ? size.width : undefined,
height: size.heightSet ? size.height : undefined
};
return obj;
});
// sort
json.rowSortBy = phantasus.SortKey.toJSON(this.getProject().getRowSortKeys());
json.columnSortBy = phantasus.SortKey.toJSON(this.getProject().getColumnSortKeys());
// group
json.rowGroupBy = phantasus.SortKey.toJSON(this.getProject().getGroupRows());
json.columnGroupBy = phantasus.SortKey.toJSON(this.getProject().getGroupColumns());
// filter
json.rowFilter = phantasus.CombinedFilter.toJSON(this.getProject().getRowFilter());
json.columnFilter = phantasus.CombinedFilter.toJSON(this.getProject().getColumnFilter());
// element size, symmetric
json.symmetric = this.options.symmetric;
json.rowSize = this.heatmap.getRowPositions().getSize();
json.columnSize = this.heatmap.getColumnPositions().getSize();
json.rowGapSize = this.heatmap.rowGapSize;
json.columnGapSize = this.heatmap.columnGapSize;
json.drawGrid = this.heatmap.isDrawGrid();
json.gridColor = this.heatmap.getGridColor();
json.gridThickness = this.heatmap.getGridThickness();
json.drawValues = this.heatmap.isDrawValues();
// selection
json.rowSelection = this.getProject().getRowSelectionModel().toModelIndices();
json.columnSelection = this.getProject().getColumnSelectionModel().toModelIndices();
// search terms
json.rowSearchTerm = this.toolbar.getSearchField(phantasus.HeatMapToolBar.ROW_SEARCH_FIELD).val();
json.columnSearchTerm = this.toolbar.getSearchField(phantasus.HeatMapToolBar.COLUMN_SEARCH_FIELD).val();
// dendrogram
if (this.rowDendrogram != null) {
var out = [];
phantasus.DendrogramUtil.writeNewick(this.rowDendrogram.tree.rootNode, out, function (n) {
return n.index;
});
json.rowDendrogram = out.join('');
json.rowDendrogramField = null;
}
if (this.columnDendrogram != null) {
var out = [];
phantasus.DendrogramUtil.writeNewick(this.columnDendrogram.tree.rootNode, out, function (n) {
return n.index;
});
json.columnDendrogram = out.join('');
json.columnDendrogramField = null;
}
if (options.dataset) {
json.dataset = phantasus.Dataset.toJSON(this.getProject().getFullDataset());
}
return json;
},
/**
* @param tree
* An object with maxHeight, a rootNode, leafNodes, and
* nLeafNodes
*/
setDendrogram: function (tree, isColumns, modelOrder) {
var dendrogram = isColumns ? this.columnDendrogram : this.rowDendrogram;
if (dendrogram) {
dendrogram.dispose();
dendrogram = null;
}
if (tree != null) {
// var modelIndexSet = new phantasus.Set();
var size = isColumns ? this.project.getFullDataset().getColumnCount() : this.project.getFullDataset().getRowCount();
if (isColumns) {
dendrogram = new phantasus.ColumnDendrogram(this, tree,
this.heatmap.getColumnPositions(), this.project);
dendrogram.filter = this.project.getColumnFilter().shallowClone();
this.columnDendrogram = dendrogram;
var sortKey = new phantasus.SpecifiedModelSortOrder(modelOrder,
modelOrder.length, 'dendrogram', true);
sortKey.setPreservesDendrogram(true);
sortKey.setLockOrder(2);
sortKey.setUnlockable(false);
this.project.setColumnSortKeys(
[sortKey], true);
} else {
dendrogram = new phantasus.RowDendrogram(this, tree,
this.heatmap.getRowPositions(), this.project);
dendrogram.filter = this.project.getRowFilter().shallowClone();
this.rowDendrogram = dendrogram;
var sortKey = new phantasus.SpecifiedModelSortOrder(modelOrder,
modelOrder.length, 'dendrogram', false);
sortKey.setPreservesDendrogram(true);
sortKey.setLockOrder(2);
sortKey.setUnlockable(false);
this.project.setRowSortKeys(
[sortKey], true);
}
dendrogram.appendTo(this.$parent);
dendrogram.$label.appendTo(this.$parent);
dendrogram.$squishedLabel.appendTo(this.$parent);
} else { // no more dendrogram
var sortKeys = isColumns ? this.project.getColumnSortKeys()
: this.project.getRowSortKeys();
// remove dendrogram sort key
for (var i = 0; i < sortKeys.length; i++) {
if (sortKeys[i].isPreservesDendrogram()) {
sortKeys.splice(i, 1);
i--;
}
}
if (isColumns) {
this.heatmap.getColumnPositions().setSquishedIndices(null);
delete this.columnDendrogram;
this.project.setColumnSortKeys(sortKeys, true);
} else {
delete this.rowDendrogram;
this.project.setRowSortKeys(sortKeys, true);
this.heatmap.getRowPositions().setSquishedIndices(null);
}
}
// FIXME update grouping
this.trigger('dendrogramChanged', {
isColumns: isColumns
});
},
getTabManager: function () {
return this.tabManager;
},
getSelectedElementsText: function () {
var _this = this;
var project = this.project;
var selectedViewIndices = project.getElementSelectionModel().getViewIndices();
if (selectedViewIndices.size() > 0) {
var tipText = [];
var dataset = project.getSortedFilteredDataset();
var rowTracks = _this.rowTracks.filter(function (t) {
return t.settings.inlineTooltip;
});
var columnTracks = _this.columnTracks.filter(function (t) {
return t.settings.inlineTooltip;
});
selectedViewIndices.forEach(function (id) {
var rowIndex = id.getArray()[0];
var columnIndex = id.getArray()[1];
tipText.push(phantasus.Util.nf(dataset.getValue(rowIndex,
columnIndex)));
rowTracks.forEach(function (track) {
tipText.push('\t');
tipText.push(phantasus.Util.toString(dataset.getRowMetadata().getByName(track.name).getValue(
rowIndex)));
});
columnTracks.forEach(function (track) {
tipText.push('\t');
tipText.push(phantasus.Util.toString(dataset.getColumnMetadata().getByName(track.name).getValue(columnIndex)));
});
tipText.push('\n');
});
return tipText.join('');
}
},
_init: function () {
var _this = this;
phantasus.MetadataUtil.renameFields(this.options.dataset, this.options);
var dataset = this.options.dataset;
var rowDendrogram = this.options.rowDendrogram;
var columnDendrogram = this.options.columnDendrogram;
_.each(this.whenLoaded, function (f) {
f(_this.options.dataset);
});
if (this.options.datasetReady) {
var updatedDataset = this.options.datasetReady(dataset);
if (updatedDataset) {
dataset = updatedDataset;
}
}
this.project = new phantasus.Project(dataset);
if (this.tabManager) {
this.tabManager.setTabTitle(this.tabId, this.project.getFullDataset().getRowCount()
+ ' row'
+ phantasus.Util.s(this.project.getFullDataset().getRowCount())
+ ' x '
+ this.project.getFullDataset().getColumnCount()
+ ' column'
+ phantasus.Util.s(this.project.getFullDataset().getColumnCount()));
}
if (this.options.inheritFromParent && this.options.parent != null) {
phantasus.HeatMap.copyFromParent(this.project, this.options);
}
// filter ui will be initialized automatically
if (this.options.rowFilter) {
phantasus.CombinedFilter.fromJSON(_this.project.getRowFilter(), this.options.rowFilter);
_this.project.setRowFilter(_this.project.getRowFilter(), true);
}
if (this.options.columnFilter) {
phantasus.CombinedFilter.fromJSON(_this.project.getColumnFilter(), this.options.columnFilter);
_this.project.setColumnFilter(_this.project.getColumnFilter(), true);
}
this.whenLoaded = null;
this.$parent = $('<div></div>').css('position', 'relative');
this.$parent.appendTo(this.$content);
if (!phantasus.Util.isHeadless()) {
this.toolbar = new phantasus.HeatMapToolBar(this);
}
// scroll bars at the bottom of the heatmap, and right of the heatmap
// TODO along bottom of row metadata, and along left of column metadata
// the viewport is the size of the visible region, the view is the full
// size of the heat map
this.vscroll = new phantasus.ScrollBar(true);
this.vscroll.appendTo(this.$parent);
this.vscroll.on('scroll', function () {
if (_this.updatingScroll) {
return;
}
_this.paintAll({
paintRows: true,
paintColumns: false,
invalidateRows: true,
invalidateColumns: false
});
});
// for resizing column dendrogram
this.beforeColumnTrackDivider = new phantasus.Divider(false);
this.beforeColumnTrackDivider.appendTo(this.$parent);
var dragStartHeight = 0;
this.beforeColumnTrackDivider.on('resizeStart', function (e) {
dragStartHeight = _this.columnDendrogram.getUnscaledHeight();
}).on('resize', function (e) {
// grow or shrink the column dendrogram
var newHeight = Math.max(8, dragStartHeight + e.delta);
_this.columnDendrogram.setPrefHeight(newHeight);
_this.revalidate();
}).on('resizeEnd', function () {
dragStartHeight = 0;
});
// for resizing row dendrogram
this.afterRowDendrogramDivider = new phantasus.Divider(true);
this.afterRowDendrogramDivider.appendTo(this.$parent);
var rowDendrogramStartWidth = 0;
this.afterRowDendrogramDivider.on('resizeStart', function (e) {
rowDendrogramStartWidth = _this.rowDendrogram.getUnscaledWidth();
}).on('resize', function (e) {
// grow or shrink the column dendrogram
var newWidth = Math.max(8, rowDendrogramStartWidth + e.delta);
_this.rowDendrogram.setPrefWidth(newWidth);
_this.revalidate();
}).on('resizeEnd', function () {
rowDendrogramStartWidth = 0;
});
this.afterVerticalScrollBarDivider = new phantasus.Divider(true);
this.afterVerticalScrollBarDivider.appendTo(this.$parent);
var resizeStartHeatMapWidth = 0;
this.afterVerticalScrollBarDivider.on('resizeStart', function (e) {
resizeStartHeatMapWidth = _this.heatmap.getUnscaledWidth();
}).on('resize', function (e) {
// grow or shrink the heat map
_this.heatmap.prefWidth = resizeStartHeatMapWidth + e.delta;
_this.revalidate();
});
// horizontal scroll
this.hscroll = new phantasus.ScrollBar(false);
this.hscroll.appendTo(this.$parent);
this.hscroll.on('scroll', function () {
if (_this.updatingScroll) {
return;
}
_this.paintAll({
paintRows: false,
paintColumns: true,
invalidateRows: false,
invalidateColumns: true
});
});
this.$whitespace = $('<div style="position: absolute;"></div>');
this.$whitespace.appendTo(this.$parent);
var heatmap = new phantasus.HeatMapElementCanvas(this.project);
if (this.options.drawCallback) {
heatmap.setDrawCallback(this.options.drawCallback);
}
$(heatmap.canvas).on(
'contextmenu',
function (e) {
var items = [];
phantasus.Popup.showPopup(
[
{
name: 'Save Image (' + phantasus.Util.COMMAND_KEY + 'S)'
},
//{
// name: 'Copy Selection',
// disabled: _this.project
// .getElementSelectionModel()
// .count() === 0,
// class: 'copy'
//},
{
separator: true
},
{
name: 'Show Inline Tooltip',
checked: _this.options.inlineTooltip
}],
{
x: e.pageX,
y: e.pageY
},
e.target,
function (event, item) {
if (item === 'Show Inline Tooltip') {
_this.options.inlineTooltip = !_this.options.inlineTooltip;
} else if (item === ('Save Image (' + phantasus.Util.COMMAND_KEY + 'S)')) {
_this.getActionManager().execute('Save Image');
} else if (item === 'Copy Selection') {
var text = _this.getSelectedElementsText();
if (text !== '') {
event.clipboardData.setData(
'text/plain',
text);
}
} else {
//console.log(item + ' unknown.');
}
});
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
});
heatmap.appendTo(this.$parent);
this.heatmap = heatmap;
var rowDendrogramSortKey = null;
if (rowDendrogram != null) {
var tree = rowDendrogram;
if (tree.leafNodes.length !== this.project.getSortedFilteredDataset().getRowCount()) {
throw '# leaf nodes in row dendrogram ' + tree.leafNodes.length
+ ' != ' + this.project.getSortedFilteredDataset().getRowCount();
}
var rowIndices = null;
// when saving a session the dataset is reordered to reflect the clustering
if (this.options.rowDendrogramField != null) {
var vector = dataset.getRowMetadata().getByName(
this.options.rowDendrogramField);
rowIndices = [];
var map = new phantasus.Map();
var re = /[,:]/g;
for (var j = 0, size = vector.size(); j < size; j++) {
var key = vector.getValue(j);
map.set(key.replace(re, ''), j);
}
// need to replace special characters to match ids in newick
// file
for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
var index = map.get(tree.leafNodes[i].name);
if (index === undefined) {
throw 'Unable to find row dendrogram id '
+ tree.leafNodes[i].name
+ ' in row annotations';
}
rowIndices.push(index);
}
} else {
// see if leaf node ids are indices
// for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
// var newickId = tree.leafNodes[i].name;
// newickId = parseInt(newickId);
// if (!isNaN(newickId)) {
// rowIndices.push(newickId);
// } else {
// break;
// }
// }
// if (rowIndices.length !== tree.leafNodes.length) {
// rowIndices = [];
// for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
// rowIndices.push(i);
// }
// }
}
this.rowDendrogram = new phantasus.RowDendrogram(this, tree, heatmap.getRowPositions(), this.project, true);
this.rowDendrogram.appendTo(this.$parent);
this.rowDendrogram.$label.appendTo(this.$parent);
this.rowDendrogram.$squishedLabel.appendTo(this.$parent);
if (rowIndices != null) {
rowDendrogramSortKey = new phantasus.SpecifiedModelSortOrder(
rowIndices, rowIndices.length, 'dendrogram');
rowDendrogramSortKey.setLockOrder(2);
rowDendrogramSortKey.setUnlockable(false);
rowDendrogramSortKey.setPreservesDendrogram(true);
}
}
var columnDendrogramSortKey = null;
if (columnDendrogram != null) {
var tree = columnDendrogram;
if (tree.leafNodes.length !== this.project.getSortedFilteredDataset().getColumnCount()) {
throw '# leaf nodes ' + tree.leafNodes.length + ' != '
+ this.project.getSortedFilteredDataset().getColumnCount();
}
var columnIndices = null;
if (this.options.columnDendrogramField != null) {
columnIndices = [];
var vector = dataset.getColumnMetadata().getByName(
this.options.columnDendrogramField);
var map = new phantasus.Map();
var re = /[,:]/g;
for (var j = 0, size = vector.size(); j < size; j++) {
var key = vector.getValue(j);
map.set(key.replace(re, ''), j);
}
for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
var index = map.get(tree.leafNodes[i].name);
if (index === undefined) {
throw 'Unable to find column dendrogram id '
+ tree.leafNodes[i].name
+ ' in column annotations';
}
columnIndices.push(index);
}
} else {
// for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
// var newickId = tree.leafNodes[i].name;
// newickId = parseInt(newickId);
// if (!isNaN(newickId)) {
// columnIndices.push(newickId);
// } else {
// break;
// }
// }
// if (columnIndices.length !== tree.leafNodes.length) {
// columnIndices = [];
// for (var i = 0, length = tree.leafNodes.length; i < length; i++) {
// columnIndices.push(i);
// }
// }
}
this.columnDendrogram = new phantasus.ColumnDendrogram(this, tree,
heatmap.getColumnPositions(), this.project, true);
this.columnDendrogram.appendTo(this.$parent);
this.columnDendrogram.$label.appendTo(this.$parent);
this.columnDendrogram.$squishedLabel.appendTo(this.$parent);
if (columnIndices != null) {
columnDendrogramSortKey = new phantasus.SpecifiedModelSortOrder(
columnIndices, columnIndices.length, 'dendrogram');
columnDendrogramSortKey.setLockOrder(2);
columnDendrogramSortKey.setUnlockable(false);
columnDendrogramSortKey.setPreservesDendrogram(true);
}
}
if (this.options.drawGrid != null) {
this.heatmap.setDrawGrid(this.options.drawGrid);
}
if (this.options.gridColor != null) {
this.heatmap.setGridColor(this.options.gridColor);
}
if (this.options.gridThickness != null) {
this.heatmap.setGridThickness(this.options.gridThickness);
}
if (this.options.drawValues != null) {
this.heatmap.setDrawValues(this.options.drawValues);
}
if (rowDendrogramSortKey != null) {
this.project.setRowSortKeys([rowDendrogramSortKey]);
}
if (columnDendrogramSortKey != null) {
this.project.setColumnSortKeys([columnDendrogramSortKey]);
}
if (this.options.rowSortBy && this.options.rowSortBy.length > 0) {
this.project.setRowSortKeys(phantasus.SortKey.fromJSON(this.project, this.options.rowSortBy), false);
}
if (this.options.columnSortBy && this.options.columnSortBy.length > 0) {
this.project.setColumnSortKeys(phantasus.SortKey.fromJSON(this.project, this.options.columnSortBy), false);
}
if (this.options.rowGroupBy != null && this.options.rowGroupBy.length > 0) {
var keys = phantasus.SortKey.fromJSON(this.project, this.options.rowGroupBy);
for (var i = 0; i < keys.length; i++) {
this.project.groupRows.push(keys[i]);
}
}
if (this.options.columnGroupBy != null && this.options.columnGroupBy.length > 0) {
var keys = phantasus.SortKey.fromJSON(this.project, this.options.columnGroupBy);
for (var i = 0; i < keys.length; i++) {
this.project.groupColumns.push(keys[i]);
}
}
if (this.options.rowSelection != null && this.options.rowSelection.length > 0) {
var indices = new phantasus.Set();
for (var i = 0, length = this.options.rowSelection.length; i < length; i++) {
indices.add(this.project.convertModelRowIndexToView(this.options.rowSelection[i]));
}
this.project.getRowSelectionModel().setViewIndices(indices, false);
}
if (this.options.columnSelection != null && this.options.columnSelection.length > 0) {
var indices = new phantasus.Set();
for (var i = 0, length = this.options.columnSelection.length; i < length; i++) {
indices.add(this.project.convertModelColumnIndexToView(this.options.columnSelection[i]));
}
this.project.getColumnSelectionModel().setViewIndices(indices, false);
}
// if (this.options.rowSearchTerm != null && this.options.rowSearchTerm !== '') {
// this.toolbar.getSearchField(phantasus.HeatMapToolBar.ROW_SEARCH_FIELD).val(this.options.rowSearchTerm);
// }
// if (this.options.columnSearchTerm != null && this.options.columnSearchTerm !== '') {
// this.toolbar.getSearchField(phantasus.HeatMapToolBar.COLUMN_SEARCH_FIELD).val(this.options.columnSearchTerm);
// }
this.vSortByValuesIndicator = new phantasus.SortByValuesIndicator(
this.project, true, heatmap.getRowPositions());
this.vSortByValuesIndicator.appendTo(this.$parent);
this.hSortByValuesIndicator = new phantasus.SortByValuesIndicator(
this.project, false, heatmap.getColumnPositions());
this.hSortByValuesIndicator.appendTo(this.$parent);
this.verticalSearchBar = new phantasus.ScentedSearch(this.project.getRowSelectionModel(), heatmap.getRowPositions(), true,
this.vscroll, this);
this.horizontalSearchBar = new phantasus.ScentedSearch(this.project.getColumnSelectionModel(), heatmap.getColumnPositions(),
false, this.hscroll, this);
this.rowTracks = [];
this.rowTrackHeaders = [];
this.columnTracks = [];
this.columnTrackHeaders = [];
if (this.options.rowSize != null && this.options.rowSize !== 'fit') {
this.heatmap.getRowPositions().setSize(this.options.rowSize);
}
if (this.options.columnSize != null && this.options.columnSize !== 'fit') {
this.heatmap.getColumnPositions().setSize(
this.options.columnSize);
}
var setInitialDisplay = function (isColumns, options) {
var nameToOption = new phantasus.Map();
// at
// least
// one
// display option
// supplied
var displaySpecified = (_this.options.parent != null && _this.options.inheritFromParent);
if (options != null && options.length > 0) {
displaySpecified = true;
for (var i = 0; i < options.length; i++) {
nameToOption.set(options[i].renameTo != null ? options[i].renameTo
: options[i].field, options[i]);
}
}
var displayMetadata = isColumns ? dataset.getColumnMetadata()
: dataset.getRowMetadata();
// see if default fields found
if (!displaySpecified) {
var defaultFieldsToShow = new phantasus.Set();
['pert_iname', 'moa', 'target', 'cell_id', 'pert_type'].forEach(function (field) {
defaultFieldsToShow.add(field);
});
for (var i = 0, metadataCount = displayMetadata.getMetadataCount(); i < metadataCount; i++) {
var v = displayMetadata.get(i);
if (defaultFieldsToShow.has(v.getName()) && !nameToOption.has(v.getName())) {
nameToOption.set(v.getName(), {
display: ['text']
});
displaySpecified = true;
}
}
}
var isFirst = true;
// console.log("heat_map ::", displayMetadata, displaySpecified);
for (var i = 0, metadataCount = displayMetadata.getMetadataCount(); i < metadataCount; i++) {
var v = displayMetadata.get(i);
var name = v.getName();
var option = nameToOption.get(name);
if (displaySpecified && option == null) {
continue;
}
if (phantasus.MetadataUtil.DEFAULT_HIDDEN_FIELDS.has(name)
&& option == null) {
continue;
}
var count = isColumns ? dataset.getColumnCount() : dataset.getRowCount();
if (option == null && !displaySpecified && count > 1
&& !phantasus.VectorUtil.containsMoreThanOneValue(v)) {
continue;
}
if (option == null) {
option = {};
}
if (option.title) {
v.getProperties().set(phantasus.VectorKeys.TITLE,
option.title);
}
if (option.display == null) {
if (name === 'pert_iname' || name === 'id' || isFirst) {
option.inlineTooltip = true;
option.display = ['text'];
} else {
option.display = isColumns ? 'color,highlight' : 'text';
}
}
isFirst = false;
var track = _this.addTrack(name, isColumns, option);
if (option.size) {
if (!isColumns && option.size.width != null) {
var header = _this.getTrackHeaderByIndex(_this.getTrackIndex(name, isColumns), isColumns);
track.setPrefWidth(option.size.width); // can only set width
header.setPrefWidth(option.size.width);
} else if (isColumns && (option.size.width != null || option.size.height != null)) {
var header = _this.getTrackHeaderByIndex(_this.getTrackIndex(name, isColumns), isColumns);
if (option.size.height) {
track.setPrefHeight(option.size.height);
header.setPrefHeight(option.size.height);
}
if (option.size.width) {
// TODO set width for all tracks since they all have same width
track.setPrefWidth(option.size.width);
header.setPrefWidth(option.size.width);
}
}
}
if (option.header && option.header.font) {
var header = _this.getTrackHeaderByIndex(_this.getTrackIndex(name, isColumns), isColumns);
header.font = option.header.font;
}
if (option.formatter) {
v.getProperties().set(phantasus.VectorKeys.FORMATTER, phantasus.Util.createNumberFormat(option.formatter));
}
if (option.formatter) {
v.getProperties().set(phantasus.VectorKeys.FORMATTER, phantasus.Util.createNumberFormat(option.formatter));
}
if (track.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)
&& option.color) {
var m = isColumns ? _this.project.getColumnColorModel()
: _this.project.getRowColorModel();
if (track.getFullVector().getProperties.get(phantasus.VectorKeys.DISCRETE)) {
_.each(options.color, function (p) {
m.setMappedValue(v, p.value, p.color);
});
} else {
var cs = m.createContinuousColorMap(v);
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
_.each(options.color, function (p) {
min = Math.min(min, p.value);
max = Math.max(max, p.value);
});
cs.setMin(min);
cs.setMax(max);
var valueToFraction = d3.scale.linear().domain(
[cs.getMin(), cs.getMax()]).range(
[0, 1]).clamp(true);
var fractions = [];
var colors = [];
_.each(options.color, function (p) {
fractions.push(valueToFraction(p.value));
colors.push(p.color);
});
cs.setFractions({
fractions: fractions,
colors: colors
});
}
if (track.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)
&& option.shape) {
var m = isColumns ? _this.project.getColumnShapeModel()
: _this.project.getRowShapeModel();
_.each(options.shape, function (p) {
m.setMappedValue(v, p.value, p.shape);
});
}
}
}
};
setInitialDisplay(false, this.options.rows);
setInitialDisplay(true, this.options.columns);
function reorderTracks(array, isColumns) {
if (array == null || array.length <= 1) {
return;
}
var nameOrderPairs = [];
var found = false;
array.forEach(function (item, index) {
var name = item.renameTo || item.field;
var order = index;
if (item.order != null) {
order = item.order;
found = true;
}
nameOrderPairs.push({
name: name,
order: order
});
});
if (!found) {
array.forEach(function (item, index) {
var name = item.renameTo || item.field;
nameOrderPairs.push({
name: name,
order: index
});
});
}
nameOrderPairs.sort(function (a, b) {
return (a.order === b.order ? 0 : (a.order < b.order ? -1
: 1));
});
for (var i = 0, counter = 0; i < nameOrderPairs.length; i++) {
var index = _this.getTrackIndex(nameOrderPairs[i].name,
isColumns);
if (index !== -1) {
_this.moveTrack(index, counter, isColumns);
counter++;
}
}
}
reorderTracks(this.options.rows, false);
reorderTracks(this.options.columns, true);
if (this.options.showRowNumber) {
this.setShowRowNumber(true);
}
var colorSchemeSpecified = this.options.colorScheme != null;
if (this.options.colorScheme == null) {
var ext = '';
if (this.options.dataSource) {
ext = phantasus.Util.getExtension(phantasus.Util.getFileName(this.options.dataSource));
}
var colorScheme = this.autoDisplay({
filename: phantasus.Util.getBaseFileName(phantasus.Util.getFileName(this.options.dataset)),
extension: ext
});
if (colorScheme == null) {
colorScheme = {
type: 'relative'
};
}
this.options.colorScheme = colorScheme;
var name = this.project.getFullDataset().getName();
if (ext === 'maf' && !this.options.rowSortBy) {
var sortKeys = [];
if (this.project.getFullDataset().getRowMetadata().getByName(
'order')) {
sortKeys.push(new phantasus.SortKey('order',
phantasus.SortKey.SortOrder.ASCENDING));
}
sortKeys.push(new phantasus.SortKey('id',
phantasus.SortKey.SortOrder.ASCENDING));
this.project.setRowSortKeys(sortKeys, false);
}
if (phantasus.DatasetUtil.getSeriesIndex(this.project.getFullDataset(), 'allelic_fraction') !== -1) {
this.options.sizeBy = {
seriesName: 'allelic_fraction',
min: 0,
max: 1
};
}
}
if (this.options.parent && this.options.inheritFromParent) {
this.heatmap.setPropertiesFromParent(this.options.parent.heatmap);
}
if (this.options.parent && this.options.inheritFromParent
&& !colorSchemeSpecified) {
heatmap.setColorScheme(this.options.parent.heatmap.getColorScheme().copy(this.project));
} else {
heatmap.setColorScheme(new phantasus.HeatMapColorScheme(
this.project, this.options.colorScheme));
if (this.options.dataset.getRowMetadata().getByName('Source') != null) {
// separate color scheme for each source file
var sourcesSet = phantasus.VectorUtil.getSet(this.options.dataset.getRowMetadata().getByName('Source'));
this.heatmap.getColorScheme().setSeparateColorSchemeForRowMetadataField('Source');
sourcesSet.forEach(function (source) {
_this.autoDisplay({
extension: phantasus.Util.getExtension(source),
filename: '' + source
});
});
}
}
if (this.options.sizeBy) {
heatmap.getColorScheme().getSizer().setSeriesName(
this.options.sizeBy.seriesName);
heatmap.getColorScheme().getSizer().setMin(
this.options.sizeBy.min);
heatmap.getColorScheme().getSizer().setMax(
this.options.sizeBy.max);
}
this.updateDataset();
// tabOpened is inherited by child heat maps
if (this.options.tabOpened) {
this.options.tabOpened(this);
this.updateDataset();
}
// renderReady is only called once for the parent heat map
if (this.options.renderReady) {
this.options.renderReady(this);
this.updateDataset();
}
if (this.options.rowSize === 'fit') {
this.heatmap.getRowPositions().setSize(this.getFitRowSize());
this.revalidate({
paint: false
});
}
if (this.options.columnSize === 'fit') {
this.heatmap.getColumnPositions().setSize(
this.getFitColumnSize());
this.revalidate({
paint: false
});
}
if (this.options.rowColorModel) {
this.getProject().getRowColorModel().fromJSON(this.options.rowColorModel);
}
if (this.options.columnColorModel) {
this.getProject().getColumnColorModel().fromJSON(this.options.columnColorModel);
}
if (this.options.rowShapeModel) {
this.getProject().getRowShapeModel().fromJSON(this.options.rowShapeModel);
}
if (this.options.columnShapeModel) {
this.getProject().getColumnShapeModel().fromJSON(this.options.columnShapeModel);
}
if (this.options.rowFontModel) {
this.getProject().getRowFontModel().fromJSON(this.options.rowFontModel);
}
if (this.options.columnFontModel) {
this.getProject().getColumnFontModel().fromJSON(this.options.columnFontModel);
}
if (this.options.rowSize === 'fit' || this.options.columnSize === 'fit') {
// note that we have to revalidate twice because column sizes are
// dependent on row sizes and vice versa
if (this.options.columnSize === 'fit') {
this.heatmap.getColumnPositions().setSize(
this.getFitColumnSize());
this.revalidate({
paint: false
});
}
if (this.options.rowSize === 'fit') {
this.heatmap.getRowPositions().setSize(this.getFitRowSize());
this.revalidate({
paint: false
});
}
this.paintAll({
paintRows: true,
paintColumns: true,
invalidateRows: true,
invalidateColumns: true
});
}
this.options.parent = null; // avoid memory leak
this.$tipFollow = $('<div style="display:none;"' +
' class="phantasus-tip-inline"></div>');
this.$tipFollow.appendTo(this.$parent);
this.$tipInfoWindow = $('<div class="phantasus-tip-dialog"></div>');
this.$tipInfoWindow.appendTo(this.$parent);
if (!phantasus.Util.isHeadless()) {
this.$tipInfoWindow.dialog({
close: function (event, ui) {
if (!_this._togglingInfoWindow) {
_this.toggleInfoWindow();
}
},
autoOpen: false,
width: 220,
height: 280,
minHeight: 38,
minWidth: 10,
collision: 'fit',
position: {
my: 'right-30 bottom',
at: 'right top',
of: this.$parent
},
title: 'Info'
});
this.setTooltipMode(this.options.tooltipMode);
}
this.getProject().on(
'rowFilterChanged columnFilterChanged rowGroupByChanged columnGroupByChanged rowSortOrderChanged columnSortOrderChanged datasetChanged',
function (e) {
if (e.type === 'datasetChanged') { // remove
// tracks
// that are no
// longer in the
// dataset
var dataset = _this.getProject().getFullDataset();
for (var i = 0; i < _this.rowTracks.length; i++) {
var track = _this.rowTracks[i];
if (!dataset.getRowMetadata().getByName(
track.getName())) {
_this.removeTrack(track.getName(),
false);
i--;
}
}
for (var i = 0; i < _this.columnTracks.length; i++) {
var track = _this.columnTracks[i];
if (!dataset.getColumnMetadata().getByName(
track.getName())) {
_this.removeTrack(track.getName(),
true);
i--;
}
}
}
_this.updateDataset();
_this.revalidate();
});
this.getProject().on('trackChanged', function (e) {
//console.log('Track changed');
if (!_this.options.dummy) {
phantasus.DatasetUtil.toESSessionPromise(dataset);
}
});
this.getProject().on('trackChanged', function (e) {
var columns = e.columns;
_.each(e.vectors, function (v, i) {
var index = _this.getTrackIndex(v.getName(), columns);
if (index === -1) {
_this.addTrack(v.getName(), columns, e.display[i]);
} else {
// repaint
var track = _this.getTrackByIndex(index, columns);
var display = e.display[i];
if (display) {
track.settingFromConfig(display);
}
track.setInvalid(true);
}
});
_this.revalidate();
});
this.getProject().on('rowTrackRemoved', function (e) {
_this.removeTrack(e.vector.getName(), false);
_this.revalidate();
});
this.getProject().on('columnTrackRemoved', function (e) {
_this.removeTrack(e.vector.getName(), true);
_this.revalidate();
});
this.getProject().getRowSelectionModel().on(
'selectionChanged',
function () {
// repaint tracks that indicate selection
for (var i = 0; i < _this.columnTracks.length; i++) {
var track = _this.columnTracks[i];
if (track.getFullVector().getProperties().get(phantasus.VectorKeys.RECOMPUTE_FUNCTION_SELECTION)) {
var selectedDataset = _this.getProject().getSelectedDataset({
selectedRows: true,
selectedColumns: false,
emptyToAll: false
});
var vector = selectedDataset.getColumnMetadata().getByName(track.getName());
var f = phantasus.VectorUtil.jsonToFunction(vector, phantasus.VectorKeys.FUNCTION);
if (typeof f === 'function') {
// iterate over each column
var view = new phantasus.DatasetColumnView(selectedDataset);
// TODO only set values that are currently visible
for (var j = 0, size = vector.size(); j < size; j++) {
view.setIndex(j);
vector.setValue(j, f(view, selectedDataset, j));
}
track.setInvalid(true);
track.repaint();
}
}
}
_this.verticalSearchBar.update();
_this.heatmap.updateRowSelectionCache();
_this.paintAll({
paintRows: true,
paintColumns: false,
invalidateRows: false,
invalidateColumns: false
});
});
this.getProject().getColumnSelectionModel().on('selectionChanged',
function () {
_this.horizontalSearchBar.update();
_this.heatmap.updateColumnSelectionCache();
_this.paintAll({
paintRows: false,
paintColumns: true,
invalidateRows: false,
invalidateColumns: false
});
});
this.pasteListener = function (e) {
if (_this.isActiveComponent()) {
var text = e.originalEvent.clipboardData.getData('text/plain');
if (text != null && text.length > 0) {
e.preventDefault();
e.stopPropagation();
// open a file from clipboard
var url;
if (text.indexOf('http') === 0) {
url = text;
} else {
var blob = new Blob([text], {type: 'text/plain'});
url = URL.createObjectURL(blob);
}
phantasus.HeatMap.showTool(
new phantasus.OpenFileTool({
file: url
}), _this);
}
}
};
this.beforeCopyListener = function (e) {
if (_this.isActiveComponent()) {
e.preventDefault();
}
};
this.copyListener = function (ev) {
if (_this.isActiveComponent()) {
var activeComponent = _this.getActiveComponent();
var project = _this.project;
if (activeComponent === 'heatMap' || ev.shiftKey) {
// copy selected text or image
// var text = _this.getSelectedElementsText();
// if (text !== '') {
// ev.originalEvent.clipboardData.setData(
// 'text/plain', text);
// return;
// }
var bounds = _this.getTotalSize();
var height = bounds.height;
var width = bounds.width;
var canvas = $('<canvas></canvas>')[0];
canvas.height = height;
canvas.width = width;
var context = canvas.getContext('2d');
_this.snapshot(context);
var url = canvas.toDataURL();
ev.originalEvent.clipboardData.setData(
'text/html',
'<img src="' + url + '">');
ev.preventDefault();
ev.stopImmediatePropagation();
return;
}
// copy all selected rows and columns
var dataset = project.getSelectedDataset({
emptyToAll: false
});
var rowsSelected = dataset.getRowCount() > 0;
var columnsSelected = dataset.getColumnCount() > 0;
var columnMetadata = dataset.getColumnMetadata();
var rowMetadata = dataset.getRowMetadata();
// only copy visible tracks
var visibleColumnFields = _this.getVisibleTrackNames(true);
var columnFieldIndices = [];
_.each(visibleColumnFields, function (name) {
var index = phantasus.MetadataUtil.indexOf(
columnMetadata, name);
if (index !== -1) {
columnFieldIndices.push(index);
}
});
columnMetadata = new phantasus.MetadataModelColumnView(
columnMetadata, columnFieldIndices);
var rowMetadata = dataset.getRowMetadata();
// only copy visible tracks
var visibleRowFields = _this.getVisibleTrackNames(false);
var rowFieldIndices = [];
_.each(visibleRowFields, function (name) {
var index = phantasus.MetadataUtil.indexOf(
rowMetadata, name);
if (index !== -1) {
rowFieldIndices.push(index);
}
});
rowMetadata = new phantasus.MetadataModelColumnView(
rowMetadata, rowFieldIndices);
var text = [];
if (rowsSelected && columnsSelected) { // copy
// as
// gct
// 1.3
text = new phantasus.GctWriter().write(dataset);
} else {
var text = [];
var model = rowsSelected ? rowMetadata
: columnMetadata;
for (var i = 0, count = model.getItemCount(); i < count; i++) {
for (var j = 0, nfields = model.getMetadataCount(); j < nfields; j++) {
var v = model.get(j);
if (j > 0) {
text.push('\t');
}
text.push(phantasus.Util.toString(v.getValue(i)));
}
text.push('\n');
}
text = text.join('');
}
ev.originalEvent.clipboardData.setData(
'text/plain', text);
ev.preventDefault();
ev.stopImmediatePropagation();
}
};
if (typeof window !== 'undefined') {
$(window)
.on('paste.phantasus', this.pasteListener)
.on('beforecopy.phantasus', this.beforeCopyListener)
.on('copy.phantasus', this.copyListener);
}
if (this.options.keyboard && !phantasus.Util.isHeadless()) {
new phantasus.HeatMapKeyListener(this);
}
if (this.options.symmetric) {
this.getProject().setSymmetric(this);
}
var dragStartScrollTop;
var dragStartScrollLeft;
var panstartMousePosition;
this.hammer = phantasus.Util.hammer(_this.heatmap.canvas, ['pan', 'pinch', 'tap', 'swipe']).on('swipe', this.swipe = function (event) {
event.preventDefault();
}).on('panend', this.panend = function (event) {
_this.panning = false;
if (panstartMousePosition) {
panstartMousePosition = null;
_this.heatmap.setSelectionBox(null);
_this.heatmap.repaint();
}
event.preventDefault();
}).on('panmove', this.panmove = function (event) {
if (panstartMousePosition) {
var pos = phantasus.CanvasUtil.getMousePosWithScroll(event.target, event,
_this.scrollLeft(), _this.scrollTop());
var rowIndex = _this.heatmap.getRowPositions().getIndex(pos.y, false);
var columnIndex = _this.heatmap.getColumnPositions().getIndex(pos.x, false);
_this.updatingScroll = false;
_this.heatmap.setSelectionBox({
y: [panstartMousePosition.rowIndex, rowIndex],
x: [panstartMousePosition.columnIndex, columnIndex]
});
var rowIndices = new phantasus.Set();
for (var i = Math.min(panstartMousePosition.rowIndex, rowIndex),
end = Math.max(panstartMousePosition.rowIndex, rowIndex); i <= end; i++) {
rowIndices.add(i);
}
var columnIndices = new phantasus.Set();
for (var i = Math.min(panstartMousePosition.columnIndex, columnIndex),
end = Math.max(panstartMousePosition.columnIndex, columnIndex); i <= end; i++) {
columnIndices.add(i);
}
_this.project.getRowSelectionModel().setViewIndices(rowIndices, true);
_this.project.getColumnSelectionModel().setViewIndices(columnIndices, true);
// _this.heatmap.repaint(); don't need to repaint as setViewIndices triggers repaint
} else {
_this.updatingScroll = true; // avoid infinite paints
var rows = false;
var columns = false;
if (event.deltaY !== 0) {
var pos = dragStartScrollTop + event.deltaY;
_this.scrollTop(pos);
rows = true;
}
if (event.deltaX !== 0) {
var pos = dragStartScrollLeft + event.deltaX;
_this.scrollLeft(pos);
columns = true;
}
_this.updatingScroll = false;
if (rows || columns) {
_this.paintAll({
paintRows: rows,
paintColumns: rows,
invalidateRows: rows,
invalidateColumns: columns
});
}
}
event.preventDefault();
}).on('panstart', this.panstart = function (event) {
_this.panning = true; // don't draw inline tooltips when panning
_this.project.setHoverRowIndex(-1);
_this.project.setHoverColumnIndex(-1);
if (event.srcEvent.shiftKey) {
var pos = phantasus.CanvasUtil.getMousePosWithScroll(event.target, event,
_this.scrollLeft(), _this.scrollTop());
panstartMousePosition = {
rowIndex: _this.heatmap.getRowPositions().getIndex(pos.y, false),
columnIndex: _this.heatmap.getColumnPositions().getIndex(pos.x, false)
};
} else {
panstartMousePosition = null;
dragStartScrollTop = _this.scrollTop();
dragStartScrollLeft = _this.scrollLeft();
}
event.preventDefault();
}).on(
'tap',
this.tap = function (event) {
// var commandKey = phantasus.Util.IS_MAC ? event.srcEvent.metaKey
// : event.srcEvent.ctrlKey;
if (phantasus.Util.IS_MAC && event.srcEvent.ctrlKey) { // right-click
// on
// Mac
return;
}
_this.project.getRowSelectionModel().setViewIndices(new phantasus.Set(), true);
_this.project.getColumnSelectionModel().setViewIndices(new phantasus.Set(), true);
// var position = phantasus.CanvasUtil
// .getMousePosWithScroll(event.target, event,
// _this.scrollLeft(), _this
// .scrollTop());
// var rowIndex = _this.heatmap.getRowPositions()
// .getIndex(position.y, false);
// var columnIndex = _this.heatmap
// .getColumnPositions().getIndex(position.x,
// false);
// _this.project.getElementSelectionModel().click(
// rowIndex, columnIndex,
// event.srcEvent.shiftKey || commandKey);
}).on(
'pinch',
this.pinch = function (event) {
var scale = event.scale;
_this.heatmap.getRowPositions().setSize(13 * scale);
_this.heatmap.getColumnPositions().setSize(
13 * scale);
var reval = {};
if (_this.project.getHoverRowIndex() !== -1) {
reval.scrollTop = this.heatmap.getRowPositions().getPosition(
this.project.getHoverRowIndex());
}
if (_this.project.getHoverColumnIndex() !== -1) {
reval.scrollLeft = this.heatmap.getColumnPositions().getPosition(
this.project.getHoverColumnIndex());
}
_this.revalidate(reval);
event.preventDefault();
});
var heatMapMouseMoved = function (event) {
var mouseI, mouseJ;
if (event.type === 'mouseout') {
mouseI = -1;
mouseJ = -1;
} else {
var position = phantasus.CanvasUtil.getMousePosWithScroll(
event.target, event, _this.scrollLeft(), _this.scrollTop());
mouseI = _this.heatmap.getRowPositions().getIndex(position.y,
false);
mouseJ = _this.heatmap.getColumnPositions().getIndex(
position.x, false);
}
_this.setMousePosition(mouseI, mouseJ, {
event: event
});
};
$(_this.heatmap.canvas).on('mouseout', heatMapMouseMoved).on(
'mousemove', heatMapMouseMoved);
// tools to run at load time
_.each(this.options.tools, function (item) {
var action = _this.getActionManager().getAction(item.name);
if (action == null) {
// console.log(item.name + ' not found.');
} else {
var actionGui = action.gui();
var gui = actionGui.gui(_this.getProject());
var formBuilder = new phantasus.FormBuilder();
_.each(gui, function (item) {
formBuilder.append(item);
});
var input = {};
_.each(gui, function (item) {
input[item.name] = formBuilder.getValue(item.name);
});
if (item.params) {
// overide default values
for (var key in item.params) {
input[key] = item.params[key];
}
}
actionGui.execute({
heatMap: _this,
project: _this.getProject(),
input: input
});
}
});
},
setMousePosition: function (i, j, options) {
this.mousePositionOptions = options;
var updateColumns = this.project.getHoverColumnIndex() !== j;
var updateRows = this.project.getHoverRowIndex() !== i;
if (updateColumns || updateRows) {
if (!this.panning) {
this.project.setHoverRowIndex(i);
this.project.setHoverColumnIndex(j);
}
this.setToolTip(i, j, options);
this.paintAll({
paintRows: updateRows,
paintColumns: updateColumns,
invalidateRows: false,
invalidateColumns: false
});
} else {
this._updateTipFollowPosition(options);
}
// else if (this.options.tooltipMode === 2 &&
// (this.project.getHoverColumnIndex() !== -1 || this.project
// .getHoverRowIndex() !== -1)) {
//
// }
this.trigger('change', {
name: 'setMousePosition',
source: this,
arguments: arguments
});
},
getContentEl: function () {
return this.$content;
},
focus: function () {
var scrollTop = document.body.scrollTop;
this.$tabPanel.focus();
document.body.scrollTop = scrollTop;
},
getFocusEl: function () {
return this.$tabPanel;
},
/**
Set where the tooltip is shown
@param mode 0 is formula bar, 1 is dialog, -1 is no tooltip
*/
setTooltipMode: function (mode) {
this._togglingInfoWindow = true;
this.options.tooltipMode = mode;
this.$tipInfoWindow.html('');
this.toolbar.$tip.html('');
this.$tipFollow.html('').css({
display: 'none'
});
this.toolbar.$tip.css('display', mode === 0 ? '' : 'none');
this.setToolTip(-1, -1);
if (this.options.tooltipMode === 1) {
this.$tipInfoWindow.dialog('open');
} else {
this.$tipInfoWindow.dialog('close');
}
this._togglingInfoWindow = false;
},
toggleInfoWindow: function () {
this.setTooltipMode(this.options.tooltipMode == 1 ? 0 : 1);
},
_setTipText: function (tipText, tipFollowText, options) {
if (this.options.tooltipMode === 0) {
this.toolbar.$tip.html(tipText.join(''));
} else if (this.options.tooltipMode === 1) {
this.$tipInfoWindow.html(tipText.join(''));
}
if (tipFollowText != null) {
this.tipFollowHidden = false;
this.$tipFollow.html(tipFollowText);
this._updateTipFollowPosition(options);
} else {
this.tipFollowHidden = true;
this.$tipFollow.empty().css({
display: 'none'
});
}
this.trigger('change', {
name: 'setToolTip',
source: this,
arguments: arguments
});
},
setToolTip: function (rowIndex, columnIndex, options) {
options = options || {};
if (this.options.showSeriesNameInTooltip) {
options.showSeriesNameInTooltip = true;
}
if (this.options.tooltipSeriesIndices) {
options.tooltipSeriesIndices = this.options.tooltipSeriesIndices;
}
if (options.heatMapLens) {
var maxSelectedCount = 20;
// don't draw lens if currently visible
// row lens
var $wrapper = $('<div></div>');
var wrapperHeight = 0;
var wrapperWidth = 0;
var found = false;
var inline = [];
var indicesForLens = [];
// only draw heat map lens if less than maxSelectedCount indices selected
if (rowIndex != null && rowIndex.length > 0) {
for (var hoverIndex = 0; hoverIndex < rowIndex.length; hoverIndex++) {
var row = rowIndex[hoverIndex];
if (row >= 0 && (row >= this.heatmap.lastPosition.bottom || row < this.heatmap.lastPosition.top)) {
indicesForLens.push(row);
} else {
inline.push(row);
}
}
if (indicesForLens.length < maxSelectedCount) {
for (var hoverIndex = 0; hoverIndex < indicesForLens.length; hoverIndex++) {
var row = indicesForLens[hoverIndex];
var heatMapWidth = this.heatmap.getUnscaledWidth();
var top = row; // Math.max(0, rowIndex - 1);
var bottom = row + 1; //Math.min(rowIndex + 1, this.heatmap.rowPositions.getLength());
var startPix = this.heatmap.rowPositions.getPosition(top);
var endPix = startPix + this.heatmap.rowPositions.getItemSize(top);
var heatMapHeight = endPix - startPix;
var canvas = phantasus.CanvasUtil.createCanvas();
var trackWidth = 0;
for (var i = 0, ntracks = this.rowTracks.length; i < ntracks; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
trackWidth += track.getUnscaledWidth();
}
}
var canvasWidth = trackWidth + heatMapWidth + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
canvas.width = canvasWidth * phantasus.CanvasUtil.BACKING_SCALE;
canvas.style.width = canvasWidth + 'px';
canvas.height = heatMapHeight * phantasus.CanvasUtil.BACKING_SCALE;
canvas.style.height = heatMapHeight + 'px';
var context = canvas.getContext('2d');
phantasus.CanvasUtil.resetTransform(context);
context.save();
context.translate(-this.heatmap.lastClip.x, -startPix);
context.rect(this.heatmap.lastClip.x, startPix, this.heatmap.lastClip.width, this.heatmap.lastClip.height);
context.clip();
this.heatmap._draw({
left: this.heatmap.lastPosition.left,
right: this.heatmap.lastPosition.right,
top: top,
bottom: bottom,
context: context
});
context.restore();
context.translate(heatMapWidth + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS, -startPix);
trackWidth = 0;
for (var i = 0, ntracks = this.rowTracks.length; i < ntracks; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
context.save();
context.translate(trackWidth, 0);
context.rect(0, startPix, track.getUnscaledWidth(), track.lastClip.height);
context.clip();
track._draw({
start: top,
end: bottom,
vector: track.getVector(),
context: context,
availableSpace: track.getUnscaledWidth()
});
context.restore();
trackWidth += track.getUnscaledWidth();
}
}
$(canvas).appendTo($wrapper);
canvas.style.top = wrapperHeight + 'px';
wrapperHeight += parseFloat(canvas.style.height);
wrapperWidth = parseFloat(canvas.style.width);
}
}
if (indicesForLens.length > 0) {
if (indicesForLens.length < maxSelectedCount) {
$wrapper.css({
height: wrapperHeight,
width: wrapperWidth
});
var rect = this.$parent[0].getBoundingClientRect();
this.$tipFollow.html($wrapper).css({
display: '',
left: Math.round(parseFloat(this.heatmap.canvas.style.left) - 1) + 'px',
top: (options.event.clientY - rect.top - wrapperHeight / 2) + 'px'
});
} else {
this.$tipFollow.html('');
}
return;
} else {
var tipText = [];
var tipFollowText = [];
if (inline.length < maxSelectedCount) {
for (var hoverIndex = 0; hoverIndex < inline.length; hoverIndex++) {
this.tooltipProvider(this, inline[hoverIndex], -1,
options, this.options.tooltipMode === 0 ? ' '
: '<br />', false, tipText);
if (this.options.inlineTooltip) {
this.tooltipProvider(this, inline[hoverIndex], -1,
options, '<br />', true, tipFollowText);
}
}
}
var text = tipFollowText.join('');
this._setTipText(tipText, text.length === 0 ? null : '<span style="max-width:400px;">' + text + '</span>', options);
}
}
if (columnIndex != null && columnIndex.length > 0) {
for (var hoverIndex = 0; hoverIndex < columnIndex.length; hoverIndex++) {
var column = columnIndex[hoverIndex];
if (column >= 0 && (column >= this.heatmap.lastPosition.right || column < this.heatmap.lastPosition.left)) {
indicesForLens.push(column);
} else {
inline.push(column);
}
}
if (indicesForLens.length < maxSelectedCount) {
for (var hoverIndex = 0; hoverIndex < indicesForLens.length; hoverIndex++) {
var column = indicesForLens[hoverIndex];
var heatMapHeight = this.heatmap.getUnscaledHeight();
var left = column; // Math.max(0, rowIndex - 1);
var right = column + 1; //Math.min(rowIndex + 1, this.heatmap.rowPositions.getLength());
var startPix = this.heatmap.columnPositions.getPosition(left);
var endPix = startPix + this.heatmap.columnPositions.getItemSize(left);
var heatMapWidth = endPix - startPix;
var canvas = phantasus.CanvasUtil.createCanvas();
var trackHeight = 0;
for (var i = 0, ntracks = this.columnTracks.length; i < ntracks; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) {
trackHeight += track.getUnscaledHeight();
}
}
var canvasHeight = trackHeight + heatMapHeight + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
canvas.width = heatMapWidth * phantasus.CanvasUtil.BACKING_SCALE;
canvas.style.width = heatMapWidth + 'px';
canvas.height = canvasHeight * phantasus.CanvasUtil.BACKING_SCALE;
canvas.style.height = canvasHeight + 'px';
var context = canvas.getContext('2d');
phantasus.CanvasUtil.resetTransform(context);
context.translate(-startPix, 0);
context.save();
context.rect(startPix, trackHeight + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS,
this.heatmap.lastClip.width, this.heatmap.lastClip.height + trackHeight +
phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS);
context.clip();
context.translate(0, trackHeight + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS - this.heatmap.lastClip.y);
this.heatmap._draw({
top: this.heatmap.lastPosition.top,
bottom: this.heatmap.lastPosition.bottom,
left: left,
right: right,
context: context
});
context.restore();
trackHeight = 0;
for (var i = 0, ntracks = this.columnTracks.length; i < ntracks; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) {
context.save();
context.translate(0, trackHeight);
context.rect(startPix, 0, track.lastClip.width, track.getUnscaledHeight());
context.clip();
track._draw({
start: left,
end: right,
vector: track.getVector(),
context: context,
availableSpace: track.getUnscaledHeight(),
clip: {
x: track.lastClip.x,
y: track.lastClip.y
}
});
context.restore();
trackHeight += track.getUnscaledHeight();
}
}
canvas.style.left = wrapperWidth + 'px';
wrapperWidth += parseFloat(canvas.style.width);
wrapperHeight = parseFloat(canvas.style.height);
$(canvas).appendTo($wrapper);
}
}
if (indicesForLens.length > 0) {
if (indicesForLens.length < maxSelectedCount) {
$wrapper.css({
height: wrapperHeight,
width: wrapperWidth
});
var rect = this.$parent[0].getBoundingClientRect();
this.$tipFollow.html($wrapper).css({
top: parseFloat(this.heatmap.canvas.style.top) - trackHeight - phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS - 1,
left: (options.event.clientX - rect.left) - (wrapperWidth / 2),
display: ''
});
} else {
this.$tipFollow.html('');
}
return;
} else {
var tipText = [];
var tipFollowText = [];
if (inline.length < maxSelectedCount) {
for (var hoverIndex = 0; hoverIndex < inline.length; hoverIndex++) {
this.tooltipProvider(this, -1, inline[hoverIndex],
options, this.options.tooltipMode === 0 ? ' '
: '<br />', false, tipText);
if (this.options.inlineTooltip) {
this.tooltipProvider(this, -1, inline[hoverIndex],
options, '<br />', true, tipFollowText);
}
}
}
var text = tipFollowText.join('');
this._setTipText(tipText, text === '' ? null : '<span style="max-width:400px;">' + text + '</span>', options);
}
}
// column lens
}
// tooltipMode=0 top, 1=window, 2=inline
var tipText = [];
this.tooltipProvider(this, rowIndex, columnIndex,
options, this.options.tooltipMode === 0 ? ' '
: '<br />', false, tipText);
var text = [];
var customToolTip = false;
var $tipFollowText;
if (!this.panning) {
var tipFollowText = [];
if (this.options.inlineTooltip) {
this.tooltipProvider(this, rowIndex, columnIndex,
options, '<br />', true, tipFollowText);
if (this.options.tooltip && rowIndex !== -1 && columnIndex !== -1) {
tipFollowText.push('<div data-name="tip"></div>');
}
}
text = tipFollowText.join('');
$tipFollowText = $('<span style="max-width:400px;">' + text + '</span>');
// tooltip callback
if (this.options.tooltip && rowIndex !== -1 && columnIndex !== -1) {
this.options.tooltip(this, rowIndex, columnIndex, $tipFollowText.find('[data-name=tip]'));
customToolTip = true;
}
}
this._setTipText(tipText, text.length > 0 || customToolTip ? $tipFollowText : null, options);
}
,
_updateTipFollowPosition: function (options) {
if (this.tipFollowHidden) {
return;
}
// top, bottom are negative when scrolled
var parentRect = this.$parent[0].getBoundingClientRect();
var tipRect = this.$tipFollow[0].getBoundingClientRect();
var tipWidth = tipRect.width;
var tipHeight = tipRect.height;
var offset = 10;
var left = options.event.clientX - parentRect.left + offset;
var top = options.event.clientY - parentRect.top + offset;
// default is bottom-right
var scrollBarSize = 18;
if ((left + tipWidth) >= ( parentRect.right - parentRect.left - scrollBarSize)) { // offscreen
// right, place tip on
// left
left = options.event.clientX - parentRect.left - offset - tipWidth;
}
if ((top + tipHeight) >= (parentRect.bottom - parentRect.top - scrollBarSize)) { // offscreen
// bottom,
// place tip
// on top
top = options.event.clientY - parentRect.top - offset - tipHeight;
}
this.$tipFollow.css({
left: left + 'px',
top: top + 'px',
display: ''
});
}
,
setTrackVisibility: function (tracks) {
var _this = this;
_.each(tracks, function (track) {
var existingTrack = _this.getTrack(track.name, track.isColumns);
if (track.visible && existingTrack != null
&& _.keys(existingTrack.settings).length === 0) {
existingTrack.settingFromConfig('Text');
}
_this.setTrackVisible(track.name, track.visible, track.isColumns);
});
this.revalidate();
this.trigger('change', {
name: 'setTrackVisibility',
source: this,
arguments: arguments
});
}
,
setTrackVisible: function (name, visible, isColumns) {
var trackIndex = this.getTrackIndex(name, isColumns);
if (trackIndex === -1) { // not currently visible
if (!visible) {
return;
}
this.addTrack(name, isColumns);
} else {
var track = isColumns ? this.columnTracks[trackIndex]
: this.rowTracks[trackIndex];
var header = isColumns ? this.columnTrackHeaders[trackIndex]
: this.rowTrackHeaders[trackIndex];
if (track.isVisible() !== visible) {
track.setVisible(visible);
header.setVisible(visible);
} else {
return;
}
}
this.trigger('change', {
name: 'setTrackVisible',
source: this,
arguments: arguments
});
},
addTrack: function (name, isColumns, renderSettings, trackIndex) {
if (name === undefined) {
throw 'Name not specified';
}
var tracks = isColumns ? this.columnTracks : this.rowTracks;
var headers = isColumns ? this.columnTrackHeaders : this.rowTrackHeaders;
// see if already exists
var existingIndex = this.getTrackIndex(name, isColumns);
if (existingIndex !== -1) {
return tracks[existingIndex];
}
if (renderSettings == null) {
var metadata = isColumns ? this.project.getFullDataset().getColumnMetadata() : this.project.getFullDataset().getRowMetadata();
renderSettings = phantasus.VectorUtil.getDataType(metadata.getByName(name)) === '[number]' ? {display: ['bar']}
: {display: ['text']};
}
var positions = isColumns ? this.heatmap.getColumnPositions() : this.heatmap.getRowPositions();
var track = new phantasus.VectorTrack(this.project, name, positions, isColumns, this);
track.settingFromConfig(renderSettings);
track.appendTo(this.$parent);
var header = new phantasus.VectorTrackHeader(this.project, name, isColumns,
this);
header.appendTo(this.$parent);
track._selection = new phantasus.TrackSelection(track, positions,
isColumns ? this.project.getColumnSelectionModel() : this.project.getRowSelectionModel(),
isColumns, this);
if (trackIndex != null && trackIndex >= 0) {
tracks.splice(trackIndex, 0, track);
headers.splice(trackIndex, 0, header);
} else {
tracks.push(track);
headers.push(header);
}
return track;
}
,
addPopup: function (item) {
if (!this.popupItems) {
this.popupItems = [];
}
this.popupItems.push(item);
}
,
getPopupItems: function () {
return this.popupItems || [];
}
,
removeTrack: function (name, isColumns) {
var index = this.getTrackIndex(name, isColumns);
var tracks = isColumns ? this.columnTracks : this.rowTracks;
if (isNaN(index) || index < 0 || index >= tracks.length) {
// console.log('removeTrack: ' + name + ' not found.');
return;
}
var headers = isColumns ? this.columnTrackHeaders
: this.rowTrackHeaders;
var track = tracks[index];
var header = headers[index];
track.dispose();
header.dispose();
tracks.splice(index, 1);
headers.splice(index, 1);
this.trigger('change', {
name: 'removeTrack',
source: this,
arguments: arguments
});
}
,
updateDataset: function () {
var dataset = this.project.getSortedFilteredDataset();
this.verticalSearchBar.update();
this.horizontalSearchBar.update();
this.heatmap.setDataset(dataset);
this.heatmap.getRowPositions().setSpaces(phantasus.HeatMap.createGroupBySpaces(dataset, this.project.getGroupRows(),
this.rowGapSize, false));
this.heatmap.getColumnPositions()
.setSpaces(phantasus.HeatMap.createGroupBySpaces(dataset, this.project.getGroupColumns(), this.columnGapSize, true));
this.trigger('change', {
name: 'updateDataset',
source: this,
arguments: arguments
});
}
,
zoom: function (isZoomIn, options) {
options = $.extend({}, {
rows: true,
columns: true
}, options);
if (isZoomIn) {
if (options.rows) {
this.heatmap.getRowPositions().setSize(
this.heatmap.getRowPositions().getSize() * 1.5);
}
if (options.columns) {
this.heatmap.getColumnPositions().setSize(
this.heatmap.getColumnPositions().getSize() * 1.5);
}
} else {
if (options.rows) {
this.heatmap.getRowPositions().setSize(
this.heatmap.getRowPositions().getSize() / 1.5);
}
if (options.columns) {
this.heatmap.getColumnPositions().setSize(
this.heatmap.getColumnPositions().getSize() / 1.5);
}
}
var reval = {};
if (options.rows && this.project.getHoverRowIndex() !== -1) {
reval.scrollTop = this.heatmap.getRowPositions().getPosition(
this.project.getHoverRowIndex());
}
if (options.columns && this.project.getHoverColumnIndex() !== -1) {
reval.scrollLeft = this.heatmap.getColumnPositions().getPosition(
this.project.getHoverColumnIndex());
}
this.revalidate(reval);
this.trigger('change', {
name: 'zoom',
source: this,
arguments: arguments
});
}
,
getTrackIndex: function (name, isColumns) {
var tracks = isColumns ? this.columnTracks : this.rowTracks;
for (var i = 0, length = tracks.length; i < length; i++) {
if (tracks[i].name !== undefined && tracks[i].name === name) {
return i;
}
}
return -1;
}
,
getNumTracks: function (isColumns) {
return isColumns ? this.columnTracks.length : this.rowTracks.length;
}
,
moveTrack: function (index, newIndex, isColumns) {
var tracks = isColumns ? this.columnTracks : this.rowTracks;
var headers = isColumns ? this.columnTrackHeaders
: this.rowTrackHeaders;
var track = tracks[index];
tracks.splice(index, 1);
var header = headers[index];
headers.splice(index, 1);
tracks.splice(newIndex, 0, track);
headers.splice(newIndex, 0, header);
this.trigger('change', {
name: 'moveTrack',
source: this,
arguments: arguments
});
}
,
getTrackByIndex: function (index, isColumns) {
return isColumns ? this.columnTracks[index] : this.rowTracks[index];
}
,
getTrackHeaderByIndex: function (index, isColumns) {
return isColumns ? this.columnTrackHeaders[index]
: this.rowTrackHeaders[index];
}
,
getTrack: function (name, isColumns) {
var index = this.getTrackIndex(name, isColumns);
if (index === -1) {
return undefined;
}
return isColumns ? this.columnTracks[index] : this.rowTracks[index];
}
,
/**
* @return true if active element is an ancestor of this heat map.
*/
isActiveComponent: function () {
var active = document.activeElement;
var tagName = active.tagName;
if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') {
return false;
}
return this.$tabPanel[0].contains(active);
}
,
/**
*
* @return {string} 'rowTrack' if row track is active, 'columnTrack' if column track is active,
* 'heatMap' if heat map is active.
*/
getActiveComponent: function () {
var active = document.activeElement;
if (active.tagName === 'CANVAS') {
for (var i = 0, ntracks = this.columnTracks.length; i < ntracks; i++) {
if (this.columnTracks[i].canvas === active) {
return 'columnTrack';
}
}
for (var i = 0, ntracks = this.rowTracks.length; i < ntracks; i++) {
if (this.rowTracks[i].canvas === active) {
return 'rowTrack';
}
}
if (this.heatmap.canvas === active) {
return 'heatMap';
}
}
return '';
},
dispose: function () {
//this.$content.remove();
if (this.project == null) {
return; // failed to initialize
}
this.project.off();
this.$tipInfoWindow.dialog('destroy');
this.rowTrackHeaders.forEach(function (header) {
header.dispose();
});
this.columnTrackHeaders.forEach(function (header) {
header.dispose();
});
this.rowTracks.forEach(function (track) {
track.dispose();
});
this.columnTracks.forEach(function (track) {
track.dispose();
});
if (this.rowDendrogram != null) {
this.rowDendrogram.dispose();
}
if (this.columnDendrogram != null) {
this.columnDendrogram.dispose();
}
this.beforeColumnTrackDivider.dispose();
this.afterRowDendrogramDivider.dispose();
this.afterVerticalScrollBarDivider.dispose();
this.hscroll.dispose();
this.vscroll.dispose();
this.hammer.off('swipe', this.swipe).off('panmove', this.panmove).off('panstart', this.panstart).off('tap',
this.tap).off('pinch', this.pinch).off('panend', this.panend);
this.hammer.destroy();
if (typeof window !== 'undefined') {
$(window)
.off('paste.phantasus', this.pasteListener)
.off('beforecopy.phantasus', this.beforeCopyListener)
.off('copy.phantasus', this.copyListener)
.off('orientationchange.phantasus resize.phantasus', this.resizeListener);
}
}
,
getVisibleTrackNames: function (isColumns) {
return this.getVisibleTracks(isColumns).map(function (track) {
return track.name;
});
},
getVisibleTracks: function (isColumns) {
var tracks = isColumns ? this.columnTracks : this.rowTracks;
return tracks.filter(function (track) {
return track.isVisible() && !track.getFullVector().getProperties().has(phantasus.VectorKeys.IS_INDEX);
});
},
isShowRowNumber: function () {
return this.options.showRowNumber;
},
setShowRowNumber: function (visible) {
this.options.showRowNumber = visible;
if (!visible) {
this.removeTrack('#', false);
} else {
var track = this.addTrack('#', false, {popupEnabled: false, display: ['text']}, 0);
track.getVector = function (name) {
var v = new phantasus.AbstractVector('#', this.project.getSortedFilteredDataset().getRowCount());
v.getProperties().set(phantasus.VectorKeys.FORMATTER, {pattern: 'i'});
v.getValue = function (index) {
return index + 1;
};
return v;
};
track.getFullVector = function () {
var v = new phantasus.AbstractVector('#', this.project.getFullDataset().getRowCount());
v.getProperties().set(phantasus.VectorKeys.FORMATTER, {pattern: 'i'});
v.getValue = function (index) {
return index + 1;
};
return v;
};
track.showPopup = function (e, isHeader) {
if (e.preventDefault) {
e.preventDefault();
}
};
}
}
,
resizeTrack: function (name, width, height, isColumns) {
var index = this.getTrackIndex(name, isColumns);
if (index === -1) {
throw name + ' not found in resize track';
}
var heatMapPrefWidth = null;
if (!isColumns) {
var track = this.rowTracks[index];
var header = this.rowTrackHeaders[index];
track.setPrefWidth(width); // can only set width
header.setPrefWidth(width);
} else {
var track = this.columnTracks[index];
var header = this.columnTrackHeaders[index];
if (height) {
track.setPrefHeight(height);
header.setPrefHeight(height);
}
if (width) {
for (var i = 0; i < this.columnTracks.length; i++) {
this.columnTracks[i].setPrefWidth(width);
this.columnTrackHeaders[i].setPrefWidth(width);
}
// set width for all tracks
}
}
this.revalidate();
this.trigger('change', {
name: 'resizeTrack',
source: this,
arguments: arguments
});
}
,
isDendrogramVisible: function (isColumns) {
var dendrogram = isColumns ? this.columnDendrogram : this.rowDendrogram;
if (dendrogram !== undefined) {
return phantasus.HeatMap.isDendrogramVisible(this.project, isColumns);
}
}
,
/**
*
* Paint all the components
*
* @param options.paintRows
* @param options.paintColumns
* @param options.invalidateRows
* @param options.invalidateColumns
*/
paintAll: function (options) {
var unscaledHeight = this.heatmap.getUnscaledHeight();
var unscaledWidth = this.heatmap.getUnscaledWidth();
var y = this.scrollTop();
var x = this.scrollLeft();
this.hscroll.paint();
this.vscroll.paint(); // FIXME
var rows = options.paintRows;
var columns = options.paintColumns;
var invalidateRows = options.invalidateRows;
var invalidateColumns = options.invalidateColumns;
// TODO double buffer search bars
this.hSortByValuesIndicator.setInvalid(invalidateRows
|| invalidateColumns);
this.hSortByValuesIndicator.paint({
x: x,
y: 0,
width: unscaledWidth,
height: this.hSortByValuesIndicator.getUnscaledHeight()
});
this.vSortByValuesIndicator.setInvalid(invalidateRows
|| invalidateColumns);
this.vSortByValuesIndicator.paint({
x: 0,
y: y,
width: this.vSortByValuesIndicator.getUnscaledWidth(),
height: unscaledHeight
});
if (rows) {
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
track.setInvalid(invalidateRows);
if (track.isVisible()) {
track.paint({
x: 0,
y: y,
height: unscaledHeight,
width: unscaledWidth
});
this.rowTrackHeaders[i].paint();
}
}
if (this.rowDendrogram != null) {
this.rowDendrogram.setInvalid(invalidateRows);
if (this.isDendrogramVisible(false)) {
this.rowDendrogram.setVisible(true);
this.rowDendrogram.paint({
x: 0,
y: y,
height: unscaledHeight,
width: this.rowDendrogram.getUnscaledWidth()
});
} else {
this.rowDendrogram.setVisible(false);
}
}
}
if (columns) {
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
track.setInvalid(invalidateColumns);
track.paint({
x: x,
y: 0,
width: unscaledWidth,
height: track.getUnscaledHeight()
});
this.columnTrackHeaders[i].paint();
}
if (this.columnDendrogram != null) {
this.columnDendrogram.setInvalid(invalidateColumns);
if (this.isDendrogramVisible(true)) {
this.columnDendrogram.setVisible(true);
this.columnDendrogram.paint({
x: x,
y: 0,
width: unscaledWidth,
height: this.columnDendrogram.getUnscaledHeight()
});
} else {
this.columnDendrogram.setVisible(false);
}
}
}
if (invalidateRows || invalidateColumns) {
this.heatmap.setInvalid(true);
}
this.heatmap.paint({
x: x,
y: y,
width: unscaledWidth,
height: unscaledHeight
});
this.trigger('change', {
name: 'paintAll',
source: this,
arguments: arguments
});
}
,
scrollTop: function (pos) {
if (pos === undefined) {
return this.vscroll.getValue();
}
if (isNaN(pos)) {
pos = 0;
}
if (this.vscroll.getVisibleExtent() === this.vscroll.getTotalExtent()) {
pos = 0;
}
pos = Math.max(pos, 0);
pos = Math.min(this.vscroll.getMaxValue(), pos);
if (pos !== this.vscroll.getValue()) {
this.vscroll.setValue(pos, true);
this.trigger('change', {
name: 'scrollTop',
source: this,
arguments: arguments
});
}
return pos;
}
,
scrollLeft: function (pos) {
if (pos === undefined) {
return this.hscroll.getValue();
}
if (isNaN(pos)) {
pos = 0;
}
if (this.hscroll.getVisibleExtent() === this.hscroll.getTotalExtent()) {
pos = 0;
}
pos = Math.max(pos, 0);
pos = Math.min(this.hscroll.getMaxValue(), pos);
if (pos !== this.hscroll.getValue()) {
this.trigger('change', {
name: 'scrollLeft',
source: this,
arguments: arguments
});
this.hscroll.setValue(pos, true);
}
return pos;
}
,
getSelectedTrackName: function (isColumns) {
return isColumns ? this.selectedColumnTrackName : this.selectedRowTrackName;
},
getLastSelectedTrackInfo: function () {
return this.selectedTrackInfo;
},
setSelectedTrack: function (name, isColumns) {
var previousName = isColumns ? this.selectedColumnTrackName : this.selectedRowTrackName;
if (name !== previousName) {
var index = this.getTrackIndex(previousName, isColumns); // de-select previous
if (index !== -1) {
this.getTrackHeaderByIndex(index, isColumns).setSelected(false);
}
if (isColumns) {
this.selectedColumnTrackName = name;
this.selectedTrackInfo = {name: name, isColumns: true};
} else {
this.selectedRowTrackName = name;
this.selectedTrackInfo = {name: name, isColumns: false};
}
var index = this.getTrackIndex(name, isColumns);
if (index !== -1) {
this.getTrackHeaderByIndex(index, isColumns).setSelected(true);
}
this.trigger('change', {
name: 'setSelected',
source: this,
arguments: arguments
});
}
}
,
saveImage: function (file, format) {
var _this = this;
var bounds = this.getTotalSize();
if (format === 'pdf') {
var context = new canvas2pdf.PdfContext(blobStream(), {size: [bounds.width, bounds.height]});
this.snapshot(context);
context.stream.on('finish', function () {
var blob = context.stream.toBlob('application/pdf');
saveAs(blob, file, true);
});
context.end();
} else if (format === 'svg') {
var context = new C2S(bounds.width, bounds.height);
this.snapshot(context);
var svg = context.getSerializedSvg();
var prefix = [];
prefix.push('<?xml version="1.0" encoding="utf-8"?>\n');
prefix.push('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"' +
' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n');
svg = prefix.join('') + svg;
var blob = new Blob([svg], {
type: 'text/plain;charset=utf-8'
});
saveAs(blob, file, true);
} else {
var canvas = $('<canvas></canvas>')[0];
var height = bounds.height;
var width = bounds.width;
var backingScale = phantasus.CanvasUtil.BACKING_SCALE;
canvas.height = backingScale * height;
canvas.style.height = height + 'px';
canvas.width = backingScale * width;
canvas.style.width = width + 'px';
var context = canvas.getContext('2d');
phantasus.CanvasUtil.resetTransform(context);
this.snapshot(context);
var toBlob = canvas.toBlobHD ? ['toBlobHD'] : 'toBlob';
canvas[toBlob](function (blob) {
if (blob == null || blob.size === 0) {
phantasus.FormBuilder.showInModal({
title: 'Save Image',
html: 'Image is too large to save.',
appendTo: _this.getContentEl(),
focus: _this.getFocusEl()
});
return;
}
saveAs(blob, file, true);
});
}
}
,
getTotalSize: function (options) {
options = $.extend({}, {
legend: true
}, options);
var _this = this;
var heatmapPrefSize = this.heatmap.getPreferredSize();
var totalSize = {
width: heatmapPrefSize.width,
height: heatmapPrefSize.height
};
if (this.isDendrogramVisible(false)) { // row dendrogram
totalSize.width += this.rowDendrogram.getUnscaledWidth() + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
}
if (this.isDendrogramVisible(true)) {
totalSize.height += this.columnDendrogram.getUnscaledHeight() + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
}
var maxRowHeaderHeight = 0;
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
var headerSize = this.rowTrackHeaders[i].getPrintSize();
totalSize.width += Math.max(headerSize.width, track.getPrintSize().width);
maxRowHeaderHeight = Math.max(maxRowHeaderHeight, headerSize.height);
}
}
var maxColumnHeaderWidth = 0;
var columnTrackHeightSum = 0;
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) {
columnTrackHeightSum += track.getPrintSize().height;
maxColumnHeaderWidth = Math.max(maxColumnHeaderWidth,
this.columnTrackHeaders[i].getPrintSize().width);
}
}
totalSize.height += Math.max(columnTrackHeightSum, maxRowHeaderHeight) + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
totalSize.width += maxColumnHeaderWidth + phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
// color legend
if (options.legend) {
var totalLegendWidth = 15;
var maxLegendHeight = 0;
var colorByValues = this.heatmap.getColorScheme().getColorByValues();
var ntracks = colorByValues.length;
for (var i = 0; i < ntracks; i++) {
var value = colorByValues[i];
if (value != null || ntracks === 1) {
// if (value != 'null') { // values are stored as string
//
// }
this.heatmap.getColorScheme().setCurrentValue(value);
var names = this.heatmap.getColorScheme().getNames();
maxLegendHeight = Math.max(maxLegendHeight, names != null ? names.length * 14 : 30);
totalLegendWidth += 250;
}
}
maxLegendHeight += 10; // spacer
totalSize.height = totalSize.height + maxLegendHeight;
totalSize.width = Math.max(totalSize.width, totalLegendWidth);
}
// color
var trackLegendSize = new phantasus.HeatMapTrackColorLegend(
_.filter(
this.columnTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.COLOR) || track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR));
}), this.getProject().getColumnColorModel()).getPreferredSize();
totalSize.height += trackLegendSize.height;
totalSize.width = Math.max(totalSize.width, trackLegendSize.width);
trackLegendSize = new phantasus.HeatMapTrackColorLegend(
_.filter(
this.rowTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.COLOR) || track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR));
}), this.getProject().getRowColorModel()).getPreferredSize();
totalSize.height += trackLegendSize.height;
totalSize.width = Math.max(totalSize.width, trackLegendSize.width);
// shape
trackLegendSize = new phantasus.HeatMapTrackShapeLegend(
_.filter(
this.columnTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE));
}), this.getProject().getColumnShapeModel()).getPreferredSize();
totalSize.height += trackLegendSize.height;
totalSize.width = Math.max(totalSize.width, trackLegendSize.width);
trackLegendSize = new phantasus.HeatMapTrackShapeLegend(
_.filter(
this.rowTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE));
}), this.getProject().getRowShapeModel()).getPreferredSize();
totalSize.height += trackLegendSize.height;
totalSize.width = Math.max(totalSize.width, trackLegendSize.width);
// font
trackLegendSize = new phantasus.HeatMapTrackShapeLegend(
_.filter(
this.columnTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT));
}), this.getProject().getColumnFontModel()).getPreferredSize();
totalSize.height += trackLegendSize.height;
totalSize.width = Math.max(totalSize.width, trackLegendSize.width);
trackLegendSize = new phantasus.HeatMapTrackShapeLegend(
_.filter(
this.rowTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT));
}), this.getProject().getRowFontModel()).getPreferredSize();
totalSize.height += trackLegendSize.height;
totalSize.width = Math.max(totalSize.width, trackLegendSize.width);
return totalSize;
}
,
getHeatMapElementComponent: function () {
return this.heatmap;
}
,
snapshot: function (context, options) {
options = $.extend({}, {
legend: true
}, options);
var heatmapPrefSize = this.heatmap.getPreferredSize();
var totalSize = this.getTotalSize(options);
var legendHeight = 0;
if (options.legend) {
var colorByValues = this.heatmap.getColorScheme().getColorByValues();
context.save();
context.translate(15, 0);
var ntracks = colorByValues.length;
for (var i = 0, ntracks = colorByValues.length; i < ntracks; i++) {
var value = colorByValues[i];
if (value != null || ntracks === 1) {
if (value != 'null') { // values are stored as string
// var $label = $('<div style="overflow:hidden;text-overflow:' +
// ' ellipsis;width:250px;max-width:250px;">'
// + value + '</div>');
// $keyContent.append($label);
// totalHeight += $label.height();
}
var trackLegend = new phantasus.ColorSupplierLegend(
this.heatmap.getColorScheme(), value);
trackLegend.draw({}, context);
legendHeight = Math.max(legendHeight, trackLegend.getUnscaledHeight());
var legendWidth = trackLegend.getUnscaledWidth();
context.translate(legendWidth, 0);
}
}
legendHeight += 10; // spacer
// phantasus.HeatMapColorSchemeLegend.drawColorScheme(context,
// this.heatmap.getColorScheme(), 200, true);
context.restore();
// legendHeight = this.heatmap.getColorScheme().getNames() != null ? this.heatmap
// .getColorScheme().getNames().length * 14
// : 40;
}
var legendOffset = 15;
var maxLegendHeight = 0;
// color legend
context.save();
context.translate(legendOffset, legendHeight);
var trackLegend = new phantasus.HeatMapTrackColorLegend(
_.filter(
this.columnTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.COLOR) || track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR));
}), this.getProject().getColumnColorModel());
trackLegend.draw({}, context);
var legendSize = trackLegend.getPreferredSize();
legendOffset += legendSize.width;
maxLegendHeight = Math.max(maxLegendHeight, legendSize.height);
context.restore();
// shape legend
context.save();
context.translate(legendOffset, legendHeight);
trackLegend = new phantasus.HeatMapTrackShapeLegend(
_.filter(
this.columnTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE));
}), this.getProject().getColumnShapeModel());
trackLegend.draw({}, context);
legendSize = trackLegend.getPreferredSize();
legendOffset += legendSize.width;
maxLegendHeight = Math.max(maxLegendHeight, legendSize.height);
context.restore();
// font legend
context.save();
context.translate(legendOffset, legendHeight);
trackLegend = new phantasus.HeatMapTrackFontLegend(
_.filter(
this.columnTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT));
}), this.getProject().getColumnFontModel());
trackLegend.draw({}, context);
legendSize = trackLegend.getPreferredSize();
legendOffset += legendSize.width;
maxLegendHeight = Math.max(maxLegendHeight, legendSize.height);
context.restore();
// row color legend
context.save();
context.translate(legendOffset, legendHeight);
trackLegend = new phantasus.HeatMapTrackColorLegend(
_.filter(
this.rowTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.COLOR) || track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR));
}), this.getProject().getRowColorModel());
trackLegend.draw({}, context);
legendSize = trackLegend.getPreferredSize();
legendOffset += legendSize.width;
maxLegendHeight = Math.max(maxLegendHeight, legendSize.height);
context.restore();
// shape legend
context.save();
context.translate(legendOffset, legendHeight);
trackLegend = new phantasus.HeatMapTrackShapeLegend(
_.filter(
this.rowTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE));
}), this.getProject().getRowShapeModel());
trackLegend.draw({}, context);
legendSize = trackLegend.getPreferredSize();
legendOffset += legendSize.width;
maxLegendHeight = Math.max(maxLegendHeight, legendSize.height);
context.restore();
// font legend
context.save();
context.translate(legendOffset, legendHeight);
trackLegend = new phantasus.HeatMapTrackFontLegend(
_.filter(
this.rowTracks,
function (track) {
return track.isVisible()
&& (track.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT));
}), this.getProject().getRowFontModel());
trackLegend.draw({}, context);
legendSize = trackLegend.getPreferredSize();
legendOffset += legendSize.width;
maxLegendHeight = Math.max(maxLegendHeight, legendSize.height);
context.restore();
legendHeight += maxLegendHeight;
var heatmapY = this.isDendrogramVisible(true) ? (this.columnDendrogram.getUnscaledHeight() +
phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS) : 0;
heatmapY += legendHeight;
var columnTrackY = heatmapY;
var heatmapX = this.isDendrogramVisible(false) ? (this.rowDendrogram.getUnscaledWidth() +
phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS) : 0;
var isColumnTrackVisible = false;
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) {
var header = this.columnTrackHeaders[i];
heatmapX = Math.max(heatmapX, header.getPrintSize().width);
heatmapY += track.getPrintSize().height;
isColumnTrackVisible = true;
}
}
if (isColumnTrackVisible) {
heatmapY += phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
}
// check if row headers are taller than column tracks
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
var header = this.rowTrackHeaders[i];
heatmapY = Math.max(heatmapY, header.getPrintSize().height);
}
}
if (this.isDendrogramVisible(true)) {
var columnDendrogramClip = {
x: 0,
y: 0,
height: this.columnDendrogram.getUnscaledHeight(),
width: heatmapPrefSize.width
};
context.save();
context.translate(heatmapX, legendHeight);
this.columnDendrogram.prePaint(columnDendrogramClip, context);
this.columnDendrogram.draw(columnDendrogramClip, context);
context.restore();
}
if (this.isDendrogramVisible(false)) {
var rowDendrogramClip = {
x: 0,
y: 0,
width: this.rowDendrogram.getUnscaledWidth(),
height: heatmapPrefSize.height
};
context.save();
context.translate(0, heatmapY);
this.rowDendrogram.prePaint(rowDendrogramClip, context);
this.rowDendrogram.draw(rowDendrogramClip, context);
context.restore();
}
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) {
context.save();
context.translate(heatmapX, columnTrackY);
var trackClip = {
x: 0,
y: 0,
width: heatmapPrefSize.width,
height: track.getPrintSize().height
};
track.print(trackClip, context);
context.restore();
// draw header
var header = this.columnTrackHeaders[i];
context.save();
var headerSize = header.getPrintSize();
var headerClip = {
x: 0,
y: 0,
width: headerSize.width,
height: trackClip.height
};
context.translate(heatmapX - 2, columnTrackY + trackClip.height);
header.print(headerClip, context);
context.restore();
columnTrackY += Math.max(headerClip.height, trackClip.height);
}
}
context.save();
context.translate(heatmapX, heatmapY);
this.heatmap.draw({
x: 0,
y: 0,
width: heatmapPrefSize.width,
height: heatmapPrefSize.height
}, context);
context.restore();
var rowTrackWidthSum = 0;
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
context.save();
var tx = phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS + heatmapX + heatmapPrefSize.width + rowTrackWidthSum;
var ty = heatmapY;
var trackClip = {
x: 0,
y: 0,
width: track.getPrintSize().width,
height: heatmapPrefSize.height
};
context.translate(tx, ty);
context.strokeStyle = 'white';
context.rect(0, 0, trackClip.width, trackClip.height);
// stroke is needed for clip to work for svg export
context.stroke();
context.clip();
track.print(trackClip, context);
context.restore();
// draw header
var header = this.rowTrackHeaders[i];
context.save();
var headerSize = header.getPrintSize();
var headerClip = {
x: 0,
y: 0,
width: headerSize.width,
height: headerSize.height
};
context.translate(tx, ty - 4);
header.print(headerClip, context);
context.restore();
rowTrackWidthSum += Math.max(headerSize.width, trackClip.width);
}
}
}
,
resetZoom: function () {
var heatmap = this.heatmap;
var rowSizes = heatmap.getRowPositions();
var columnSizes = heatmap.getColumnPositions();
rowSizes.setSize(13);
columnSizes.setSize(13);
var reval = {};
if (this.project.getHoverRowIndex() !== -1) {
reval.scrollTop = this.heatmap.getRowPositions().getPosition(
this.project.getHoverRowIndex());
}
if (this.project.getHoverColumnIndex() !== -1) {
reval.scrollLeft = this.heatmap.getColumnPositions().getPosition(
this.project.getHoverColumnIndex());
}
this.revalidate(reval);
}
,
getFitColumnSize: function () {
var heatmap = this.heatmap;
var availablePixels = this.getAvailableWidth();
if (availablePixels === -1) {
return 13;
}
if (this.rowDendrogram) {
availablePixels -= this.rowDendrogram.getUnscaledWidth();
}
var trackPixels = 12; // spacer
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
trackPixels += track.getUnscaledWidth();
}
}
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) { // all column track headers have the
// same width
trackPixels += this.columnTrackHeaders[i].getUnscaledWidth();
break;
}
}
availablePixels -= trackPixels;
var positions = heatmap.getColumnPositions();
var totalCurrent = positions.getItemSize(positions.getLength() - 1)
+ positions.getPosition(positions.getLength() - 1);
var size = positions.getSize();
size = size * (availablePixels / totalCurrent);
size = Math.min(13, size);
return size;
}
,
getFitRowSize: function () {
var heatmap = this.heatmap;
var availablePixels = this.getAvailableHeight();
if (availablePixels === -1) {
return 13;
}
if (this.columnDendrogram) {
availablePixels -= this.columnDendrogram.getUnscaledHeight();
}
var trackPixels = 12;
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) {
trackPixels += track.getUnscaledHeight();
}
}
availablePixels -= trackPixels;
var positions = heatmap.getRowPositions();
var totalCurrent = positions.getItemSize(positions.getLength() - 1)
+ positions.getPosition(positions.getLength() - 1);
var size = positions.getSize();
size = size * (availablePixels / totalCurrent);
size = Math.min(13, size);
return size;
}
,
/**
* @param options.fitRows
* @param options.fitColumns
* @param options.repaint
*/
fitToWindow: function (options) {
if (options.fitRows) {
this.heatmap.getRowPositions().setSize(this.getFitRowSize());
}
if (options.fitColumns) {
this.heatmap.getColumnPositions().setSize(this.getFitColumnSize());
}
if (options.repaint) {
var revalOptions = {};
if (options.fitRows) {
if (this.project.getHoverRowIndex() !== -1) {
revalOptions.scrollTop = this.heatmap.getRowPositions().getPosition(
this.project.getHoverRowIndex());
}
}
if (options.fitColumns) {
if (this.project.getHoverColumnIndex() !== -1) {
revalOptions.scrollLeft = this.heatmap.getColumnPositions().getPosition(this.project.getHoverColumnIndex());
}
}
this.revalidate(revalOptions);
}
}
,
getAvailableHeight: function () {
if (_.isNumber(this.options.height)) {
return this.options.height;
}
var height = $(window).height() - this.$parent.offset().top - 24;
if (this.options.height === 'window') {
return height;
}
return Math.max(Math.round(screen.height * 0.7), height);
}
,
getAvailableWidth: function () {
if (this.options.width) {
return this.options.width;
}
// (this.$el.parent().outerWidth() - 30);
// return this.$el.width() - 30;
return this.tabManager.getWidth() - 30;
}
,
/**
* Layout all the components
*/
revalidate: function (options) {
if (phantasus.Util.isHeadless()) {
// hack to force creation of color scheme
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
track.setInvalid(true);
if (track.isVisible()) {
track.paint({
x: 0,
y: 0,
height: 10,
width: 10
});
}
}
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
track.setInvalid(true);
if (track.isVisible()) {
track.paint({
x: 0,
y: 0,
height: 10,
width: 10
});
}
}
return;
}
options = $.extend({}, {
paint: true
}, options);
this.updatingScroll = true;
var availableHeight = this.getAvailableHeight();
var availableWidth = this.getAvailableWidth();
var heatmapPrefSize = this.heatmap.getPreferredSize();
var columnDendrogramHeight = 0;
var rowDendrogramWidth = 0;
if (this.columnDendrogram) {
columnDendrogramHeight = phantasus.CanvasUtil.getPreferredSize(this.columnDendrogram).height;
}
if (this.rowDendrogram) {
rowDendrogramWidth = phantasus.CanvasUtil.getPreferredSize(this.rowDendrogram).width;
}
var rowTrackWidthSum = 0;
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
if (this.rowTracks[i].isVisible()) {
// was manually resized
if (this.rowTracks[i].getPrefWidth() !== undefined) {
this.rowTrackHeaders[i].setPrefWidth(this.rowTracks[i].getPrefWidth());
}
rowTrackWidthSum += Math.max(phantasus.CanvasUtil.getPreferredSize(this.rowTrackHeaders[i]).width,
phantasus.CanvasUtil.getPreferredSize(this.rowTracks[i]).width);
}
}
if (availableWidth !== -1 && (rowTrackWidthSum + rowDendrogramWidth + heatmapPrefSize.width) > availableWidth) {
// shrink row tracks
//var over = (rowTrackWidthSum + rowDendrogramWidth + heatmapPrefSize.width) - availableWidth;
rowTrackWidthSum = 0;
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
if (this.rowTracks[i].isVisible()) {
var rowTrackHeaderSize = phantasus.CanvasUtil.getPreferredSize(this.rowTrackHeaders[i]);
var width = Math.max(rowTrackHeaderSize.width, phantasus.CanvasUtil.getPreferredSize(this.rowTracks[i]).width);
if (!rowTrackHeaderSize.widthSet) {
width = Math.min(400, width);
this.rowTracks[i].setPrefWidth(width);
this.rowTrackHeaders[i].setPrefWidth(width);
}
rowTrackWidthSum += width;
}
}
}
var ypos = columnDendrogramHeight;
var maxHeaderWidth = 0;
// get max column header width
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
if (this.columnTracks[i].isVisible()) {
if (this.columnTracks[i].getPrefHeight() !== undefined) {
this.columnTrackHeaders[i].setPrefHeight(this.columnTracks[i].getPrefHeight());
}
var width = phantasus.CanvasUtil.getPreferredSize(this.columnTrackHeaders[i]).width;
maxHeaderWidth = Math.max(maxHeaderWidth, width);
}
}
var xpos = Math.max(rowDendrogramWidth, maxHeaderWidth);
var heatMapWidth = heatmapPrefSize.width;
var maxHeatMapWidth = Math.max(50, availableWidth === -1 ? Number.MAX_VALUE : (availableWidth - rowTrackWidthSum
- xpos
- phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS));
if (maxHeatMapWidth > 0 && heatMapWidth > maxHeatMapWidth) {
heatMapWidth = maxHeatMapWidth;
heatMapWidth = Math.min(heatMapWidth, heatmapPrefSize.width); // can't
// go
// bigger
// than
// pref
// width
}
if (this.heatmap.prefWidth !== undefined) { // heat map was manually
// resized
heatMapWidth = Math.min(heatmapPrefSize.width,
this.heatmap.prefWidth);
}
if (this.columnDendrogram !== undefined) {
this.columnDendrogram.setBounds({
width: heatMapWidth,
height: columnDendrogramHeight,
left: xpos,
top: 0
});
this.columnDendrogram.$label.css('left',
xpos + this.columnDendrogram.getUnscaledWidth() + 10).css(
'top', 2);
this.columnDendrogram.$squishedLabel.css('left',
xpos + this.columnDendrogram.getUnscaledWidth() + 10).css(
'top', 18);
this.beforeColumnTrackDivider.setVisible(true);
this.beforeColumnTrackDivider.setBounds({
left: xpos - maxHeaderWidth,
top: ypos,
width: maxHeaderWidth
});
ypos++;
} else {
this.beforeColumnTrackDivider.setVisible(false);
}
for (var i = 0, length = this.columnTracks.length; i < length; i++) {
var track = this.columnTracks[i];
if (track.isVisible()) {
var size = phantasus.CanvasUtil.getPreferredSize(track);
var headerSize = phantasus.CanvasUtil.getPreferredSize(this.columnTrackHeaders[i]);
size.height = Math.max(size.height, headerSize.height);
track.setBounds({
width: heatMapWidth,
height: size.height,
left: xpos,
top: ypos
});
this.columnTrackHeaders[i].setBounds({
width: maxHeaderWidth,
height: size.height,
left: xpos - maxHeaderWidth,
top: ypos
});
ypos += size.height;
}
}
this.$whitespace[0].style.left = Math.ceil(xpos + heatMapWidth + 10) + 'px';
this.$whitespace[0].style.top = '0px';
ypos += phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS;
var heatMapHeight = heatmapPrefSize.height;
if (availableHeight !== -1 && heatMapHeight > (availableHeight - ypos)) {
heatMapHeight = Math.max(100, Math.min(heatmapPrefSize.height,
availableHeight - ypos));
}
if (ypos < 0) {
ypos = 0;
}
if (this.rowDendrogram) {
this.rowDendrogram.setBounds({
width: Math.max(rowDendrogramWidth, maxHeaderWidth),
height: heatMapHeight,
left: 0,
top: ypos
});
this.rowDendrogram.$label.css('left', 0).css('top', 2);
this.afterRowDendrogramDivider.setVisible(true);
this.afterRowDendrogramDivider.setBounds({
height: heatMapHeight,
left: this.rowDendrogram.getUnscaledWidth(),
top: ypos
});
xpos++;
} else {
this.afterRowDendrogramDivider.setVisible(false);
}
this.heatmap.setBounds({
width: heatMapWidth,
height: heatMapHeight,
left: xpos,
top: ypos
});
this.hSortByValuesIndicator.setBounds({
height: 4,
width: heatMapWidth,
left: xpos,
top: ypos - 4
});
this.hscroll.setVisible(heatMapWidth < heatmapPrefSize.width);
this.hscroll.setExtent(heatMapWidth, heatmapPrefSize.width,
options.scrollLeft !== undefined ? options.scrollLeft
: (heatmapPrefSize.width === this.hscroll.getTotalExtent() ? this.hscroll.getValue()
: heatmapPrefSize.width
* this.hscroll.getValue()
/ this.hscroll.getMaxValue()));
this.hscroll.setBounds({
left: xpos,
top: ypos + heatMapHeight + 2
});
xpos += heatMapWidth;
var nvisibleRowTracks = 0;
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
nvisibleRowTracks++;
break;
}
}
this.vSortByValuesIndicator.setBounds({
width: 4,
height: heatMapHeight,
left: xpos,
top: ypos
});
if (nvisibleRowTracks > 0) {
xpos = xpos
+ phantasus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; // leave
// space
// after
// afterVerticalScrollBarDivider
}
var rowAnnotationXStart = xpos;
// set row track bounds
for (var i = 0, length = this.rowTracks.length; i < length; i++) {
var track = this.rowTracks[i];
if (track.isVisible()) {
var size = phantasus.CanvasUtil.getPreferredSize(track);
var headerSize = phantasus.CanvasUtil.getPreferredSize(this.rowTrackHeaders[i]);
size.width = Math.max(headerSize.width, size.width);
size.height = heatMapHeight;
track.setBounds({
width: size.width,
height: size.height,
left: xpos,
top: ypos
});
this.rowTrackHeaders[i].setBounds({
width: size.width,
left: xpos,
top: ypos - headerSize.height - 5,
height: headerSize.height
});
xpos += size.width;
}
}
this.afterVerticalScrollBarDivider.setVisible(nvisibleRowTracks > 0 ? true : false);
this.afterVerticalScrollBarDivider.setBounds({
left: rowAnnotationXStart - 2,
top: ypos - 18
});
this.vscroll.setVisible(heatMapHeight < heatmapPrefSize.height);
this.vscroll.setExtent(heatMapHeight, heatmapPrefSize.height,
options.scrollTop !== undefined ? options.scrollTop
: (heatmapPrefSize.height === this.vscroll.getTotalExtent() ? this.vscroll.getValue()
: heatmapPrefSize.height
* this.vscroll.getValue()
/ this.vscroll.getMaxValue()));
xpos += 2;
this.vscroll.setBounds({
left: xpos,
top: ypos
});
xpos += this.vscroll.getUnscaledWidth();
if (this.hscroll.isVisible()) {
ypos += this.hscroll.getUnscaledHeight() + 2;
}
var totalHeight = 2 + ypos + heatMapHeight;
if (options.paint) {
this.paintAll({
paintRows: true,
paintColumns: true,
invalidateRows: true,
invalidateColumns: true
});
}
this.$parent.css({
height: Math.ceil(totalHeight) + 'px',
width: availableWidth === -1 ? (Math.ceil(xpos + 2) + 'px') : ''
});
this.updatingScroll = false;
this.trigger('change', {
name: 'revalidate',
source: this,
arguments: arguments
});
}
};
phantasus.HeatMap.copyFromParent = function (project, options) {
// TODO persist sort order, grouping, dendrogram
project.rowColorModel = options.parent.getProject().getRowColorModel().copy();
project.columnColorModel = options.parent.getProject().getColumnColorModel().copy();
project.rowShapeModel = options.parent.getProject().getRowShapeModel().copy();
project.columnShapeModel = options.parent.getProject().getColumnShapeModel().copy();
project.rowFontModel = options.parent.getProject().getRowFontModel().copy();
project.columnFontModel = options.parent.getProject().getColumnFontModel().copy();
var parentRowTracks = options.parent.rowTracks || [];
var parentColumnTracks = options.parent.columnTracks || [];
if (options.inheritFromParentOptions.rows) { // row similarity matrix
project.columnShapeModel = project.rowShapeModel;
project.columnColorModel = project.rowColorModel;
project.columnFontModel = project.rowFontModel;
parentColumnTracks = parentRowTracks.slice().reverse();
}
if (options.inheritFromParentOptions.columns) { // column similarity matrix
project.rowShapeModel = project.columnShapeModel;
project.rowColorModel = project.columnColorModel;
project.rowFontModel = project.columnFontModel;
parentRowTracks = parentColumnTracks.slice().reverse();
}
if (options.inheritFromParentOptions.transpose) {
var tmp = project.rowShapeModel;
project.rowShapeModel = project.columnShapeModel;
project.columnShapeModel = tmp;
tmp = project.rowColorModel;
project.rowColorModel = project.columnColorModel;
project.columnColorModel = tmp;
tmp = project.rowFontModel;
project.rowFontModel = project.columnFontModel;
project.columnFontModel = tmp;
tmp = parentRowTracks.slice().reverse();
// swap tracks
parentRowTracks = parentColumnTracks.slice().reverse();
parentColumnTracks = tmp;
}
// copy track rendering options and order
// from parent
options.rows = options.rows || [];
for (var i = 0; i < parentRowTracks.length; i++) {
var track = parentRowTracks[i];
if (track.isVisible()) {
options.rows.push({
order: options.rows.length,
field: track.getName(),
display: $.extend(true, {}, track.settings),
force: true
});
}
}
options.columns = options.columns || [];
for (var i = 0; i < parentColumnTracks.length; i++) {
var track = parentColumnTracks[i];
if (track.isVisible()) {
options.columns.push({
order: options.columns.length,
field: track.getName(),
display: $.extend(true, {}, track.settings),
force: true
});
}
}
};
phantasus.Util.extend(phantasus.HeatMap, phantasus.Events);
phantasus.HelpMenu = function () {
var html = [];
html.push('<div class="btn-group">');
html.push('<button type="button" class="btn btn-default btn-xxs' +
' dropdown-toggle"' +
' data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">');
html.push('<svg width="14px" height="14px" style="inline-block;vertical-align:middle;"><g><rect x="0" y="0" width="14" height="6" style="fill:#ca0020;stroke:none"></rect><rect x="0" y="7" width="14" height="6" style="fill:#0571b0;stroke:none"></rect></g></svg>');
html.push('<span class="fa fa-caret-down"></span>');
html.push('</button>');
html
.push('<ul class="dropdown-menu dropdown-menu-right" role="menu">');
html.push('<li><a data-name="contact" href="#">Contact</a></li>');
html.push('<li><a data-name="tutorial" href="#">Tutorial</a></li>');
html.push('<li><a data-name="source" href="#">Source Code</a></li>');
html.push('<li><a data-name="about" href="#">About</a></li>');
html.push('</ul>');
html.push('</div>');
this.$el = $(html.join(''));
this.$el.find('[data-name=contact]').on('click', function (e) {
phantasus.FormBuilder.showInModal({
title: 'Contact',
html: 'Please email us at alsergbox@gmail.com',
focus: document.activeElement
});
e.preventDefault();
});
this.$el.find('[data-name=tutorial]').on('click', function (e) {
window
.open('phantasus-tutorial.html');
e.preventDefault();
});
this.$el.find('[data-name=source]').on('click', function (e) {
window.open('https://github.com/ctlab/phantasus');
e.preventDefault();
});
this.$el.find('[data-name=about]').on('click', function (e) {
var $div = $([
'<div>',
'Phantasus version: ' + PHANTASUS_VERSION + ', build: ' + PHANTASUS_BUILD + '<br/>',
'Changelog available at: <a href="https://raw.githubusercontent.com/ctlab/phantasus/master/NEWS" target="_blank">Github</a><br/>',
'Source Code available at: <a href="http://github.com/ctlab/phantasus" target="_blank">Github</a>',
'</div>'
].join('\n'));
phantasus.FormBuilder.showInModal({
title: 'About Phantasus',
close: 'Close',
html: $div,
});
e.preventDefault();
});
};
phantasus.HistogramLegend = function (dataset, colorScheme, metadataValue) {
phantasus.AbstractCanvas.call(this, true);
this.colorScheme = colorScheme;
this.metadataValue = metadataValue;
this.dataset = dataset;
this.binNumberToOccurences = null;
this.setBounds({
width: 250,
height: 70
});
this.name = null;
this.canvas.style.position = '';
this.canvas.style.border = '1px solid LightGrey';
};
phantasus.HistogramLegend.prototype = {
binSize: 0,
maxCount: 0,
total: 0,
setName: function (name) {
this.name = name;
},
setBinSize: function (binSize) {
this.binSize = binSize;
},
buildHistogram: function () {
var binSize = this.binSize;
var dataset = this.dataset;
var metadataValue = this.metadataValue;
var colorScheme = this.colorScheme;
var min = colorScheme.getMin();
var max = colorScheme.getMax();
if (min === max) {
min -= 0.5;
max += 0.5;
}
var vector =
dataset.getRowMetadata().getByName(colorScheme.getSeparateColorSchemeForRowMetadataField());
// var numberOfBins = Math.ceil(phantasus.Log2(dataset.getRowCount() * dataset.getColumnCount()) + 1);
// var binSize = (max - min) / numberOfBins;
var numberOfBins = Math.ceil((max - min) / binSize);
var binNumberToOccurences = new Uint32Array(numberOfBins);
this.binNumberToOccurences = binNumberToOccurences;
//var values = new Float32Array(dataset.getRowCount() * dataset.getColumnCount()); // for
// boxplot
var index = 0;
for (var i = 0, nrows = dataset.getRowCount(); i < nrows; i++) {
if (vector == null || vector.getValue(i) === metadataValue) {
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
var value = dataset.getValue(i, j);
if (isNaN(value)) {
continue;
}
// values[index++] = value;
var bin = Math.floor(((value - min) / binSize));
if (bin < 0) {
bin = 0;
} else if (bin >= numberOfBins) {
bin = numberOfBins - 1;
}
binNumberToOccurences[bin]++;
}
}
}
// values = values.slice(0, index);
// values.sort();
var maxCount = 0;
var total = 0;
for (var i = 0; i < numberOfBins; i++) {
var count = binNumberToOccurences[i];
maxCount = count >= maxCount ? count : maxCount;
total += count;
}
this.maxCount = maxCount;
this.total = total;
},
draw: function (clip, context) {
this.buildHistogram();
var colorScheme = this.colorScheme;
var canvasWidth = this.getUnscaledWidth() - 50;
var valueToPosition = d3.scale.linear().domain([colorScheme.getMin(), colorScheme.getMax()]).range([0, canvasWidth]).clamp(
true);
var histogramHeight = 30;
var countToPosition = d3.scale.linear().domain([0, this.maxCount / this.total]).range([histogramHeight, 0]).clamp(
true);
var binNumberToOccurences = this.binNumberToOccurences;
var min = colorScheme.getMin();
var binSize = this.binSize;
var y0 = countToPosition(0);
if (this.name != null) {
context.font = '11px ' + phantasus.CanvasUtil.getFontFamily(context);
context.fillStyle = 'black';
context.lineWidth = 1;
// context.textBaseline = 'top';
context.fillText(this.name, 0.5, 12);
context.translate(0, 14);
}
context.lineWidth = 0.2;
context.strokeStyle = '#D3D2C2';
context.fillStyle = '#D3D2C2'; //'#d9d9d9';
context.translate(25, 0);
context.beginPath();
context.moveTo(0, y0);
context.lineTo(canvasWidth, y0);
context.stroke();
context.lineWidth = 1;
context.strokeStyle = 'white';
for (var i = 0, numberOfBins = binNumberToOccurences.length; i < numberOfBins; i++) {
var count = binNumberToOccurences[i];
if (count > 0) {
count /= this.total;
var start = min + (i * binSize);
var end = start + binSize;
var x = valueToPosition(start);
var width = valueToPosition(end) - x;
var y = countToPosition(count);
context.rect(x, y0, width, y - y0);
context.fill();
context.stroke();
}
}
// boxplot
// var q25 = valueToPosition(this.q25);
// var q75 = valueToPosition(this.q75);
// var median = valueToPosition(this.median);
// var lav = valueToPosition(this.lowerAdjacentValue);
// var uav = valueToPosition(this.upperAdjacentValue);
// context.translate(0, histogramHeight + 1);
// context.fillStyle = 'black';
// var boxPlotHeight = 8;
// context.fillRect(q25, 0, q75 - q25, boxPlotHeight);
//
// context.fillRect(lav, boxPlotHeight / 2 - 1, q25 - lav, 2);
//
// context.fillRect(q75, boxPlotHeight / 2 - 1, uav - q75, 2);
//
// context.fillStyle = 'white';
// context.fillRect(median - 1, 0.5, 2, boxPlotHeight - 0.5);
//
context.translate(0, histogramHeight + 1);
context.fillStyle = 'black';
phantasus.HeatMapColorSchemeLegend.drawColorScheme(context,
this.colorScheme, canvasWidth, false, false, 6);
}
}
;
phantasus.Util.extend(phantasus.HistogramLegend, phantasus.AbstractCanvas);
phantasus.LegendWithStops = function () {
var _this = this;
phantasus.AbstractCanvas.call(this, false);
this.setBounds({
width: 300,
height: 40
});
$(this.canvas).on('mousedown', function (event) {
var position = phantasus.CanvasUtil.getMousePos(
event.target, event);
_this.selectedIndex = _this
.findIndexForPosition(position);
_this.trigger('selectedIndex', {
selectedIndex: _this.selectedIndex
});
});
this.hammer = phantasus.Util.hammer(this.canvas, ['pan'])
.on(
'panmove',
this.panmove = function (event) {
if (_this.selectedIndex !== -1) {
var position = phantasus.CanvasUtil.getMousePos(
event.target, event);
var fraction = _this.fractionToStopPix
.invert(position.x);
fraction = Math.max(0, fraction);
fraction = Math.min(1, fraction);
_this.trigger('moved', {
fraction: fraction
});
}
}).on(
'panstart',
this.panstart = function (event) {
}).on('panend', this.panend = function (event) {
_this.selectedIndex = -1;
});
$(this.canvas).on('keydown', function (e) {
// 8=backspace, 46=delete
if ((e.which == 8 || e.which == 46) && _this.selectedIndex !== -1) {
_this.trigger('delete');
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
});
};
phantasus.LegendWithStops.prototype = {
border: 7,
stopHalfSize: 5,
selectedIndex: -1,
destroy: function () {
$(this.canvas).off('keyup').off('mousedown');
this.hammer.off('panstart',
this.panstart).off('panmove', this.panmove);
this.hammer.destroy();
},
setSelectedIndex: function (index) {
this.selectedIndex = index;
},
findIndexForPosition: function (position) {
// pix - stopHalfSize to pix + stopHalfSize
if (position.y >= 22) {
for (var i = 0, length = this.fractions.length; i < length; i++) {
var pix = this.fractionToStopPix(this.fractions[i]);
var start = pix - this.stopHalfSize;
var end = pix + this.stopHalfSize;
if (position.x >= start && position.x <= end) {
return i;
}
}
}
return -1;
},
draw: function (fractions, colors, stepped, fractionToStopPix) {
this.fractions = fractions;
this.colors = colors;
this.stepped = stepped;
this.fractionToStopPix = fractionToStopPix;
var context = this.canvas.getContext('2d');
phantasus.CanvasUtil.resetTransform(context);
context.clearRect(0, 0, this.getUnscaledWidth(), this
.getUnscaledHeight());
context.translate(this.border, 0);
phantasus.HeatMapColorSchemeLegend.draw(context, fractions, colors, this
.getUnscaledWidth()
- 2 * this.border, this.getUnscaledHeight() - 20, stepped);
context.translate(-this.border, 0);
context.lineWidth = 1;
context.strokeStyle = 'Grey';
context.strokeRect(this.border, 0, this.getUnscaledWidth() - 2
* this.border, this.getUnscaledHeight() - 20);
for (var i = 0; i < fractions.length; i++) {
if (i > 0 && fractions[i] === fractions[i - 1]) {
continue;
}
context.fillStyle = colors[i];
var pix = fractionToStopPix(fractions[i]);
context.fillRect(pix - this.stopHalfSize, 22,
this.stopHalfSize * 2, this.stopHalfSize * 2);
if (this.selectedIndex === i) {
context.lineWidth = 2;
context.strokeStyle = 'black';
} else {
context.lineWidth = 1;
context.strokeStyle = 'Grey';
}
context.strokeRect(pix - this.stopHalfSize, 22,
this.stopHalfSize * 2, this.stopHalfSize * 2);
}
}
};
phantasus.Util.extend(phantasus.LegendWithStops, phantasus.AbstractCanvas);
phantasus.Util.extend(phantasus.LegendWithStops, phantasus.Events);
phantasus.Popup = {};
phantasus.Popup.initted = false;
phantasus.Popup.init = function () {
if (phantasus.Popup.initted) {
return;
}
phantasus.Popup.initted = true;
phantasus.Popup.$popupDiv = $(document.createElement('div'));
phantasus.Popup.$popupDiv.css('position', 'absolute').css('zIndex', 1050).css('overflow', 'auto').addClass('dropdown clearfix');
phantasus.Popup.$contextMenu = $(document.createElement('ul'));
phantasus.Popup.$contextMenu.addClass('dropdown-menu').css('display',
'block').css('position', 'static').css('margin-bottom', '5px');
phantasus.Popup.$contextMenu.appendTo(phantasus.Popup.$popupDiv);
phantasus.Popup.$contextMenu.on('click', 'a', function (e) {
e.preventDefault();
var $this = $(this);
// if (!$this.hasClass('copy')) {
phantasus.Popup.popupCallback(e, $this.data('name'));
phantasus.Popup.hide();
// }
});
};
phantasus.Popup.popupInDom = false;
phantasus.Popup.hidePopupMenu = function (e) {
if (phantasus.Popup.component == e.target) {
e.preventDefault();
e.stopPropagation();
}
phantasus.Popup.hide();
};
phantasus.Popup.hide = function () {
phantasus.Popup.$popupDiv.hide();
$(document.body).off('mousedown', phantasus.Popup.hidePopupMenu);
phantasus.Popup.popupCallback = null;
phantasus.Popup.component = null;
};
phantasus.Popup.showPopup = function (menuItems, position, component, callback) {
phantasus.Popup.init();
if (phantasus.Popup.component == component) {
phantasus.Popup.hide();
return;
}
phantasus.Popup.popupCallback = callback;
phantasus.Popup.component = component;
var html = [];
for (var i = 0, length = menuItems.length; i < length; i++) {
var item = menuItems[i];
if (item.header) {
html.push('<li role="presentation" class="dropdown-header">'
+ item.name + '</li>');
} else if (item.separator) {
html.push('<li class="divider"></li>');
} else {
html.push('<li role="presentation"');
if (item.disabled) {
html.push('class="disabled"');
}
html.push('><a data-name="' + item.name
+ '" data-type="popup-item" tabindex="-1" href="#"');
if (item.class) {
html.push(' class="' + item.class + '"');
}
html.push('>');
if (item.checked) {
html
.push('<span class="dropdown-checkbox fa fa-check"></span>');
}
html.push(item.name);
if (item.icon) {
html.push('<span class="pull-right ' + item.icon + '"></span>');
}
html.push('</a>');
html.push('</li>');
}
}
phantasus.Popup.$contextMenu.html(html.join(''));
if (!phantasus.Popup.popupInDom) {
phantasus.Popup.popupInDom = true;
phantasus.Popup.$popupDiv.appendTo($(document.body));
}
var $body = $(document.body);
var $window = $(window);
var windowWidth = $window.width();
var windowHeight = $window.height();
var popupWidth = phantasus.Popup.$popupDiv.width();
var popupHeight = phantasus.Popup.$popupDiv.height();
var left = position.x;
var top = position.y;
// default is bottom-right
if ((left + popupWidth) >= windowWidth) { // offscreen right
left -= popupWidth;
left = Math.max(4, left);
}
if ((top + popupHeight) >= (windowHeight)) { // offscreen bottom
top -= popupHeight;
top = Math.max(4, top);
}
phantasus.Popup.$popupDiv.css({
// height: popupHeight + 'px',
display: 'block',
left: left,
top: top
});
phantasus.Popup.$popupDiv.show();
$body.off('mousedown', phantasus.Popup.hidePopupMenu);
window.setTimeout(function () {
$body.on('mousedown', function (e) {
var $target = $(e.target);
if ($target[0] !== phantasus.Popup.$popupDiv[0] && $target.data('type') !== 'popup-item') {
phantasus.Popup.hidePopupMenu(e);
}
});
}, 1);
};
phantasus.RowDendrogram = function (heatMap, tree, positions, project) {
phantasus.AbstractDendrogram.call(this, heatMap, tree, positions,
project, phantasus.AbstractDendrogram.Type.ROW);
};
phantasus.RowDendrogram.prototype = {
drawNode: function (context, node) {
var radius = this.getNodeRadius(node);
var pix = this.toPix(node);
context.beginPath();
context.arc(pix[0], pix[1], radius, Math.PI * 2, false);
context.fill();
},
isDragHotSpot: function (p) {
return Math.abs(this.scale(this.cutHeight) - p.x) <= 2;
},
drawCutSlider: function (clip, context) {
if (context.setLineDash) {
context.setLineDash([5]);
}
context.strokeStyle = 'black';
var nx = this.scale(this.cutHeight);
context.beginPath();
context.moveTo(nx, clip.y);
context.lineTo(nx, this.getUnscaledHeight());
context.stroke();
if (context.setLineDash) {
context.setLineDash([]);
}
},
getPreferredSize: function () {
return {
width: 100,
height: Math.ceil(this.positions.getPosition(this.positions
.getLength() - 1)
+ this.positions
.getItemSize(this.positions.getLength() - 1))
};
},
paintMouseOver: function (clip, context) {
if (this.project.getHoverRowIndex() !== -1) {
phantasus.CanvasUtil.resetTransform(context);
context.translate(0, -clip.y);
this.drawRowBorder(context, this.positions, this.project
.getHoverRowIndex(), this.getUnscaledWidth());
}
},
drawRowBorder: function (context, positions, index, gridSize) {
var size = positions.getItemSize(index);
var pix = positions.getPosition(index);
// top and bottom lines
context.beginPath();
context.moveTo(0, pix + size);
context.lineTo(gridSize, pix + size);
context.stroke();
context.beginPath();
context.moveTo(0, pix);
context.lineTo(gridSize, pix);
context.stroke();
},
createScale: function () {
return d3.scale.linear().domain([0, this.tree.maxHeight]).range(
[this.getUnscaledWidth(), 0]);
},
getMaxIndex: function (clip) {
return phantasus.Positions.getBottom(clip, this.positions);
},
getMinIndex: function (clip) {
return phantasus.Positions.getTop(clip, this.positions);
},
toPix: function (node) {
var min = this.positions.getPosition(node.minIndex)
+ this.positions.getItemSize(node.minIndex) / 2;
var max = this.positions.getPosition(node.maxIndex)
+ this.positions.getItemSize(node.maxIndex) / 2;
return [this.scale(node.height), (min + max) / 2];
},
drawPathFromNodeToParent: function (context, node) {
var pix = this.toPix(node);
var parentPix = this.toPix(node.parent);
context.beginPath();
context.moveTo(pix[0], pix[1]);
context.lineTo(parentPix[0], pix[1]);
context.lineTo(parentPix[0], parentPix[1]);
context.stroke();
},
drawNodePath: function (context, node, minIndex, maxIndex) {
var children = node.children;
var left = children[0];
var right = children[1];
// set up points for poly line
var ry = this.toPix(right)[1];
var rx = this.scale(right.height);
var ly = this.toPix(left)[1];
var lx = this.scale(left.height);
var nx = this.scale(node.height);
var x;
var y;
if (!this.drawLeafNodes) {
var leftIsLeaf = left.children !== undefined;
var rightIsLeaf = right.children !== undefined;
if (leftIsLeaf) {
lx = nx + 4;
}
if (rightIsLeaf) {
rx = nx + 4;
}
x = [rx, nx, nx, lx];
y = [ry, ry, ly, ly];
} else {
x = [rx, nx, nx, lx];
y = [ry, ry, ly, ly];
}
context.beginPath();
context.moveTo(x[0], y[0]);
for (var i = 1, length = x.length; i < length; i++) {
context.lineTo(x[i], y[i]);
}
context.stroke();
}
};
phantasus.Util.extend(phantasus.RowDendrogram, phantasus.AbstractDendrogram);
/**
* @param model{phantasus.SelectionModel}
*/
phantasus.ScentedSearch = function (model, positions, isVertical, scrollbar,
heatMap) {
phantasus.AbstractCanvas.call(this, false);
this.model = model;
this.positions = positions;
this.isVertical = isVertical;
this.scrollbar = scrollbar;
this.heatMap = heatMap;
this.searchIndices = [];
scrollbar.decorator = this;
var _this = this;
var mouseMove = function (e) {
var indices = _this.getSearchIndices(e);
document.body.style.cursor = indices.length === 0 ? 'default' : 'pointer';
scrollbar.canvas.style.cursor = indices.length === 0 ? 'default' : 'pointer';
var tipOptions = {
event: e,
heatMapLens: indices.length >= 0
};
if (isVertical) {
heatMap.setToolTip(indices.length >= 0 ? indices : null,
-1, tipOptions);
} else {
heatMap.setToolTip(-1, indices.length >= 0 ? indices
: null, tipOptions);
}
};
var mouseExit = function (e) {
// need to set body cursor b/c mouse can be partially on the scroll bar,
// but the canvas cursor has no effect
document.body.style.cursor = 'default';
scrollbar.canvas.style.cursor = 'default';
heatMap.setToolTip(-1, -1, {event: e});
};
$(scrollbar.canvas).on('mousemove.phantasus', mouseMove).on('mouseout.phantasus', mouseExit);
};
phantasus.ScentedSearch.LINE_HEIGHT = 3.5;
phantasus.ScentedSearch.prototype = {
mouseMovedIndex: -1,
getIndex: function (event) {
var pix = phantasus.CanvasUtil.getMousePos(event.target, event);
var val = pix[this.isVertical ? 'y' : 'x'];
return this.getIndexForPix(val);
},
getSearchIndices: function (event) {
var pix = phantasus.CanvasUtil.getMousePos(event.target, event);
var val = pix[this.isVertical ? 'y' : 'x'];
return this.getSearchIndicesForPix(val);
},
getSearchIndicesForPix: function (pix) {
var indices = this.searchIndices;
if (indices == null) {
return [];
}
var scale = this.scale;
var tolerance = phantasus.ScentedSearch.LINE_HEIGHT;
var matches = [];
for (var i = 0, length = indices.length; i < length; i++) {
var midVal = this.positions.getPosition(indices[i]) * scale;
if (Math.abs(midVal - pix) <= tolerance) {
matches.push(indices[i]);
}
}
return matches;
},
getIndexForPix: function (pix) {
var indices = this.searchIndices;
if (indices == null) {
return -1;
}
var tolerance = phantasus.ScentedSearch.LINE_HEIGHT;
if (this.mouseMovedIndex > 0) {
var midVal = this.positions
.getPosition(indices[this.mouseMovedIndex])
* scale;
if (Math.abs(midVal - pix) <= tolerance) {
return this.mouseMovedIndex;
}
}
var low = 0;
var scale = this.scale;
var high = indices.length - 1;
while (low <= high) {
var mid = (low + high) >> 1;
var midVal = this.positions.getPosition(indices[mid]) * scale;
var cmp = 0;
if (Math.abs(midVal - pix) <= tolerance) {
cmp = 0;
} else if (midVal < pix) {
cmp = -1; // Neither val is NaN, thisVal is smaller
} else if (midVal > pix) {
cmp = 1; // Neither val is NaN, thisVal is larger
}
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -1; // -(low + 1); // key not found.
},
tap: function (position) {
var val = position[this.isVertical ? 'y' : 'x'];
var index = this.getIndexForPix(val);
this.scrollbar.canvas.style.cursor = index < 0 ? 'default' : 'pointer';
if (index >= 0) {
if (this.isVertical) {
this.heatMap.scrollTop(this.positions
.getPosition(this.searchIndices[index]));
} else {
this.heatMap.scrollLeft(this.positions
.getPosition(this.searchIndices[index]));
}
return true;
}
return false;
},
update: function () {
this.searchIndices = this.model.getViewIndices().values().sort(
function (a, b) {
return a < b ? -1 : 1;
});
},
draw: function (clip, context) {
var width = this.scrollbar.getUnscaledWidth();
var height = this.scrollbar.getUnscaledHeight();
var availableLength = ((this.isVertical ? height : width))
- phantasus.ScentedSearch.LINE_HEIGHT;
this.scale = availableLength
/ (this.positions.getPosition(this.positions.getLength() - 1) + this.positions
.getItemSize(this.positions.getLength() - 1));
context.fillStyle = phantasus.ScentedSearch.TICK_COLOR;
context.lineWidth = 1;
this.drawIndices(context, this.searchIndices);
this.drawHoverMatchingValues(context);
},
drawHoverMatchingValues: function (context) {
var heatmap = this.heatMap;
context.fillStyle = phantasus.ScentedSearch.MATCHING_VALUES_TICK_COLOR;
if (heatmap.mousePositionOptions
&& heatmap.mousePositionOptions.name != null) {
var isColumns = !this.isVertical;
var track = heatmap.getTrack(heatmap.mousePositionOptions.name,
isColumns);
if (track == null) {
return;
}
if (track.settings.highlightMatchingValues) {
var hoverIndex = isColumns ? heatmap.getProject()
.getHoverColumnIndex() : heatmap.getProject()
.getHoverRowIndex();
if (hoverIndex === -1) {
return;
}
var vector = track.getVector();
var value = vector.getValue(hoverIndex);
var valueToModelIndices = track.getFullVector().getProperties()
.get(phantasus.VectorKeys.VALUE_TO_INDICES);
if (!valueToModelIndices) {
var fullVector = track.getFullVector();
valueToModelIndices = phantasus.VectorUtil
.createValueToIndicesMap(fullVector);
fullVector.getProperties().set(
phantasus.VectorKeys.VALUE_TO_INDICES,
valueToModelIndices);
}
var modelIndices = valueToModelIndices.get(value);
if (modelIndices == null) {
// console.log('valueToModelIndices error');
return;
}
var scale = this.scale;
var lineLength = !this.isVertical ? this.scrollbar
.getUnscaledHeight() : this.scrollbar
.getUnscaledWidth();
var isVertical = this.isVertical;
var positions = this.positions;
var project = heatmap.getProject();
for (var i = 0, length = modelIndices.length; i < length; i++) {
var modelIndex = modelIndices[i];
var index = isVertical ? project
.convertModelRowIndexToView(modelIndex) : project
.convertModelColumnIndexToView(modelIndex);
if (index === -1) {
continue;
}
var pix = positions.getPosition(index) * scale;
if (isVertical) {
context.fillRect(0, pix, lineLength,
phantasus.ScentedSearch.LINE_HEIGHT);
} else {
context.fillRect(pix, 0,
phantasus.ScentedSearch.LINE_HEIGHT, lineLength);
}
}
}
}
},
drawIndices: function (context, highlightedIndices) {
var scale = this.scale;
var lineLength = !this.isVertical ? this.scrollbar.getUnscaledHeight()
: this.scrollbar.getUnscaledWidth();
var isVertical = this.isVertical;
var positions = this.positions;
for (var i = 0, length = highlightedIndices.length; i < length; i++) {
var index = highlightedIndices[i];
var pix = positions.getPosition(index) * scale;
if (isVertical) {
context.beginPath();
context.rect(0, pix, lineLength,
phantasus.ScentedSearch.LINE_HEIGHT);
context.fill();
// context.stroke();
} else {
context.beginPath();
context.rect(pix, 0, phantasus.ScentedSearch.LINE_HEIGHT,
lineLength);
context.fill();
//context.stroke();
}
}
}
};
phantasus.Util.extend(phantasus.ScentedSearch, phantasus.AbstractCanvas);
phantasus.ScentedSearch.MATCHING_VALUES_TICK_COLOR = 'black';
phantasus.ScentedSearch.TICK_COLOR = '#3182bd';
phantasus.ScrollBar = function (isVertical) {
phantasus.AbstractCanvas.call(this);
this.isVertical = isVertical;
$(this.canvas).css('border', '1px solid #d8d8d8');
if (isVertical) {
this.setBounds({
width: 12
});
} else {
this.setBounds({
height: 12
});
}
this.field = this.isVertical ? 'y' : 'x';
var that = this;
var mouseMove = function (event) {
if (!phantasus.CanvasUtil.dragging) {
var position = phantasus.CanvasUtil.getMousePos(event.target, event,
true);
var mouseOver = (position[that.field] >= that.thumbPos && position[that.field] <= (that.thumbPos + that.thumbExtent));
if (that.thumbMouseOver !== mouseOver) {
that.thumbMouseOver = mouseOver;
that.repaint();
}
}
};
var mouseExit = function (e) {
if (!phantasus.CanvasUtil.dragging && that.thumbMouseOver) {
that.thumbMouseOver = false;
that.repaint();
}
};
$(this.canvas).on('mousemove', mouseMove).on('mouseout', mouseExit).on(
'mouseenter', mouseMove);
this.hammer = phantasus.Util
.hammer(this.canvas, [this.isVertical ? 'panv' : 'panh', 'tap'])
.on(
'panstart',
this.panstart = function (event) {
var position = phantasus.CanvasUtil.getMousePos(
event.target, event, true);
if (position[that.field] >= that.thumbPos
&& position[that.field] <= (that.thumbPos + that.thumbExtent)) {
that.draggingThumb = true;
that.dragStartThumbPos = that.thumbPos;
} else {
that.draggingThumb = false;
}
})
.on('panend', this.panend = function (event) {
that.draggingThumb = false;
})
.on(
'panmove',
this.panmove = function (event) {
if (that.draggingThumb) {
var position = phantasus.CanvasUtil.getMousePos(
event.target, event);
var thumbPosPix = that.dragStartThumbPos
+ (that.isVertical ? event.deltaY
: event.deltaX);
var f = thumbPosPix
/ (that.visibleExtent - that.thumbExtent);
var value = f * that.maxValue;
// convert pix to value
that.setValue(value, true);
event.preventDefault();
event.srcEvent.stopPropagation();
event.srcEvent.stopImmediatePropagation();
}
})
.on(
'tap doubletap',
this.tap = function (event) {
// ensure not clicked on the thumb
if (!that.draggingThumb) {
var position = phantasus.CanvasUtil.getMousePos(
event.target, event);
if (!that.decorator.tap(position)) {
// scroll up or down by thumbExtent
var thumbExtentToValue = (that.thumbExtent / that.totalExtent)
* that.totalExtent;
that.scrollToTop = position[that.field] < that.thumbPos;
that.setValue(that.scrollToTop ? that.value
- thumbExtentToValue : that.value
+ thumbExtentToValue, true);
}
}
});
};
phantasus.ScrollBar.prototype = {
thumbPos: 0, // the top of the thumb, from 0 to visibleExtent-thumbExtent
thumbExtent: 0,
extent: 0,
value: 0, // from 0 to totalExtent-extent
maxValue: 0, // totalExtent-extent
totalExtent: 0,
visibleExtent: 0,
dragStartThumbPos: 0,
draggingThumb: false,
thumbMouseOver: false,
dispose: function () {
phantasus.AbstractCanvas.prototype.dispose.call(this);
this.hammer.off('panend', this.panend).off('panstart',
this.panstart).off('panmove', this.panmove).off('tap', this.tap).off('doubletap', this.tap);
this.hammer.destroy();
},
draw: function (clip, context) {
var width = this.getUnscaledWidth();
var height = this.getUnscaledHeight();
if (this.visibleExtent === this.totalExtent) {
context.clearRect(0, 0, width, height);
} else {
context.fillStyle = 'rgb(241,241,241)';
context.fillRect(0, 0, width, height);
context.fillStyle = !this.thumbMouseOver ? 'rgb(137,137,137)'
: 'rgb(100,100,100)';
if (this.isVertical) {
context.fillRect(0, this.thumbPos, width, this.thumbExtent);
} else {
context.fillRect(this.thumbPos, 0, this.thumbExtent, height);
}
}
this.decorator.draw(clip, context);
},
setThumbPosFromValue: function () {
// value is thumb top position
var f = this.maxValue == 0 ? 0 : this.value / this.maxValue;
this.thumbPos = f * (this.visibleExtent - this.thumbExtent);
this.thumbPos = Math.max(0, this.thumbPos);
},
getValue: function () {
return this.value;
},
getMaxValue: function () {
return this.maxValue;
},
setValue: function (value, trigger) {
if (isNaN(value)) {
value = 0;
}
if (this.visibleExtent === this.totalExtent) {
value = 0;
}
value = Math.max(value, 0);
value = Math.min(this.maxValue, value);
this.value = value;
this.setThumbPosFromValue();
if (trigger) {
this.trigger('scroll', {value: this.value});
this.repaint();
}
return this.value;
},
setTotalExtent: function (totalExtent) {
this.totalExtent = totalExtent;
this._setRange();
},
getTotalExtent: function () {
return this.totalExtent;
},
getVisibleExtent: function () {
return this.visibleExtent;
},
_setRange: function () {
this.thumbExtent = Math.max(10, this.visibleExtent
* (this.visibleExtent / this.totalExtent));
this.maxValue = this.totalExtent - this.visibleExtent;
this.maxValue = Math.max(0, this.maxValue);
if (this.isVertical) {
this.setBounds({
height: this.visibleExtent
});
} else {
this.setBounds({
width: this.visibleExtent
});
}
},
setExtent: function (visibleExtent, totalExtent, value) {
this.visibleExtent = visibleExtent;
this.totalExtent = totalExtent;
this._setRange();
this.setValue(value, false);
}
};
phantasus.Util.extend(phantasus.ScrollBar, phantasus.AbstractCanvas);
phantasus.Util.extend(phantasus.ScrollBar, phantasus.Events);
phantasus.ShapeChooser = function (options) {
var formBuilder = new phantasus.FormBuilder();
var map = options.map;
var html = ['<select name="valuePicker" class="selectpicker" data-live-search="true">'];
map.forEach(function (val, key) {
html.push('<option');
html.push(' value="');
html.push(key);
html.push('">');
html.push(key);
html.push('</option>');
});
html.push('</select>');
formBuilder.append({
name: 'selected_value',
type: 'custom',
value: html.join('')
});
var shapeField = new phantasus.ShapeField({showNone: true});
formBuilder.append({
style: 'max-width:50px;',
name: 'selected_shape',
type: 'custom',
value: '<div data-name="shape"></div>'
});
shapeField.$el.appendTo(formBuilder.$form.find('[data-name=shape]'));
var $valuePicker = formBuilder.$form.find('[name=valuePicker]');
var selectedVal = $valuePicker.val();
var _this = this;
shapeField.setShapeValue(map.get(selectedVal));
shapeField.on('change', function (e) {
map.set(selectedVal, e.shape);
_this.trigger('change', {
value: selectedVal,
shape: e.shape
});
});
$valuePicker.selectpicker().change(function () {
selectedVal = $valuePicker.val();
shapeField.setShapeValue(map.get(selectedVal));
});
this.$div = formBuilder.$form;
};
phantasus.ShapeChooser.prototype = {};
phantasus.Util.extend(phantasus.ShapeChooser, phantasus.Events);
/**
*
* @param options.shapes Array of shape names or null to use phantasus.VectorShapeModel.SHAPES
* @param options.showNone Whether none should be an option
* @constructor
*/
phantasus.ShapeField = function (options) {
var shapes = options.shapes || phantasus.VectorShapeModel.SHAPES;
var _this = this;
var html = [];
var size2 = 8;
var x = 4;
var y = 4;
html
.push('<div style="margin-bottom:1em;" class="btn-group">');
html
.push(
'<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span data-name="selection"></span> <span class="fa fa-caret-down"></span></button>');
html.push('<ul class="dropdown-menu" role="menu">');
if (options.showNone) {
html.push('<li><a data-name="none" href="#">(None)</a></li>');
}
for (var i = 0; i < shapes.length; i++) {
var context = new C2S(size2 * 2, size2 * 2);
context.translate(4, 4);
phantasus.CanvasUtil.drawShape(context, shapes[i], x, y, size2);
var svg = context.getSerializedSvg();
html.push('<li><a data-name="' + shapes[i] + '" href="#">' + svg
+ '</a></li>');
}
html.push('</ul></div>');
var $el = $(html.join(''));
var $header = $el.find('[data-name=selection]');
$el.on('click', 'li > a', function (e) {
e.preventDefault();
var shape = $(this).data('name');
setShapeValue(shape);
_this.trigger('change', {
shape: shape
});
});
var setShapeValue = function (val) {
if (val === 'none') {
$header.html('(None)');
} else {
var context = new C2S(size2 * 2, size2 * 2);
context.translate(4, 4);
phantasus.CanvasUtil.drawShape(context, val, x, y, size2);
$header.html(context.getSerializedSvg());
}
};
this.setShapeValue = setShapeValue;
this.$el = $el;
};
phantasus.ShapeField.prototype = {};
phantasus.Util.extend(phantasus.ShapeField, phantasus.Events);
phantasus.SortByValuesIndicator = function (project, isVertical, positions) {
phantasus.AbstractCanvas.call(this, true);
this.project = project;
this.isVertical = isVertical;
this.positions = positions;
this.lastPosition = {
start: -1,
end: -1
};
};
phantasus.SortByValuesIndicator.prototype = {
prePaint: function (clip, context) {
var positions = this.positions;
var start = 0;
var end = positions.getLength();
if (!this.isVertical) {
start = phantasus.Positions.getLeft(clip, positions);
end = phantasus.Positions.getRight(clip, positions);
} else {
start = phantasus.Positions.getTop(clip, positions);
end = phantasus.Positions.getBottom(clip, positions);
}
if (this.invalid || start !== this.lastPosition.start
|| end !== this.lastPosition.end) {
this.lastPosition.start = start;
this.lastPosition.end = end;
this.invalid = true;
}
},
draw: function (clip, context) {
var project = this.project;
var isVertical = this.isVertical;
var positions = this.positions;
var sortKeys = isVertical ? project.getColumnSortKeys() : project
.getRowSortKeys();
context.translate(-clip.x, -clip.y);
context.fillStyle = 'black';
context.textBaseline = 'top';
context.textAlign = 'left';
context.font = '8px ' + phantasus.CanvasUtil.getFontFamily(context);
var start = 0;
var end = positions.getLength();
if (!isVertical) {
start = phantasus.Positions.getLeft(clip, positions);
end = phantasus.Positions.getRight(clip, positions);
} else {
start = phantasus.Positions.getTop(clip, positions);
end = phantasus.Positions.getBottom(clip, positions);
}
var arrowWidth = 3;
var arrowHeight = 4;
for (var i = 0; i < sortKeys.length; i++) {
var key = sortKeys[i];
if (key instanceof phantasus.SortByValuesKey) { // are we sorting
// columns by the
// values in a row?
var modelIndices = key.modelIndices;
for (var j = 0; j < modelIndices.length; j++) {
var modelIndex = modelIndices[j];
var view = isVertical ? project
.convertModelRowIndexToView(modelIndex) : project
.convertModelColumnIndexToView(modelIndex);
if (view !== -1 && view >= start && view < end) {
context.save();
var pix = positions.getPosition(view);
var size = positions.getItemSize(view);
if (!isVertical) {
context.translate(pix + size / 2, 0);
} else {
context.translate(2, pix + size / 2);
}
context.beginPath();
// if (!isVertical) {
if (key.getSortOrder() === phantasus.SortKey.SortOrder.ASCENDING) {
// up arrow
context.moveTo(0, 0);
context.lineTo(arrowWidth, arrowHeight);
context.lineTo(-arrowWidth, arrowHeight);
} else if (key.getSortOrder() === phantasus.SortKey.SortOrder.DESCENDING) { // down
// arrow
context.moveTo(0, arrowHeight);
context.lineTo(arrowWidth, 0);
context.lineTo(-arrowWidth, 0);
} else { // diamond
context.moveTo(0, 0);
context.lineTo(arrowWidth, arrowHeight / 2);
context.lineTo(0, arrowHeight);
context.lineTo(-arrowWidth, arrowHeight / 2);
}
// } else {
// if (!ascending) { // left arrow
// context.moveTo(0, 0);
// context.lineTo(arrowWidth, arrowHeight);
// context.lineTo(arrowWidth, -arrowHeight);
// } else {
// context.moveTo(arrowWidth, 0); // right arrow
// context.lineTo(0, arrowHeight);
// context.lineTo(0, -arrowHeight);
// }
// }
context.fill();
// don't indicate sort priority b/c of limited space
// if (sortKeys.length > 1) {
// context.fillText('' + (i + 1), 0, 0);
// }
context.restore();
}
}
}
}
}
};
phantasus.Util.extend(phantasus.SortByValuesIndicator, phantasus.AbstractCanvas);
phantasus.SortDialog = function (project) {
var _this = this;
// choose rows or columns
var $chooserDiv = $('<div class="container-fluid"></div>');
var $div = $('<div class="container-fluid"></div>');
var html = [];
html
.push('<div style="border-bottom:1px solid LightGrey;margin-bottom:20px;" class="row">');
html.push('<form class="form-horizontal" role="form">');
html
.push('<div class="col-xs-2"><label class="control-label">Sort</label></div>');
html.push('<div class="col-xs-5">');
html
.push('<div class="radio"><label><input type="radio" name="rowsOrColumns" value="rows" checked>Rows</label></div>');
html
.push('<div class="radio"><label><input type="radio" name="rowsOrColumns" value="columns">Columns</label></div>');
html.push('</div>');
html.push('</form>');
html.push('</div>');
$chooserDiv.html(html.join(''));
function toggle(isColumns) {
_this.isColumns = isColumns;
var $element = _this.build(project, isColumns);
$div.empty().html($element);
$div.on('click', '[data-name=delete]', function (e) {
var $this = $(this);
e.preventDefault();
$this.closest('div.row').remove();
});
$div.on('click', '[data-name=add]', function (e) {
var $this = $(this);
var level = [];
var $sibling = $this.closest('div.row');
_this.createLevel(level, new phantasus.SortKey('',
phantasus.SortKey.SortOrder.ASCENDING), _this.fields);
$sibling.after($(level.join('')));
e.preventDefault();
});
}
$chooserDiv.on('change', '[name=rowsOrColumns]', function (e) {
var $this = $(this);
toggle($this.val() === 'columns');
});
toggle(false);
var $outer = $('<div></div>');
$chooserDiv.appendTo($outer);
$div.appendTo($outer);
phantasus.FormBuilder
.showOkCancel({
title: 'Sort',
content: $outer,
okCallback: function () {
var $forms = $div.find('form');
var sortBy = $forms.find('[name=sortBy]').map(function () {
return $(this).val();
});
var lockOrder = $forms.find('[name=lockOrder]').map(function () {
return $(this).prop('checked');
});
var sortOrder = $forms.find('[name=sortOrder]:checked')
.map(function () {
return $(this).val();
});
var groupBy = $div.find('[name=groupBy]').val();
var newSortKeys = [];
var modelIndices = _this.isColumns ? project
.getRowSelectionModel().toModelIndices() : project
.getColumnSelectionModel().toModelIndices();
var existingSortKeys = _this.isColumns ? project
.getColumnSortKeys() : project.getRowSortKeys();
for (
var i = 0; i <
existingSortKeys.length; i++) {
// delete existing sort keys that were locked and were deleted by user
if (existingSortKeys[i].isUnlockable()) {
existingSortKeys.splice(i, 1) ;
i--;
}
}
var newSortKeyFields = new phantasus.Set();
for (var i = 0; i < sortBy.length; i++) {
if (!newSortKeyFields.has(sortBy[i])) {// don't add 2x
newSortKeyFields.add(sortBy[i]);
var key = null;if (sortBy[i] === 'selection') {
key =new phantasus.SortByValuesKey(
modelIndices, sortOrder[i],
_this.isColumns);
} else if (sortBy[i] !== '') {
key =new phantasus.SortKey(
sortBy[i], sortOrder[i]);}
if (key != null) {
newSortKeys.push(key);
if (lockOrder[i]) {
key.setLockOrder(1);
}
}
if (key != null) {
newSortKeys.push(key);
if (lockOrder[i]) {
key.setLockOrder(1);
}
}}
}
var newGroupKeys = [];
if (groupBy != null) {
for (var i = 0; i < groupBy.length; i++) {
newGroupKeys.push(new phantasus.SortKey(groupBy[i],
phantasus.SortKey.SortOrder.UNSORTED));
}
}
if (_this.isColumns) {
project.setGroupColumns(newGroupKeys, true);
project.setColumnSortKeys(phantasus.SortKey
.keepExistingSortKeys(newSortKeys, existingSortKeys), true);
} else {
project.setGroupRows(newGroupKeys, true);
project.setRowSortKeys(phantasus.SortKey
.keepExistingSortKeys(newSortKeys, existingSortKeys), true);
}
}
});
};
phantasus.SortDialog.prototype = {
isColumns: false,
build: function (project, isColumns) {
var fields = phantasus.MetadataUtil.getMetadataNames(isColumns ? project
.getFullDataset().getColumnMetadata() : project
.getFullDataset().getRowMetadata());
this.fields = fields;
var html = [];
var sortKeys = isColumns ? project.getColumnSortKeys() : project
.getRowSortKeys();
this.createLevel0(html);
for (var i = 0; i < sortKeys.length; i++) { // add existing keys
if (sortKeys[i].isUnlockable()) {
this.createLevel(html, sortKeys[i], fields);
}
}
// group by
html.push('<div class="row">');
html
.push('<form class="form-horizontal" role="form">');
html.push('<div class="col-xs-2"><label>Group by</label></div>');
html.push('<div class="col-xs-4">');
var groupByKeys = (isColumns ? project.getGroupColumns() : project
.getGroupRows()).map(function (key) {
return key.field;
});
html.push('<select multiple name="groupBy" class="selectpicker form-control">');
_.each(fields, function (field) {
html.push('<option value="' + field + '"');
if (_.indexOf(groupByKeys, field) !== -1) {
html.push(' selected');
}
html.push('>');
html.push(field);
html.push('</option>');
});
html.push('</select>');
html.push('</div>');
html.push('</div>');
var $div = $(html.join(''));
$div.find('.selectpicker').selectpicker({
iconBase: 'fa',
tickIcon: 'fa-check',
style: 'btn-default btn-sm'
});
return $div;
},
createLevel0: function (html) {
html
.push('<div style="border-bottom:1px solid LightGrey;margin-bottom:20px;" class="row">');
html.push('<form class="form-horizontal" role="form">');
html.push('<div class="col-xs-8">');
html.push('<a data-name="add" href="#">Add sort level</a>');
html.push('</div>');
html.push('</form>');
html.push('</div>');
},
createLevel: function (html, key, fields) {
html
.push('<div style="border-bottom:1px solid LightGrey;margin-bottom:20px;" class="row">');
html.push('<form class="form-horizontal" role="form">');
html
.push('<div class="col-xs-2"><label class="control-label">Sort by</label></div>');
html.push('<div class="col-xs-4">');
html.push('<select name="sortBy" class="form-control">');
html.push('<option value=""></option>');
html.push('<option value="selection"'
+ (key instanceof phantasus.SortByValuesKey ? ' selected' : '')
+ '>selection</option>');
_.each(fields, function (field) {
html.push('<option value="' + field + '"');
if (field == key.toString()) {
html.push(' selected');
}
html.push('>');
html.push(field);
html.push('</option>');
});
html.push('</select>');
html.push('</div>');
html.push('<div class="col-xs-5">');
html
.push('<div class="radio"><label><input type="radio" name="sortOrder" value="ascending"'
+ (phantasus.SortKey.SortOrder.ASCENDING == key
.getSortOrder() ? ' checked' : '')
+ '>Ascending</label></div>');
html
.push('<div class="radio"><label><input type="radio" name="sortOrder" value="descending"'
+ (phantasus.SortKey.SortOrder.DESCENDING == key
.getSortOrder() ? ' checked' : '')
+ '>Descending</label></div>');
html.push('</div>');
html.push('<div class="col-xs-1">');
html.push('<a data-name="delete">Delete</a>');
html.push('</div>');
html.push('<div class="col-xs-12">');
html.push('<div class="checkbox"><label><input name="lockOrder" type="checkbox"' + (key.getLockOrder() !== 0 ? ' checked' : '') + '> Lock sort level</label></div>');
html.push('</div>');
html.push('<div class="col-xs-12">');
html.push('<br />');
html.push('<a data-name="add" href="#">Add sort level</a>');
html.push('</div>');
html.push('</form>');
html.push('</div>');
}
};
phantasus.SteppedColorSupplier = function () {
phantasus.AbstractColorSupplier.call(this);
this.hiddenValue = 0;
this.hiddenValues = new phantasus.Set();
this.stepped = true;
};
/**
* Convert value from input data range of input0 to input1 to pixel range of
* pix0, pix1.
*
* @return The converted value.
*/
phantasus.SteppedColorSupplier.linearScale = function (value, input0, input1,
pix0, pix1) {
return (value - input0) / (input1 - input0) * (pix1 - pix0) + pix0;
};
phantasus.SteppedColorSupplier.prototype = {
createInstance: function () {
return new phantasus.SteppedColorSupplier();
},
isStepped: function () {
return true;
},
getHiddenValues: function () {
return this.hiddenValues;
},
getIndexForFraction: function (f) {
var fractions = this.fractions;
if (f <= fractions[0]) {
return 0;
}
if (f >= fractions[fractions.length - 1]) {
return fractions.length - 1;
}
// Intervals exclude right end point and include left end point except
// for the highest interval which includes everything > min
for (var i = 0; i < fractions.length - 1; i++) {
var left = fractions[i];
var right = fractions[i + 1];
if (f >= left && f < right) {
return i;
}
}
return fractions.length - 1;
},
getColor: function (row, column, value) {
if (this.hiddenValues.has(value)) {
value = this.hiddenValue;
}
if (isNaN(value)) {
return this.missingColor;
}
var min = this.min;
var max = this.max;
var colors = this.colors;
if (value <= min) {
return colors[0];
} else if (value >= max) {
return colors[colors.length - 1];
}
var fraction = phantasus.SteppedColorSupplier.linearScale(value, min,
max, 0, 100) / 100;
return colors[this.getIndexForFraction(fraction)];
}
};
phantasus.Util.extend(phantasus.SteppedColorSupplier,
phantasus.AbstractColorSupplier);
/**
* @param options.autohideTabBar
* Whether to autohide the tab bar when only 1 tab showing
* @param options.landingPage Landing page to show when all tabs are closed
*/
phantasus.TabManager = function (options) {
this.options = $.extend({}, {
autohideTabBar: false,
rename: true
}, options);
var _this = this;
this.activeTabObject = null;
this.activeTabId = null;
this.idToTabObject = new phantasus.Map();
this.$nav = $('<ul class="nav nav-tabs compact phantasus-nav"></ul>');
this.$nav.sortable({
containment: 'parent',
axis: 'x',
helper: 'clone',
cancel: 'li:not(.phantasus-sortable)',
items: 'li.phantasus-sortable'
});
this.$nav.sortable('disable');
this.$nav.on('click', 'li > a', function (e) {
var tabId = $(this).data('link');
if (tabId != null) {
e.preventDefault();
if (_this.activeTabId !== tabId) {
$(this).tab('show');
}
}
});
if (this.options.autohideTabBar) {
this.$nav.css('display', 'none');
}
if (options.dropTab) {
var html = [];
html.push('<li class="phantasus-tab-addon dropdown pull-right tabdrop">');
html.push('<div class="btn-group">');
html.push('<button type="button" class="phantasus-drop-tab-toggle btn btn-link' +
' dropdown-toggle"' +
' data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">');
html.push(' <span class="fa fa-angle-double-down"></span>');
html.push('</button>');
html
.push('<ul class="dropdown-menu dropdown-menu-right" role="menu">');
html.push('</ul>');
html.push('</div>');
html.push('</li>');
var $tabDrop = $(html.join(''));
// $tabDrop.css('display', 'none');
var $tabDropMenu = $tabDrop.find('.dropdown-menu');
$tabDrop.appendTo(this.$nav);
var updateDropTab = function () {
var totalWith = _this.$nav.width() - 17; // 17=width of dropdown
var sum = 0;
var tabDropItems = [];
_this.$nav.find('> li').each(function () {
var $li = $(this);
var $a = $li.find('a');
if (!$li.hasClass('phantasus-tab-addon')) {
var title = $a.contents().first().text();
var isActive = $li.hasClass('active');
var href = $a.attr('href');
tabDropItems.push('<li class="' + (isActive ? 'active' : '') + '"><a data-link="' + href.substring(1) + '" data-toggle="tab"' +
' href="' + href + '">' + title + '</a></li>');
sum += $li.outerWidth();
if (sum >= totalWith) {
$li.css('display', 'none');
} else {
$li.css('display', '');
}
}
});
$tabDrop.css('display', tabDropItems.length > 0 ? '' : 'none');
$tabDropMenu.html(tabDropItems.join(''));
};
$tabDrop.css('display', 'none');
this.$nav.on('sortstop', function (event, ui) {
updateDropTab();
});
$(window).on('resize', updateDropTab);
this.$nav.on('remove', function () {
$(window).off('resize', updateDropTab);
});
this.on('add remove rename reorder change', function () {
updateDropTab();
});
}
this.$nav.on('dblclick', 'li > a', function (e) {
e.preventDefault();
var $a = $(this);
var $li = $a.parent('li');
if ($li.hasClass('phantasus-tab-addon')) {
return;
}
_this.rename($a.data('link'));
});
this.$nav.on('contextmenu.phantasus', 'li > a', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
var $a = $(this);
var $li = $a.parent('li');
if ($li.hasClass('phantasus-tab-addon')) {
return;
}
var menuItems = [];
if ($a.data('phantasus-rename') && _this.options.rename) {
menuItems.push({name: 'Rename'});
}
if ($a.data('phantasus-pin')) { // pinned
menuItems.push({name: 'Unpin tab'});
} else {
menuItems.push({name: 'Pin tab'});
}
if (menuItems.length > 0) {
phantasus.Popup.showPopup(menuItems, {
x: e.pageX,
y: e.pageY
}, e.target, function (event, item) {
if (item === 'Rename') {
_this.rename($a.data('link'));
} else if (item === 'Pin tab') {
$a.data('phantasus-pin', true);
$li.removeClass('phantasus-sortable');
$li.detach();
_this.$nav.prepend($li);
$a.find('.close').hide(); // hide close button
_this.$nav.sortable('option', 'items', 'li.phantasus-sortable');
_this.$nav.sortable('refresh');
} else if (item === 'Unpin tab') {
$a.data('phantasus-pin', false);
$li.addClass('phantasus-sortable');
$a.find('.close').show(); // show close button
_this.$nav.sortable('option', 'items', 'li.phantasus-sortable');
_this.$nav.sortable('refresh');
}
});
}
return false;
});
this.$nav.on('click', 'button', function (e) { // close a tab
// remove the link and tab content
e.preventDefault();
var target = $(this).attr('data-target');
if (target != null) {
target = target.substring(1); // remove #
_this.remove(target);
}
});
this.$tabContent = $('<div class="tab-content"></div>');
this.$nav.on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
// triggered when clicking tab
var previous = _this.activeTabId;
_this.activeTabId = $(e.target).data('link');
_this.activeTabObject = _this.idToTabObject.get(_this.activeTabId);
_this.$nav.find('li').removeClass('active');
_this.$nav.find('[data-link=' + _this.activeTabId + ']').each(function () {
$(this).parent().addClass('active');// not added via droptab
});
var scrollTop = document.body.scrollTop;
$('#' + _this.activeTabId).focus();
document.body.scrollTop = scrollTop; // focus can change scroll position
if (_this.adding) {
return;
}
_this.trigger('change', {
tab: _this.activeTabId,
previous: previous
});
});
};
phantasus.TabManager.prototype = {
getTabText: function (id) {
return this.$nav.find('> li > a').filter('a[data-link=' + id + ']').contents().first().text();
},
getTabCount: function () {
return this.idToTabObject.size();
},
setTabText: function (id, text) {
this.$nav.find('> li > a').filter('[data-link=' + id + ']').contents().first()
.replaceWith(text + ' ');
this.idToTabObject.get(id).setName(name);
},
/**
* @param id
* Tab id
* @param task
* @param task.worker
* Optional worker that the task is run in.
* @param task.name
* @param task.tabId
* Tab id for task
*/
addTask: function (task) {
},
removeTask: function (task) {
},
getWidth: function () {
return this.$tabContent.outerWidth() || $(window).width();
},
getActiveTab: function () {
return this.activeTabObject;
},
getActiveTabId: function () {
return this.activeTabId;
},
/**
*
* @param options.object The object that stores the tab content state and has a setName if
* function if rename is true.
* @param options.$el
* the tab element
* @param options.title
* the tab title
* @param options.closeable
* Whether tab can be closed
* @param options.rename
* Whether tab can be renamed
* @param options.focus
* Whether new tab should be focused-note the change event is not
* triggered when true
* @param options.enabled
* Whether new tab is enabled
*
*/
add: function (options) {
this.adding = true;
var id = _.uniqueId('phantasus-tab');
this.idToTabObject.set(id, options.object);
var li = [];
li.push('<li class="phantasus-sortable" role="presentation">');
li.push('<a data-phantasus-rename="' + options.rename
+ '" data-toggle="tab" data-link="' + id + '" href="#' + id + '">');
li.push(options.title);
li.push(' <i style="color:black;"></i>');
if (options.closeable) {
li
.push(' <button style="font-size: 18px;" type="button" class="close"' +
' aria-label="Close"' +
' data-target="#'
+ id
+ '"><span aria-hidden="true">×</span></button>');
}
li.push('</a></li>');
var $link = $(li.join(''));
$link.appendTo(this.$nav);
var $panel = $('<div tabIndex="0" style="outline:0;cursor:default;" role="tabpanel"' +
' class="tab-pane" id="'
+ id + '"></div>');
options.$el.appendTo($panel);
$panel.appendTo(this.$tabContent);
if (options.enabled === false) {
$link.addClass('disabled');
$link.find('a').addClass('btn disabled');
}
if (options.focus) {
// update active tab, but don't fire event
this.$nav.find('> li > a[data-toggle="tab"]:last').tab('show');
this.activeTabId = id;
this.activeTabObject = options.object;
$panel.focus();
}
if (this.options.autohideTabBar) {
this.$nav.css('display', this.idToTabObject.size() > 1 ? ''
: 'none');
}
this.getTabCount() <= 1 ? this.$nav.sortable('disable') : this.$nav.sortable('enable');
this.adding = false;
this.trigger('add');
return {
$panel: $panel,
id: id
};
},
appendTo: function ($target) {
this.$nav.appendTo($target);
this.$tabContent.appendTo($target);
},
remove: function (target) {
if (target === undefined) {
target = this.activeTabId;
}
var obj = this.idToTabObject.remove(target);
$('#' + target).remove(); // remove tab-pane
this.activeTabObject = null;
this._getA(target).parent().remove();
this.$tabContent.find(target).remove();
var $a = this.$nav.find('> li > a[data-toggle="tab"]:last');
if ($a.length === 0) {
// no content
if (this.options.landingPage) {
if (typeof this.options.landingPage === 'function') {
this.options.landingPage().show();
} else {
this.options.landingPage.show();
}
}
}
$a.tab('show');
if (this.options.autohideTabBar) {
this.$nav.css('display', this.idToTabObject.size() > 1 ? ''
: 'none');
}
this.getTabCount() <= 1 ? this.$nav.sortable('disable') : this.$nav.sortable('enable');
if (this.idToTabObject.size() > 0) {
$($a.attr('href')).focus();
}
if (obj != null && obj.onRemove) {
obj.onRemove();
}
this.trigger('remove', {
tab: target
});
},
setOptions: function (options) {
this.options = options;
if (this.options.autohideTabBar) {
this.$nav.css('display', this.idToTabObject.size() > 1 ? ''
: 'none');
}
},
getOptions: function () {
return this.options;
},
setActiveTab: function (id) {
if (id !== this.activeTabId) {
var $a = this._getA(id);
// make sure it's enabled
$a.parent().removeClass('disabled');
$a.removeClass('btn disabled');
$a.tab('show');
var previous = this.activeTabId;
this.activeTabId = id;
this.activeTabObject = this.idToTabObject.get(this.activeTabId);
this.trigger('change', {
tab: this.activeTabId,
previous: previous
});
}
},
/**
*
* @param id
* The tab id
* @param title
* The title (used to show tooltip)
*/
setTabTitle: function (id, title) {
this._getA(id).attr('title', title);
},
setTabName: function (id, name) {
this._getA(id).contents().first().replaceWith(name + ' ');
},
_getA: function (id) {
if (id[0] === '#') {
id = id.substring(1);
}
return this.$nav.find('> li > a[data-link=' + id + ']:first');
},
setTabEnabled: function (id, enabled) {
var $a = this._getA(id);
if (enabled) {
$a.parent().removeClass('disabled');
$a.removeClass('btn disabled');
} else {
$a.parent().addClass('disabled');
$a.addClass('btn disabled');
}
},
getIdToTabObject: function () {
return this.idToTabObject;
},
getTabObject: function (id) {
return this.idToTabObject.get(id);
},
rename: function (id) {
var $a = this._getA(id);
var $li = $a.parent();
if ($li.hasClass('phantasus-tab-addon')) {
return;
}
if (!$a.data('phantasus-rename') || !this.options.rename) {
return;
}
var _this = this;
var builder = new phantasus.FormBuilder();
builder.append({
name: 'name',
type: 'text',
value: $.trim($a.contents().first().text())
});
phantasus.FormBuilder.showOkCancel({
title: 'Rename Tab',
content: builder.$form,
focus: document.activeElement,
okCallback: function () {
var name = $.trim(builder.getValue('name'));
if (name !== '') {
if (_this.activeTabObject != null && _this.activeTabObject.setName) {
_this.activeTabObject.setName(name);
}
$a.contents().first().replaceWith(name + ' ');
_this.trigger('rename');
}
}
});
// edit tab name
}
};
phantasus.Util.extend(phantasus.TabManager, phantasus.Events);
/**
* @param options.$el The jQuery element to render to. Must be in the DOM.
* @param options.items An array of items to display in the table
* @param options.search Whether to create a search widget
* @param options.rowHeader Renderer to call for each row in the table
* @param options.rowHeight Table row height
* @param height: Height in pixels of table. '564px',
* @param options.collapseBreakpoint: 500
* @param options.showHeader: true
* @param options.select: true
* @param options.responsive: true
* @param options.fixedWidth: Fixed table with when responsive is false. '320px'
* @param options.columns An array of column descriptors. Each column can have the properties:
* visible, name, field, renderer
*/
phantasus.Table = function (options) {
options = phantasus.Table.createOptions(options);
this.options = options;
if (!options.width) {
options.width = options.$el.attr('class');
}
var _this = this;
var height = options.height;
var $gridDiv = $('<div class="slick-table'
+ (options.tableClass ? (' ' + options.tableClass) : '')
+ '" style="width:' + options.fixedWidth + ';height:' + height
+ '"></div>');
this.$gridDiv = $gridDiv;
$gridDiv.appendTo(options.$el);
// all columns (including those that are currently not visible */
var columns = options.columns;
this.columns = columns;
var visibleColumns = columns.filter(function (c) {
return c.visible;
});
var grid = new phantasus.Grid({
gridOptions: {
select: options.select,
rowHeight: options.rowHeight,
autoEdit: false,
editable: false,
autoHeight: options.height === 'auto',
enableTextSelectionOnCells: true,
},
$el: $gridDiv,
items: options.items,
columns: visibleColumns
});
this.grid = grid;
this.searchFunction = null;
var searchFilter = {
isEmpty: function () {
return _this.searchFunction == null;
},
init: function () {
},
accept: function (item) {
return _this.searchFunction(item);
}
};
// add empty search filter
this.grid
.getFilter().add(searchFilter);
var $header = $('<div class="slick-table-header"><div name="top"></div><div style="display: inline-block;" name="left" class="pad-bottom-8 pad-top-8"></div><div name="right" class="pull-right pad-bottom-8' +
' pad-top-8"></div></div>');
this.$header = $header;
var $right = $header.find('.pull-right');
if (options.search) {
var tableSearch = new phantasus.TableSearchUI({
$el: $header.find('[name=top]'),
$right: $right
});
tableSearch.setTable(this);
this.tableSearch = tableSearch;
}
if (options.columnPicker && visibleColumns.length !== this.columns.length) {
var select = [];
select
.push('<select data-width="90px" data-selected-text-format="static" title="Columns..." multiple class="pad-left-4 selectpicker show-tick">');
// sort column names
var sortedColumns = this.columns.slice().sort(function (a, b) {
a = a.name.toLowerCase();
b = b.name.toLowerCase();
return (a === b ? 0 : (a < b ? -1 : 1));
});
sortedColumns.forEach(function (c, i) {
select.push('<option value="' + i + '"');
if (c.visible) {
select.push(' selected');
}
select.push('>');
select.push(c.name);
select.push('</option>');
});
select.push('</select>');
var $select = $(select.join(''));
$select.appendTo($right);
$select.selectpicker({
iconBase: 'fa',
tickIcon: 'fa-check',
style: 'btn-default btn-xs'
});
$select.on('change', function () {
var oldColumns = grid.getColumns().map(function (c) {
return c.id;
});
var selectedColumnIndices = $select.val();
visibleColumns = [];
for (var i = 0; i < selectedColumnIndices.length; i++) {
visibleColumns.push(sortedColumns[parseInt(selectedColumnIndices[i])]);
}
var newColumns = visibleColumns.map(function (c) {
return c.id;
});
grid.setColumns(visibleColumns);
if (newColumns.length > oldColumns.length) {
var set = new phantasus.Set();
for (var i = 0; i < newColumns.length; i++) {
set.add(newColumns[i]);
}
for (var i = 0; i < oldColumns.length; i++) {
set.remove(oldColumns[i]);
}
var added = set.values();
grid.setSortColumns([{
columnId: added[0],
sortAsc: true
}]);
}
// if column added, sort by added column
_this.resize();
_this.redraw();
});
}
$header.prependTo(options.$el);
var collapsed = false;
var lastWidth = -1;
var resize = function () {
if (!_this.options.responsive) {
return;
}
var gridWidth = options.$el.width();
if (gridWidth === lastWidth) {
return;
}
lastWidth = gridWidth;
$gridDiv.css('width', gridWidth + 'px');
// if (options.responsiveHeight) {
// var verticalPosition = _this.$gridDiv[0].getBoundingClientRect().top
// + window.pageYOffset;
// $gridDiv.css('height',
// (document.body.clientHeight - verticalPosition) + 'px');
// }
if (!collapsed && gridWidth < options.collapseBreakpoint
&& visibleColumns.length > 1) {
collapsed = true;
$gridDiv.addClass('slick-stacked');
_this.grid.grid.getOptions().rowHeight = (options.collapsedRowHeight ? options.collapsedRowHeight : options.rowHeight)
* visibleColumns.length;
// collapse
_this.grid.grid
.setColumns([{
id: 0,
tooltip: function (item, value) {
var html = [];
for (var i = 0; i < visibleColumns.length; i++) {
var text = visibleColumns[i].tooltip(item, visibleColumns[i]
.getter(item));
if (text != null && text !== '') {
html.push(text);
}
}
return html.join('<br />');
},
collapsed: true,
getter: function (item) {
return item;
},
formatter: function (row, cell, value, columnDef,
dataContext) {
var html = [];
html
.push('<div class="slick-table-wrapper"><div class="slick-cell-wrapper">');
if (options.rowHeader) { // e.g. render checkbox
html.push(options.rowHeader(dataContext));
html.push('<div style="height:4px;"></div>');
}
for (var i = 0; i < visibleColumns.length; i++) {
if (i > 0) {
html.push('<div style="height:4px;"></div>');
}
var c = visibleColumns[i];
html.push(c.name);
html.push(':');
var s = c.renderer(dataContext, c
.getter(dataContext));
html.push(s);
}
html.push('</div></div>');
return html.join('');
},
sortable: false,
name: ''
}]);
$gridDiv.find('.slick-header').hide();
_this.grid.grid.resizeCanvas();
_this.grid.grid.invalidate();
} else if (collapsed && gridWidth >= options.collapseBreakpoint) {
$gridDiv.removeClass('slick-stacked');
collapsed = false;
if (options.showHeader) {
$gridDiv.find('.slick-header').show();
}
_this.grid.grid.getOptions().rowHeight = options.rowHeight;
_this.grid.grid.setColumns(visibleColumns);
_this.grid.grid.resizeCanvas();
if (options.select) {
_this.grid.grid.setSelectedRows(_this.grid.grid
.getSelectedRows());
}
_this.grid.grid.invalidate();
} else {
_this.grid.grid.resizeCanvas();
_this.grid.grid.invalidate();
}
_this.grid.maybeAutoResizeColumns();
};
if (!options.showHeader) {
$gridDiv.find('.slick-header').hide();
}
if (options.responsive) {
$(window).on('resize orientationchange', resize);
$gridDiv.on('remove', function () {
$(window).off('resize', resize);
});
resize();
}
this.resize = resize;
if (visibleColumns.length > 1 && options.items != null
&& options.items.length > 0) {
this.setItems(options.items);
}
if (!$gridDiv.is(':visible')) {
// find 1st parent that is not visible
var $parent = $gridDiv;
var observer = new MutationObserver(function (mutations) {
if (window.getComputedStyle($parent[0]).display !== 'none') {
observer.disconnect();
resize();
}
});
while ($parent.length > 0) {
if (window.getComputedStyle($parent[0]).display === 'none') {
break;
}
$parent = $parent.parent();
}
if ($parent.length > 0) {
observer.observe($parent[0], {
attributes: true,
childList: false,
characterData: false
});
}
}
}
;
phantasus.Table.defaultRenderer = function (item, value) {
if (value == null) {
return '';
} else if (_.isNumber(value)) {
return phantasus.Util.nf(value);
} else if (phantasus.Util.isArray(value)) {
var s = [];
for (var i = 0, length = value.length; i < length; i++) {
if (i > 0) {
s.push(', ');
}
var val = value[i];
s.push(value[i]);
}
return s.join('');
} else {
return '' + value.toString();
}
};
phantasus.Table.prototype = {
toText: function () {
var text = [];
var items = this.getItems();
var columns = this.columns.filter(function (c) {
return c.visible;
});
for (var j = 0; j < columns.length; j++) {
if (j > 0) {
text.push('\t');
}
text.push(columns[j].name);
}
text.push('\n');
for (var i = 0; i < items.length; i++) {
var item = items[i];
for (var j = 0; j < columns.length; j++) {
if (j > 0) {
text.push('\t');
}
var value = columns[j].getter(item);
text.push(phantasus.Util.toString(value));
}
text.push('\n');
}
return text.join('');
},
setHeight: function (height) {
this.options.height = height;
if (height === 'auto') {
this.$gridDiv.css('height', '');
this.grid.grid.getOptions().autoHeight = true;
this.grid.grid.setOptions(this.grid.grid.getOptions());
} else {
this.$gridDiv.css('height', height);
}
this.grid.grid.resizeCanvas();
if (height === 'auto') {
var height = this.getItems().length * this.options.rowHeight
+ this.options.rowHeight;
this.$gridDiv.find('.slick-viewport').css('height', height + 'px');
}
this.grid.grid.invalidate();
},
setSearchVisible: function (visible) {
this.$header.find('[name=search]').css('display', visible ? '' : 'none');
},
autocomplete: function (tokens, response) {
var matches = [];
var token = tokens != null && tokens.length > 0 ? tokens[tokens.selectionStartIndex]
: '';
token = $.trim(token);
var columns = this.columns.filter(function (c) {
return (c.searchable && c.visible) || c.alwaysSearch;
});
var ncolumns = columns.length;
var showField = ncolumns > 1;
if (token === '') {
if (ncolumns <= 1) {
return response(matches);
}
for (var i = 0; i < ncolumns; i++) {
var field = columns[i].name;
matches.push({
value: field + ':',
label: '<span style="font-weight:300;">' + field
+ ':</span>',
show: true
});
// show column names
}
matches
.sort(function (a, b) {
return (a.value === b.value ? 0
: (a.value < b.value ? -1 : 1));
});
return response(matches);
}
var field = null;
var semi = token.indexOf(':');
var regex = new RegExp('^' + phantasus.Util.escapeRegex(token), 'i');
if (semi > 0) { // field search?
if (token.charCodeAt(semi - 1) !== 92) { // \:
var possibleField = $.trim(token.substring(0, semi));
if (possibleField.length > 0 && possibleField[0] === '"'
&& possibleField[token.length - 1] === '"') {
possibleField = possibleField.substring(1,
possibleField.length - 1);
}
var columnNameToColumn = new phantasus.Map();
var columnNames = columns.map(function (c) {
return c.name;
});
for (var i = 0; i < columnNames.length; i++) {
columnNameToColumn.set(columnNames[i], columns[i]);
}
var c = columnNameToColumn.get(possibleField);
if (c !== undefined) {
token = $.trim(token.substring(semi + 1));
columns = [c];
ncolumns = 1;
}
}
} else if (ncolumns > 1) {
var regex = new RegExp('^' + phantasus.Util.escapeRegex(token), 'i');
for (var j = 0; j < ncolumns; j++) {
var field = columns[j].name;
if (regex.test(field)) {
matches.push({
value: field + ':',
label: '<span style="font-weight:300;">' + field
+ ':</span>',
show: true
});
}
}
}
var set = new phantasus.Set();
var regex = new RegExp('^' + phantasus.Util.escapeRegex(token), 'i');
var items = this.getItems();
var dataTypes = [];
// filter numeric columns
var filteredColumns = [];
columns.forEach(function (c) {
var dataType = null;
for (var i = 0, nitems = items.length; i < nitems; i++) {
var value = c.getter(items[i]);
if (value != null) {
dataType = phantasus.Util.getDataType(value);
break;
}
}
if (dataType === 'string' || dataType === '[string]') {
dataTypes.push(dataType);
filteredColumns.push(c);
}
});
columns = filteredColumns;
ncolumns = columns.length;
var maxSize = matches.length + 10;
for (var i = 0, nitems = items.length; i < nitems; i++) {
var item = items[i];
for (var j = 0; j < ncolumns; j++) {
var field = columns[j].name;
var value = columns[j].getter(item);
var dataType = dataTypes[j];
if (dataType === '[string]') {
var nvalues = value == null ? 0 : value.length;
for (var k = 0; k < nvalues; k++) {
var val = value[k];
if (regex.test(val) && !set.has(val)) {
set.add(val);
matches
.push({
value: showField ? (field + ':' + val)
: val,
label: showField ? ('<span style="font-weight:300;">'
+ field
+ ':</span>'
+ '<span style="font-weight:900;">'
+ val + '</span>')
: ('<span style="font-weight:900;">'
+ val + '</span>')
});
}
if (matches.length === maxSize) {
return response(matches);
}
}
} else {
if (regex.test(value) && !set.has(value)) {
set.add(value);
matches
.push({
value: showField ? (field + ':' + value)
: value,
label: showField ? ('<span style="font-weight:300;">'
+ field
+ ':</span>'
+ '<span style="font-weight:900;">'
+ value + '</span>')
: ('<span style="font-weight:900;">'
+ value + '</span>')
});
if (matches.length === maxSize) {
return response(matches);
}
}
}
}
}
return response(matches);
},
searchWithPredicates: function (predicates) {
if (predicates == null || predicates.length === 0) {
this.searchFunction = null;
this.grid
.setFilter(this.grid
.getFilter());
return;
}
var columns = this.columns.filter(function (c) {
return (c.searchable && c.visible) || c.alwaysSearch;
});
var columnNameToColumn = new phantasus.Map();
var columnNames = columns.map(function (c) {
return c.name;
});
for (var i = 0; i < columnNames.length; i++) {
columnNameToColumn.set(columnNames[i], columns[i]);
}
var filteredPredicates = [];
var npredicates = predicates.length;
for (var i = 0; i < npredicates; i++) {
var predicate = predicates[i];
var filterColumnName = predicate.getField();
if (filterColumnName != null) {
var column = columnNameToColumn.get(filterColumnName);
if (column) {
predicate.column = column;
filteredPredicates.push(predicate);
}
} else {
filteredPredicates.push(predicate);
}
}
predicates = filteredPredicates;
npredicates = predicates.length;
var f = function (item) {
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
var searchColumns;
if (predicate.column) {
searchColumns = [predicate.column];
} else {
searchColumns = columns;
}
for (var j = 0, ncolumns = searchColumns.length; j < ncolumns; j++) {
var value = searchColumns[j].getter(item);
if (phantasus.Util.isArray(value)) {
var nvalues = value.length;
for (var i = 0; i < nvalues; i++) {
if (predicate.accept(value[i])) {
return true;
}
}
} else {
var predicate = predicates[p];
if (predicate.accept(value)) {
return true;
}
}
}
}
return false;
};
this.searchFunction = f;
this.grid
.setFilter(this.grid
.getFilter());
},
search: function (text) {
if (text === '') {
this.searchFunction = null;
this.grid
.setFilter(this.grid
.getFilter());
} else {
var tokens = phantasus.Util.getAutocompleteTokens(text);
var columns = this.columns.filter(function (c) {
return (c.searchable && c.visible) || c.alwaysSearch;
});
var columnNames = columns.map(function (c) {
return c.name;
});
var predicates = phantasus.Util.createSearchPredicates({
tokens: tokens,
fields: columnNames
});
this.searchWithPredicates(predicates);
}
},
getSelectedRows: function () {
return this.grid.getSelectedRows();
},
getSelectedItems: function () {
return this.grid.getSelectedItems();
},
getSelectedItem: function () {
return this.grid.getSelectedItem();
},
setSelectedRows: function (rows) {
this.grid.setSelectedRows(rows);
},
getItems: function (items) {
return this.grid.getItems();
},
getAllItemCount: function () {
return this.grid.getAllItemCount();
},
getFilteredItemCount: function () {
return this.grid.getFilteredItemCount();
},
setFilter: function (f) {
this.grid.setFilter(f);
},
getFilter: function () {
return this.grid.getFilter();
},
setItems: function (items) {
this.grid.setItems(items);
this.grid.redraw();
// TODO update height?
},
redraw: function () {
this.grid.redraw();
},
/**
* @param evtStr
* selectionChanged
*/
on: function (evtStr, handler) {
this.grid.on(evtStr, handler);
return this;
},
off: function (evtStr, handler) {
this.grid.off(evtStr, handler);
return this;
},
trigger: function (evtStr) {
this.grid.trigger(evtStr);
}
};
phantasus.Table.createOptions = function (options) {
options = $.extend(true, {}, {
items: [],
height: '564px',
collapseBreakpoint: 400,
showHeader: true,
select: true,
rowHeader: null,
responsive: true,
fixedWidth: '320px',
columnPicker: true
}, options);
if (!options.columns) {
options.columns = [{
name: ''
}];
}
var columns = [];
options.columns.forEach(function (c, i) {
var column = $.extend(true, {}, {
id: i,
tooltip: function (dataContext, value) {
return phantasus.Table.defaultRenderer(dataContext, value);
},
formatter: function (row, cell, value, columnDef, dataContext) {
var html = [];
html.push('<div class="slick-table-wrapper"><div class="slick-cell-wrapper">');
if (options.rowHeader && cell === 0) {
html.push(options.rowHeader(dataContext));
}
html.push(column.renderer(dataContext, value));
html.push('</div></div>');
return html.join('');
},
comparator: function (a, b) {
var aNaN = (a == null || _.isNumber(a) && isNaN(a));
var bNaN = (b == null || _.isNumber(b) && isNaN(b));
if (aNaN && bNaN) {
return 0;
}
if (aNaN) {
return 1;
}
if (bNaN) {
return -1;
}
if (a.toLowerCase) {
a = a.toLowerCase();
}
if (b.toLowerCase) {
b = b.toLowerCase();
}
return (a === b ? 0 : (a < b ? -1 : 1));
},
sortable: true,
searchable: true,
width: null,
name: c.name,
renderer: phantasus.Table.defaultRenderer
}, c);
if (column.visible === undefined) {
column.visible = true;
}
if (!column.getter) {
column.getter = column.field == null ? function (item) {
return item;
} : function (item) {
return item[c.field];
};
}
columns.push(column);
});
options.columns = columns;
if (options.columns.length === 1) {
options.tableClass = 'slick-table-compact';
} else {
options.tableClass = 'slick-bordered-table';
}
if (!options.rowHeight) {
// options.rowHeight = options.tableClass === 'slick-table-compact' ? 18
// : 20;
options.rowHeight = 22;
}
return options;
};
phantasus.TableSearchUI = function (options) {
var _this = this;
var $search = $('<input name="search" type="text" class="form-control input-sm"' +
' placeholder="Search" autocomplete="off">');
$search.appendTo(options.$el);
this.$search = $search;
this.$searchResults = $('<span class="pad-top-2 tableview-rowcount" name="search"></span>');
this.$showAll = $('<div style="display:inline-block;min-width:60px;" name="search" class="pad-left-8 text-button-copy tableview-rowcount">Show' +
' all</div>');
this.$searchResults.appendTo(options.$right);
this.$showAll.appendTo(options.$right);
this.$showAll.on('click', function (e) {
e.preventDefault();
$search.val('');
_this.table.search('');
_this.table.trigger('showAll', {table: _this.table});
});
$search.on('keyup', _.debounce(function () {
_this.table.search($.trim($(this).val()));
}, 100));
phantasus.Util.autosuggest({
$el: $search,
suggestWhenEmpty: true,
filter: function (tokens, response) {
_this.table.autocomplete(tokens, response);
},
select: function () {
_this.table.search($.trim($search.val()));
}
});
};
phantasus.TableSearchUI.prototype = {
updateSearchLabel: function () {
var text = 'Showing: ' + phantasus.Util.intFormat(this.table.getFilteredItemCount()) + ' / ' + phantasus.Util.intFormat(this.table.getAllItemCount());
this.$searchResults.html(text);
},
setTable: function (table) {
this.table = table;
var _this = this;
table.on('filter', function () {
_this.updateSearchLabel();
});
}
};
phantasus.TrackSelection = function (track, positions, selectionModel, isColumns,
heatMap) {
var canvas = track.canvas;
var startIndex = -1;
var coord = isColumns ? 'x' : 'y';
function getPosition(event, useDelta) {
if (track.settings.squished) {
var total = positions.getPosition(positions.getLength() - 1)
+ positions.getItemSize(positions.getLength() - 1);
var squishFactor = total
/ (isColumns ? track.getUnscaledWidth() : track
.getUnscaledHeight());
var clientXY = phantasus.CanvasUtil.getClientXY(event, useDelta);
var p = phantasus.CanvasUtil.getMousePosWithScroll(event.target,
event, 0, 0, useDelta);
p[coord] *= squishFactor;
return p;
} else {
return phantasus.CanvasUtil.getMousePosWithScroll(event.target,
event, heatMap.scrollLeft(), heatMap.scrollTop(),
useDelta);
}
}
var panning = false;
var scrollIntervalId;
var lastScrollTime = new Date().getTime();
var _this = this;
var throttlePanMove = 50;
function mouseleave(e) {
// listen for mouse hold events
var scroll = function () {
var now = new Date().getTime();
var rect = canvas.getBoundingClientRect();
var doPan = false;
if (!isColumns) {
if (e.clientY > rect.bottom || e.clientY < rect.top) {
doPan = true;
}
} else {
if (e.clientX > rect.right || e.clientX < rect.left) {
doPan = true;
}
}
if (doPan) {
_this.panmove(e);
scrollIntervalId = setTimeout(scroll, throttlePanMove);
}
};
scrollIntervalId = setTimeout(scroll, throttlePanMove);
$(canvas).one('mouseover', mouseover);
}
function mouseover() {
// on mouse exit, see if mouse held
// on mouse enter, stop listening
clearTimeout(scrollIntervalId);
$(canvas).one('mouseleave', mouseleave);
}
this.hammer = phantasus.Util
.hammer(canvas, ['pan', 'tap', 'longpress'])
.on('longpress', this.longpress = function (event) {
event.preventDefault();
event.srcEvent.stopImmediatePropagation();
event.srcEvent.stopPropagation();
heatMap.setSelectedTrack(track.name, isColumns);
track.showPopup(event.srcEvent);
}).on('panend', this.panend = function (event) {
panning = false;
clearInterval(scrollIntervalId);
$(canvas).off('mouseover mouseleave');
})
.on(
'panmove',
this.panmove = function (event) {
var now = new Date().getTime();
var elapsed = now - lastScrollTime;
if (elapsed < throttlePanMove) {
return;
}
var position = getPosition(event);
var endIndex = positions.getIndex(position[coord],
false);
var commandKey = (event.srcEvent == null) ? false : (phantasus.Util.IS_MAC ? event.srcEvent.metaKey
: event.srcEvent.ctrlKey);
var viewIndices = commandKey ? selectionModel
.getViewIndices() : new phantasus.Set();
var _startIndex = startIndex;
if (startIndex > endIndex) {
var tmp = endIndex;
endIndex = _startIndex;
_startIndex = tmp;
}
for (var i = _startIndex; i <= endIndex; i++) {
viewIndices.add(i);
}
selectionModel.setViewIndices(viewIndices, true);
if (!isColumns) {
var scrollTop = heatMap.scrollTop();
var heatMapHeight = heatMap.heatmap.getUnscaledHeight();
var scrollBottom = scrollTop + heatMapHeight;
if (position.y > scrollBottom) { // scroll down
heatMap.scrollTop(position.y + 8 - heatMapHeight);
} else if (position.y < scrollTop) {
heatMap.scrollTop(position.y - 8);
}
} else {
var scrollLeft = heatMap.scrollLeft();
var heatMapWidth = heatMap.heatmap.getUnscaledWidth();
var scrollRight = scrollLeft + heatMapWidth;
if (position.x > scrollRight) {
heatMap.scrollLeft(position.x + 8 - heatMapWidth);
} else if (position.x < scrollLeft) {
heatMap.scrollLeft(position.x - 8);
}
}
event.preventDefault();
if (event.srcEvent != null) {
event.srcEvent.stopPropagation();
event.srcEvent.stopImmediatePropagation();
}
lastScrollTime = new Date().getTime();
})
.on('panstart', this.panstart = function (event) {
heatMap.setSelectedTrack(track.name, isColumns);
var position = getPosition(event, true);
startIndex = positions.getIndex(position[coord], false);
panning = true;
$(canvas).one('mouseleave.phantasus', mouseleave);
})
.on(
'tap doubletap',
this.tap = function (event) {
var position = getPosition(event);
var index = positions.getIndex(position[coord], false);
if (event.tapCount > 1) {
if ((isColumns && !heatMap.options.columnsSortable)
|| (!isColumns && !heatMap.options.rowsSortable)) {
return;
}
heatMap.sortBasedOnSelection(null, isColumns,
event.srcEvent.shiftKey);
} else {
heatMap.setSelectedTrack(track.name, isColumns);
var commandKey = phantasus.Util.IS_MAC ? event.srcEvent.metaKey
: event.srcEvent.ctrlKey;
if (phantasus.Util.IS_MAC && event.srcEvent.ctrlKey) { // right-click
// on
// Mac
return;
}
var viewIndices;
if (commandKey) { // toggle selection
viewIndices = selectionModel.getViewIndices();
if (viewIndices.has(index)) {
viewIndices.remove(index);
} else {
viewIndices.add(index);
}
} else if (event.srcEvent.shiftKey) { // add to
// selection
viewIndices = selectionModel.getViewIndices();
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
viewIndices.forEach(function (viewIndex) {
min = Math.min(viewIndex, min);
max = Math.max(viewIndex, max);
});
if (index >= max) { // select from index to max
for (var i = max; i <= index; i++) {
viewIndices.add(i);
}
} else {// select from index to min
for (var i = Math.min(index, min), max = Math
.max(index, min); i <= max; i++) {
viewIndices.add(i);
}
}
} else {
viewIndices = new phantasus.Set();
viewIndices.add(index);
}
selectionModel.setViewIndices(viewIndices, true);
}
});
};
phantasus.TrackSelection.prototype = {
dispose: function () {
this.hammer.off('longpress', this.longpress).off('panstart',
this.panstart).off('panmove', this.panmove).off('panend', this.panend).off('tap', this.tap).off('doubletap', this.tap);
this.hammer.destroy();
}
};
phantasus.VectorTrackHeader = function (project, name, isColumns, heatMap) {
phantasus.AbstractCanvas.call(this);
this.font = {weight: '400'};
this.project = project;
this.name = name;
this.isColumns = isColumns;
var canvas = this.canvas;
this.heatMap = heatMap;
this.selectedBackgroundColor = '#c8c8c8';
this.backgroundColor = 'rgb(255,255,255)';
var vector = (isColumns ? project.getFullDataset().getColumnMetadata()
: project.getFullDataset().getRowMetadata()).getByName(name);
if (vector && vector.getProperties().has(phantasus.VectorKeys.TITLE)) {
this.canvas.setAttribute('title', vector.getProperties().get(
phantasus.VectorKeys.TITLE));
$(this.canvas).tooltip();
}
var _this = this;
this.setBounds({
height: this.defaultFontHeight
+ phantasus.VectorTrackHeader.FONT_OFFSET
});
function getResizeCursor(pos) {
if (isColumns) {
if (pos.y < 3) {
return {
cursor: 'ns-resize',
isPrevious: true
};
}
if (pos.y >= (_this.getUnscaledHeight() - 3)) {
return {
cursor: 'ns-resize',
isPrevious: false
};
}
if (pos.x >= (_this.getUnscaledWidth() - 3)) { // change change column width
return {
isPrevious: false,
cursor: 'ew-resize'
};
}
} else {
if (pos.x < 3 && heatMap.getTrackIndex(name, isColumns) > 0) { // can't drag left on 1st
// row header
return {
cursor: 'ew-resize',
isPrevious: true
};
}
if (pos.x >= (_this.getUnscaledWidth() - 3)) {
return {
cursor: 'ew-resize',
isPrevious: false
};
}
}
}
var mouseMove = function (event) {
if (!phantasus.CanvasUtil.dragging) {
var pos = phantasus.CanvasUtil.getMousePos(event.target, event);
var resizeCursor = getResizeCursor(pos);
canvas.style.cursor = resizeCursor == null ? 'default' : resizeCursor.cursor;
//document.body.style.cursor = !cursor ? 'default' : cursor;
_this.isMouseOver = true;
_this.repaint();
}
};
var mouseExit = function (e) {
if (!phantasus.CanvasUtil.dragging) {
canvas.style.cursor = 'default';
}
_this.isMouseOver = false;
_this.repaint();
};
var showPopup = function (e) {
heatMap.setSelectedTrack(_this.name, isColumns);
var track = heatMap.getTrack(_this.name, isColumns);
if (!track) {
throw _this.name + ' track not found';
}
e.preventDefault();
if (e.stopPropagation) {
e.stopPropagation();
}
if (e.stopImmediatePropagation) {
e.stopImmediatePropagation();
}
track.showPopup(e, true);
return false;
};
$(this.canvas).css({'background-color': this.backgroundColor}).on(
'mousemove.phantasus', mouseMove).on('mouseout.phantasus', mouseExit)
.on('mouseenter.phantasus', mouseMove).on('contextmenu.phantasus', showPopup).addClass('phantasus-track-header ' + (isColumns ? 'phantasus-columns' : 'phantasus-rows'));
var resizeCursor;
var dragStartWidth = 0;
var dragStartHeight = 0;
var reorderingTrack = false;
var dragStartPosition;
var resizeTrackName;
// var throttled = _.throttle(function(event) {
//
// if (event.type === 'mouseout') {
// } else {
// }
// }, 100);
// $(canvas).on('mouseout', throttled).on('mousemove', throttled);
this.hammer = phantasus.Util
.hammer(canvas, ['pan', 'tap', 'longpress'])
.on('longpress', this.longpress = function (event) {
event.preventDefault();
heatMap.setSelectedTrack(_this.name, isColumns);
var track = heatMap.getTrack(_this.name, isColumns);
track.showPopup(event.srcEvent, true);
})
.on(
'panend',
this.panend = function (event) {
_this.isMouseOver = false;
phantasus.CanvasUtil.dragging = false;
canvas.style.cursor = 'default';
var index = heatMap.getTrackIndex(_this.name,
isColumns);
var header = heatMap.getTrackHeaderByIndex(index,
isColumns);
var track = heatMap
.getTrackByIndex(index, isColumns);
var $canvas = $(track.canvas);
$canvas.css('z-index', '0');
$(header.canvas).css('z-index', '0');
heatMap.revalidate();
})
.on(
'panstart',
this.panstart = function (event) {
_this.isMouseOver = false;
if (phantasus.CanvasUtil.dragging) {
return;
}
if (resizeCursor != null) { // resize
phantasus.CanvasUtil.dragging = true;
canvas.style.cursor = resizeCursor.cursor;
if (resizeCursor.isPrevious) {
var index = heatMap.getTrackIndex(_this.name,
isColumns);
index--; // FIXME index = -1
if (index === -1) {
index = 0;
}
var header = heatMap.getTrackHeaderByIndex(
index, isColumns);
dragStartWidth = header.getUnscaledWidth();
dragStartHeight = header.getUnscaledHeight();
resizeTrackName = header.name;
} else {
resizeTrackName = null;
dragStartWidth = _this.getUnscaledWidth();
dragStartHeight = _this.getUnscaledHeight();
}
event.preventDefault();
reorderingTrack = false;
} else { // move track
var index = heatMap.getTrackIndex(_this.name,
isColumns);
if (index == -1) {
throw _this.name + ' not found';
}
var header = heatMap.getTrackHeaderByIndex(
index, isColumns);
var track = heatMap.getTrackByIndex(index,
isColumns);
heatMap.setSelectedTrack(_this.name, isColumns);
var $canvas = $(track.canvas);
dragStartPosition = $canvas.position();
$canvas.css('z-index', '100');
$(header.canvas).css('z-index', '100');
phantasus.CanvasUtil.dragging = true;
resizeCursor = undefined;
reorderingTrack = true;
}
})
.on(
'panmove',
this.panmove = function (event) {
_this.isMouseOver = false;
if (resizeCursor != null) {
var width;
var height;
if (resizeCursor.cursor === 'ew-resize') {
var dx = event.deltaX;
width = Math.max(8, dragStartWidth + dx);
}
if (resizeCursor.cursor === 'ns-resize') {
var dy = event.deltaY;
height = Math.max(8, dragStartHeight + dy);
}
heatMap.resizeTrack(resizeTrackName == null ? _this.name : resizeTrackName, width, height,
isColumns);
} else if (reorderingTrack) { // reorder
var index = heatMap.getTrackIndex(_this.name,
isColumns);
var header = heatMap.getTrackHeaderByIndex(
index, isColumns);
var track = heatMap.getTrackByIndex(index,
isColumns);
var ntracks = heatMap.getNumTracks(isColumns);
var delta = isColumns ? event.deltaY : event.deltaX;
var newIndex = index + (delta > 0 ? 1 : -1);
newIndex = Math.min(Math.max(0, newIndex),
ntracks - 1);
var prop = isColumns ? 'top' : 'left';
var w = isColumns ? 'getUnscaledHeight'
: 'getUnscaledWidth';
var trackBounds = {};
trackBounds[prop] = dragStartPosition[prop] + delta;
track.setBounds(trackBounds);
header.setBounds(trackBounds);
var dragOverTrack = heatMap.getTrackByIndex(
newIndex, isColumns);
var dragOverWidth = dragOverTrack[w]();
var dragOverLeft = $(dragOverTrack.canvas)
.position()[prop];
var dragleft = dragStartPosition[prop] + delta;
var dragright = dragleft + track[w]();
if ((delta > 0 && dragright >= dragOverLeft
+ dragOverWidth / 2)
|| (delta < 0 && dragleft <= dragOverLeft
+ dragOverWidth / 2)) {
if (index !== newIndex) {
heatMap.moveTrack(index, newIndex,
isColumns);
var otherHeader = heatMap
.getTrackHeaderByIndex(index,
isColumns);
var otherTrack = heatMap
.getTrackByIndex(index, isColumns);
var $movedCanvas = $(otherTrack.canvas);
var newLeft = $movedCanvas.position()[prop];
if (delta < 0) {
newLeft += track[w]();
} else {
newLeft -= track[w]();
}
var otherBounds = {};
otherBounds[prop] = newLeft;
otherTrack.setBounds(otherBounds);
otherHeader.setBounds(otherBounds);
}
}
}
})
.on(
'tap',
this.tap = function (event) {
if (phantasus.Util.IS_MAC && event.srcEvent.ctrlKey) { // right-click
return;
}
_this.isMouseOver = false;
heatMap.setSelectedTrack(_this.name, isColumns);
var vector = (isColumns ? project.getFullDataset().getColumnMetadata()
: project.getFullDataset().getRowMetadata()).getByName(name);
// vector will be null for row #
if ((isColumns && !heatMap.options.columnsSortable) || vector == null) {
return;
}
if ((!isColumns && !heatMap.options.rowsSortable) || vector == null) {
return;
}
var additionalSort = event.srcEvent.shiftKey;
var isGroupBy = false; // event.srcEvent.altKey;
var existingSortKeyIndex = _this
.getSortKeyIndexForColumnName(_this
.getSortKeys(), _this.name);
var sortOrder;
var sortKey;
var dataType = phantasus.VectorUtil.getDataType(vector);
if (existingSortKeyIndex != null) {
sortKey = _this.getSortKeys()[existingSortKeyIndex.index];
if (sortKey.getSortOrder() === phantasus.SortKey.SortOrder.UNSORTED) {
sortOrder = phantasus.SortKey.SortOrder.ASCENDING; // 1st
// click
} else if (sortKey.getSortOrder() === phantasus.SortKey.SortOrder.ASCENDING) {
sortOrder = phantasus.SortKey.SortOrder.DESCENDING; // 2nd
// click
} else if (sortKey.getSortOrder() === phantasus.SortKey.SortOrder.TOP_N) {
sortOrder = phantasus.SortKey.SortOrder.UNSORTED;
} else {
sortOrder = dataType === 'number' || dataType === '[number]' ? phantasus.SortKey.SortOrder.TOP_N : phantasus.SortKey.SortOrder.UNSORTED; // 3rd
// click
}
} else {
sortKey = new phantasus.SortKey(_this.name,
phantasus.SortKey.SortOrder.ASCENDING);
sortOrder = phantasus.SortKey.SortOrder.ASCENDING;
}
if (sortKey != null) {
sortKey.setSortOrder(sortOrder);
_this.setSortingStatus(_this.getSortKeys(),
sortKey, additionalSort, isGroupBy);
}
});
$(this.canvas).on('mousedown', function (event) {
resizeCursor = getResizeCursor(phantasus.CanvasUtil
.getMousePos(event.target, event, true));
});
};
phantasus.VectorTrackHeader.FONT_OFFSET = 2;
phantasus.VectorTrackHeader.prototype = {
selected: false,
isMouseOver: false,
defaultFontHeight: 11,
dispose: function () {
phantasus.AbstractCanvas.prototype.dispose.call(this);
this.hammer.off('longpress', this.longpress).off('panend', this.panend).off('panstart',
this.panstart).off('panmove', this.panmove).off('tap', this.tap);
this.hammer.destroy();
},
getPreferredSize: function () {
var size = this.getPrintSize();
size.width += 24; // leave space for sort, drag icon, lock
if (!this.isColumns) {
size.height = this.defaultFontHeight
+ phantasus.VectorTrackHeader.FONT_OFFSET;
}
// var vector = (this.isColumns ? this.project.getFullDataset()
// .getColumnMetadata() : this.project.getFullDataset()
// .getRowMetadata()).getByName(this.name);
// if (vector
// && vector.getProperties().get(
// phantasus.VectorKeys.SHOW_HEADER_SUMMARY)) {
// if (isNaN(size.height)) {
// size.height = 0;
// }
// if (!this.isColumns) {
// size.height += 50;
// } else {
// size.width += 50;
// }
//
// }
return size;
},
getPrintSize: function () {
var context = this.canvas.getContext('2d');
context.font = this.fontWeight + ' ' + this.defaultFontHeight + 'px '
+ phantasus.CanvasUtil.getFontFamily(context);
var textWidth = 4 + context.measureText(this.name).width;
return {
width: textWidth,
height: this.defaultFontHeight
+ phantasus.VectorTrackHeader.FONT_OFFSET
};
},
getSortKeys: function () {
return this.isColumns ? this.project.getColumnSortKeys() : this.project
.getRowSortKeys();
},
setOrder: function (sortKeys) {
if (this.isColumns) {
this.project.setColumnSortKeys(phantasus.SortKey
.keepExistingSortKeys(sortKeys, this.project
.getColumnSortKeys()), false);
} else {
this.project.setRowSortKeys(phantasus.SortKey.keepExistingSortKeys(
sortKeys, this.project.getRowSortKeys()), false);
}
},
setGroupBy: function (groupBy) {
var existingGroupBy = this.isColumns ? this.project.groupColumns
: this.project.groupRows;
// see if already exists, if so remove it
var index = -1;
for (var i = 0, length = existingGroupBy.length; i < length; i++) {
if (existingGroupBy[i].toString() === groupBy.toString()) {
index = i;
break;
}
}
var newGroupBy = [groupBy];
if (index !== -1) {
newGroupBy = existingGroupBy;
newGroupBy.splice(index, 1);
}
if (this.isColumns) {
this.project.setGroupColumns(newGroupBy, true);
} else {
this.project.setGroupRows(newGroupBy, true);
}
},
setSelected: function (selected) {
if (selected != this.selected) {
this.selected = selected;
$(this.canvas)
.css(
{
'background-color': this.selected ? this.selectedBackgroundColor
: this.backgroundColor
});
}
},
setSortingStatus: function (sortKeys, sortKey, additionalSort, isGroupBy) {
if (!isGroupBy) {
if (sortKey.getSortOrder() == phantasus.SortKey.SortOrder.UNSORTED
&& !additionalSort) {
this.setOrder([]);
} else {
if (additionalSort && sortKeys.length == 0) {
additionalSort = false;
}
if (!additionalSort) {
sortKeys = [sortKey];
} else {
var sortKeyIndex = this.getSortKeyIndexForColumnName(
sortKeys, sortKey.toString());
if (sortKeyIndex === null) { // new sort column
sortKeys.push(sortKey);
} else { // change sort order of existing sort column
sortKeys[sortKeyIndex.index] = sortKey;
}
}
this.setOrder(sortKeys);
}
}
if (isGroupBy) {
this.setGroupBy(sortKey);
} else {
if (this.isColumns) {
this.project.trigger('columnSortOrderChanged');
} else {
this.project.trigger('rowSortOrderChanged');
}
}
},
getSortKeyIndexForColumnName: function (sortKeys, columnName) {
if (sortKeys != null) {
var counter = 0;
for (var i = 0, size = sortKeys.length; i < size; i++) {
if (sortKeys[i].isUnlockable()) {
counter++;
}
if (sortKeys[i] instanceof phantasus.SortKey && columnName === sortKeys[i].toString()) {
return {
index: i,
number: counter
};
}
}
}
return null;
},
print: function (clip, context) {
if (clip.height <= 6) {
return;
}
context.textBaseline = 'bottom';
if (this.isColumns) {
context.textAlign = 'right';
context.font = this.font.weight + ' ' + Math.min(this.defaultFontHeight, clip.height
- phantasus.VectorTrackHeader.FONT_OFFSET)
+ 'px ' + phantasus.CanvasUtil.getFontFamily(context);
} else {
context.textAlign = 'left';
context.font = (clip.height - phantasus.VectorTrackHeader.FONT_OFFSET)
+ 'px ' + phantasus.CanvasUtil.getFontFamily(context);
}
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
context.fillText(this.name, 0, 0);
},
draw: function (clip, context) {
var sortKeys = this.getSortKeys();
var name = this.name;
var existingSortKeyIndex = this.getSortKeyIndexForColumnName(sortKeys,
name);
var unlockableSortKeys = sortKeys.filter(function (key) {
return key.isUnlockable();
});
phantasus.CanvasUtil.resetTransform(context);
context.clearRect(0, 0, this.getUnscaledWidth(), this
.getUnscaledHeight());
if (this.getUnscaledHeight() < 5) {
return;
}
context.strokeStyle = '#ddd';
if (this.isColumns) {
context.beginPath();
context.moveTo(0, this.getUnscaledHeight());
context.lineTo(this.getUnscaledWidth(), this.getUnscaledHeight());
context.stroke();
context.textAlign = 'right';
} else {
context.beginPath();
context.moveTo(this.getUnscaledWidth(), 0);
context.lineTo(this.getUnscaledWidth(), this.getUnscaledHeight());
context.stroke();
context.textAlign = 'left';
}
var textWidth = context.measureText(name).width;
var isColumns = this.isColumns;
var xpix = this.isColumns ? this.getUnscaledWidth() - 2 : 10;
if (isColumns) {
if (existingSortKeyIndex != null) {
xpix -= 6;
if (sortKeys[existingSortKeyIndex.index].getLockOrder() !== 0) {
xpix -= 10;
}
}
if (sortKeys.length > 1) {
xpix -= 6;
}
}
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
var ypix = this.isColumns ? (this.getUnscaledHeight() / 2)
: (this.getUnscaledHeight() - (this.defaultFontHeight + phantasus.VectorTrackHeader.FONT_OFFSET) / 2);
context.textBaseline = 'middle';
if (this.isMouseOver) {
var xdot = xpix - (isColumns ? textWidth + 4 : 4);
var ydot = ypix - 3;
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 3; j++) {
context.fillRect(xdot - i * 3, ydot + j * 3, 1.5, 1.5);
}
}
}
var fontHeight = Math.min(this.defaultFontHeight, this
.getUnscaledHeight()
- phantasus.VectorTrackHeader.FONT_OFFSET);
fontHeight = Math.min(fontHeight, phantasus.VectorTrack.MAX_FONT_SIZE);
context.font = this.font.weight + ' ' + fontHeight + 'px ' + phantasus.CanvasUtil.getFontFamily(context);
context.fillText(name, xpix, ypix);
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
if (existingSortKeyIndex !== null) {
// draw arrow
context.beginPath();
var x = this.isColumns ? xpix + 4 : xpix + textWidth + 6;
var arrowHeight = Math.min(8, this.getUnscaledHeight() / 2 - 1);
var arrowWidth = 3;
if (sortKeys[existingSortKeyIndex.index].getSortOrder() === phantasus.SortKey.SortOrder.ASCENDING) {
// up arrow
context.translate(x, ypix - arrowHeight);
context.moveTo(0, 0);
context.lineTo(arrowWidth, arrowHeight);
context.lineTo(-arrowWidth, arrowHeight);
} else if (sortKeys[existingSortKeyIndex.index].getSortOrder() === phantasus.SortKey.SortOrder.DESCENDING) { // down
// arrow
context.translate(x, ypix);
context.moveTo(0, arrowHeight);
context.lineTo(arrowWidth, 0);
context.lineTo(-arrowWidth, 0);
} else { // diamond
context.translate(x, ypix - arrowHeight / 2);
context.moveTo(0, 0);
context.lineTo(arrowWidth, arrowHeight / 2);
context.lineTo(0, arrowHeight);
context.lineTo(-arrowWidth, arrowHeight / 2);
}
context.fill();
phantasus.CanvasUtil.resetTransform(context);
context.textAlign = 'left';
if (unlockableSortKeys.length > 1) {
context.font = '8px ' + phantasus.CanvasUtil.getFontFamily(context);
var sortIndex = '' + (existingSortKeyIndex.number);
context.fillText(sortIndex, x + 4,
ypix - 2);
x += context.measureText(sortIndex).width;
}
if (sortKeys[existingSortKeyIndex.index].getLockOrder() !== 0) {
context.font = fontHeight + 'px FontAwesome';
context.fillText('\uf023', x + arrowWidth + 2, ypix);
}
}
}
};
phantasus.Util.extend(phantasus.VectorTrackHeader, phantasus.AbstractCanvas);
phantasus.VectorTrack = function (project, name, positions, isColumns, heatmap) {
phantasus.AbstractCanvas.call(this, true);
this.preferredSize = {
width: 0,
height: 0
};
this.project = project;
this.positions = positions;
this.isColumns = isColumns;
this.name = name;
this.visible = true;
this.heatmap = heatmap;
// this.highlightColor = 'rgb(255,255,0)';
this.id = _.uniqueId();
var _this = this;
this.updateSpanMapFunction = function () {
_this.spanMap = phantasus.VectorUtil.createSpanMap(_this.getVector());
};
this.lastPosition = {
start: -1,
end: -1
};
// for molecule span
this.events = 'rowSortOrderChanged rowFilterChanged datasetChanged';
var mouseMoved = function (event) {
var index = -1;
if (event.type !== 'mouseout') {
var position = phantasus.CanvasUtil.getMousePosWithScroll(
event.target, event, heatmap.scrollLeft(), heatmap
.scrollTop());
if (_this.settings.squished) {
var total = positions.getPosition(positions.getLength() - 1)
+ positions.getItemSize(positions.getLength() - 1);
var squishFactor = total
/ (isColumns ? _this.getUnscaledWidth() : _this
.getUnscaledHeight());
position[isColumns ? 'x' : 'y'] *= squishFactor;
}
index = !isColumns ? _this.positions.getIndex(position.y, false)
: _this.positions.getIndex(position.x, false);
}
if (isColumns) {
heatmap.setMousePosition(-1, index, {
name: _this.name,
event: event
});
} else {
heatmap.setMousePosition(index, -1, {
name: _this.name,
event: event
});
}
};
$(this.canvas).on('contextmenu.phantasus', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
heatmap.setSelectedTrack(_this.name, isColumns);
_this.showPopup(e);
return false;
});
// display options:
// - text and color, shape, bar (stacked or not), highlight matching (any
// except arrays)
// - color and bar-discrete or contin
// - color by for bar plots
this.settings = {
maxTextWidth: undefined,
squished: false,
inlineTooltip: false,
tooltip: true,
highlightMatchingValues: false,
colorBarSize: 12,
stackedBar: false,
display: [],
// selectionColor: 'rgb(182,213,253)',
selectionColor: 'rgb(255,125,0)',
colorByField: null, // color this vector by another vector, used in bar plot and text
fontField: null, // use a different field for determining font
barColor: '#bdbdbd',
barSize: 40,
min: undefined,
mid: undefined,
max: undefined,
autoscaleAlways: false, // autoscale on every repaint
minMaxReversed: false
// whether to reverse min and max when auto-setting min and max
};
$(this.canvas).on('mousemove.phantasus mouseout.phantasus', mouseMoved);
};
phantasus.VectorTrack.RENDER = {
TEXT: 'text',
COLOR: 'color',
BAR: 'bar',
MOLECULE: 'molecule',
TEXT_AND_COLOR: 'text_and_color',
TEXT_AND_FONT: 'text_and_font',
SHAPE: 'shape',
ARC: 'arc',
BOX_PLOT: 'box_plot',
HEAT_MAP: 'heat_map'
};
phantasus.VectorTrack.vectorToString = function (vector) {
var dataType = phantasus.VectorUtil.getDataType(vector);
var formatter = vector.getProperties().get(phantasus.VectorKeys.FORMATTER);
if (formatter != null) {
if (typeof formatter === 'object') { // convert to function
formatter = phantasus.Util.createNumberFormat(formatter.pattern);
if (dataType === '[number]') {
var nf = formatter;
formatter = function (v) {
var s = [];
if (v != null) {
for (var i = 0, arrayLength = v.length; i < arrayLength; i++) {
s.push(nf(v[i]));
}
}
return s.join(', ');
};
}
vector.getProperties().set(phantasus.VectorKeys.FORMATTER, formatter);
}
} else if (dataType === 'number') {
formatter = phantasus.Util.nf;
} else if (dataType === '[number]') {
formatter = function (v) {
var s = [];
if (v != null) {
for (var i = 0, arrayLength = v.length; i < arrayLength; i++) {
s.push(phantasus.Util.nf(v[i]));
}
}
return s.join(', ');
};
} else if (dataType === '[string]') {
formatter = function (v) {
var s = [];
if (v != null) {
for (var i = 0, arrayLength = v.length; i < arrayLength; i++) {
s.push(v[i]);
}
}
return s.join(', ');
};
} else {
formatter = function (v) {
return '' + v;
};
}
return formatter;
};
phantasus.VectorTrack.prototype = {
settingFromConfig: function (conf) {
var _this = this;
var settings = this.settings;
// new style= rows:[{field: 'test', display:['text']}]
// old style= rows:[{field: 'test', display:'text,color'}]
var fromString = function (s) {
settings.display = [];
var tokens = s.split(',');
for (var i = 0, length = tokens.length; i < length; i++) {
var method = $.trim(tokens[i]);
method = method.toUpperCase();
var mapped = phantasus.VectorTrack.RENDER[method];
if (mapped !== undefined) {
settings.display.push(mapped);
} else if (method === 'DISCRETE') {
_this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE, true);
} else if (method === 'CONTINUOUS') {
_this.getFullVector().getProperties().set(phantasus.VectorKeys.DISCRETE, false);
} else if (method === 'HIGHLIGHT') {
settings.highlightMatchingValues = true;
} else if (method === 'STACKED_BAR') {
settings.stackedBar = true;
settings.display.push(phantasus.VectorTrack.RENDER.BAR);
} else if (method === 'TOOLTIP') {
settings.inlineTooltip = true;
} else {
// console.log(method + ' not found.');
}
}
};
var fromArray = function (array) {
settings.display = [];
for (var i = 0; i < array.length; i++) {
var method = array[i].toUpperCase();
var mapped = phantasus.VectorTrack.RENDER[method];
if (mapped !== undefined) {
settings.display.push(mapped);
} else {
console.log(method + ' not found.');
}
}
};
var fromObject = function (obj) {
settings.display = [];
for (var key in obj) {
if (obj[key]) {
var method = key.toUpperCase();
var mapped = phantasus.VectorTrack.RENDER[method];
if (mapped !== undefined) {
settings.display.push(mapped);
}
}
}
};
if (conf != null) {
if (_.isString(conf)) { // deprecated, comma separated list of text, color, etc
fromString(conf);
} else if (_.isArray(conf)) {
fromArray(conf);
} else {
var userSuppliedSettings = conf;
if (!_.isArray(conf.display) && _.isObject(conf.display)) { // deprecated
userSuppliedSettings = conf.display;
}
settings = $.extend({}, settings, userSuppliedSettings);
settings.maxTextWidth = undefined;
if (userSuppliedSettings.discrete != null) {
_this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE, userSuppliedSettings.discrete);
}
if (_.isArray(userSuppliedSettings.display)) {
settings.display = userSuppliedSettings.display;
} else if (_.isString(userSuppliedSettings.display)) {
fromString(userSuppliedSettings.display);
}
if (!_.isArray(settings.render) && _.isObject(settings.render)) {// deprecated
fromObject(settings.render);
delete settings.render;
}
}
this.settings = settings;
}
if (!this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT) &&
(this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR) || this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT))) {
settings.display.push(phantasus.VectorTrack.RENDER.TEXT);
}
this._update();
},
setShowTooltip: function (value) {
this.settings.tooltip = value;
},
isShowTooltip: function () {
return this.settings.tooltip;
},
isRenderAs: function (value) {
return this.settings.display.indexOf(value) !== -1;
},
dispose: function () {
phantasus.AbstractCanvas.prototype.dispose.call(this);
$(this.canvas).off();
this._selection.dispose();
this.project.off(this.events, this.updateSpanMapFunction);
},
getName: function () {
return this.name;
},
getVector: function (name) {
name = name == null ? this.name : name;
var vector = this.isColumns ? this.project.getSortedFilteredDataset()
.getColumnMetadata().getByName(name) : this.project
.getSortedFilteredDataset().getRowMetadata().getByName(name);
return !vector ? new phantasus.Vector(name, 0) : vector;
},
getFullVector: function () {
var vector = this.isColumns ? this.project.getFullDataset()
.getColumnMetadata().getByName(this.name) : this.project
.getFullDataset().getRowMetadata().getByName(this.name);
return !vector ? new phantasus.Vector(this.name, 0) : vector;
},
_updatePreferredSize: function () {
var size = this._computePreferredSize();
this.preferredSize.width = size.width;
this.preferredSize.height = size.height;
},
_computePreferredSize: function (forPrint) {
var width = 0;
var height = 0;
phantasus.VectorUtil.getDataType(this.getVector());
if (this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT)) {
if (this.positions.getSize() >= 6) {
var context = this.canvas.getContext('2d');
var textWidth = phantasus.CanvasUtil.getVectorStringWidth(
context, this.getVector(), this.positions,
forPrint ? -1 : (this.isColumns ? 120 : 100));
if (!forPrint) {
this.settings.maxTextWidth = textWidth;
}
width += textWidth;
} else if (!forPrint) {
this.settings.maxTextWidth = 0; // text not drawn
}
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.BAR)) {
width += this.settings.barSize;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)) {
width += this.settings.colorBarSize;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)) {
width += this.settings.colorBarSize;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)) {
width += 300;
}
// if (!forPrint && !this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)) {
// width = Math.min(300, width);
// }
if (this.isRenderAs(phantasus.VectorTrack.RENDER.ARC)) {
width += this.settings.arcSize;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.BOX_PLOT)) {
width += 100;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.HEAT_MAP)) {
width += 100;
}
// 2 pixel spacing between display types
var nkeys = this.settings.display.length;
if (nkeys > 0) {
width += (nkeys - 1) * 2;
}
width = Math.max(0, width);
return this.isColumns ? {
width: height,
height: width
} : {
width: width,
height: height
};
},
getPreferredSize: function () {
return this.preferredSize;
},
getPrintSize: function () {
return this._computePreferredSize(true);
},
_createDiscreteValueMap: function () {
var values = phantasus.VectorUtil.getValues(this.getFullVector());
values.sort(phantasus.SortKey.ASCENDING_COMPARATOR);
this.discreteValueMap = new phantasus.Map();
for (var i = 0, length = values.length; i < length; i++) {
this.discreteValueMap.set(values[i], i + 1);
}
this.settings.min = 0;
this.settings.mid = 0;
this.settings.max = values.length;
},
_setChartMinMax: function () {
if (this.isRenderAs(phantasus.VectorTrack.RENDER.BAR)
|| this.isRenderAs(phantasus.VectorTrack.RENDER.BOX_PLOT)) {
if (!this.settings.stackedBar && this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE)
&& !this.isRenderAs(phantasus.VectorTrack.RENDER.BOX_PLOT)) {
if (!this.discreteValueMap) {
this._createDiscreteValueMap();
}
} else {
if (this.settings.autoscaleAlways || this.settings.min == null || this.settings.max == null
|| this.settings.mid == null) {
var vector = this.getFullVector();
var minMax = phantasus.VectorUtil.getMinMax(vector);
var min = minMax.min;
var max = minMax.max;
if (this.settings.minMaxReversed) {
var tmp = max;
max = min;
min = tmp;
}
if (this.settings.autoscaleAlways || this.settings.min == null) {
this.settings.min = Math.min(0, min);
}
if (this.settings.autoscaleAlways || this.settings.max == null) {
this.settings.max = Math.max(0, max);
}
if (this.settings.autoscaleAlways || this.settings.mid == null) {
this.settings.mid = this.settings.min < 0 ? 0
: this.settings.min;
}
}
}
}
},
_update: function () {
if (this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE) == null
&& (this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR)
|| this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR) || this
.isRenderAs(phantasus.VectorTrack.RENDER.BAR))) {
if ((this.isColumns ? this.project.getColumnColorModel() : this.project.getRowColorModel()).getContinuousColorScheme(this.getFullVector()) != null) {
this.getFullVector().getProperties().set(phantasus.VectorKeys.DISCRETE, false);
this.settings.highlightMatchingValues = false;
} else if ((this.isColumns ? this.project.getColumnColorModel() : this.project.getRowColorModel()).getDiscreteColorScheme(this.getFullVector()) != null) {
this.getFullVector().getProperties().set(phantasus.VectorKeys.DISCRETE, true);
this.settings.highlightMatchingValues = true;
} else if (this.getFullVector().getProperties().has(
phantasus.VectorKeys.FIELDS)
|| phantasus.VectorUtil.getDataType(this.getFullVector()) === 'number' || phantasus.VectorUtil.getDataType(this.getFullVector()) === '[number]') {
this.getFullVector().getProperties().set(phantasus.VectorKeys.DISCRETE, false);
this.settings.highlightMatchingValues = false;
}
}
if (this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE) == null) {
this.getFullVector().getProperties().set(phantasus.VectorKeys.DISCRETE, true);
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)) {
this.project.off(this.events, this.updateSpanMapFunction);
}
this._setChartMinMax();
if (this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)) {
this.project.on(this.events, this.updateSpanMapFunction);
if (!this.moleculeCache) {
this.moleculeCache = {};
var _this = this;
var valueToModelIndices = this.getFullVector().getProperties()
.get(phantasus.VectorKeys.VALUE_TO_INDICES);
if (!valueToModelIndices) {
var fullVector = this.getFullVector();
valueToModelIndices = phantasus.VectorUtil
.createValueToIndicesMap(fullVector);
fullVector.getProperties().set(
phantasus.VectorKeys.VALUE_TO_INDICES,
valueToModelIndices);
}
if (_this.heatmap.options.structureUrlProvider !== undefined) {
valueToModelIndices.forEach(function (indices, value) {
var url = _this.heatmap.options
.structureUrlProvider(value);
var image = new Image();
image.src = url;
_this.moleculeCache[value] = image;
});
setTimeout(function () {
_this.setInvalid(true);
_this.repaint();
}, 2000);
} else {
var values = valueToModelIndices.keys();
var doRequest = function (smile) {
$
.ajax(
{
contentType: 'text/plain',
context: {
smile: smile
},
data: {
'string': smile,
'representation': 'sdf'
},
url: 'http://cactus.nci.nih.gov/chemical/structure'
}).done(function (text) {
_this.moleculeCache[this.smile] = text;
if (values.length > 0) {
doRequest(values.pop());
}
_this.invalid = true;
_this.repaint();
});
};
for (var i = 0; i < 6; i++) {
doRequest(values.pop());
}
}
this.updateSpanMapFunction();
}
}
this._updatePreferredSize();
}
,
postPaint: function (clip, context) {
// draw hover, matching values
context.lineWidth = 1;
context.strokeStyle = 'Grey';
var project = this.project;
var setup = this._setup(context, clip);
var vector = setup.vector;
var start = setup.start;
var end = setup.end;
// hover
if (this.isColumns) {
if (project.getHoverColumnIndex() !== -1) {
this.drawColumnBorder(context, this.positions, project
.getHoverColumnIndex(), this.getUnscaledHeight());
}
} else {
if (project.getHoverRowIndex() !== -1) {
this.drawRowBorder(context, this.positions, project
.getHoverRowIndex(), this.getUnscaledWidth());
}
}
this._highlightMatchingValues(context, vector, start, end);
}
,
_highlightMatchingValues: function (context, viewVector, start, end) {
var project = this.project;
var positions = this.positions;
context.strokeStyle = 'black';
context.lineWidth = 3;
var hoverIndex = this.isColumns ? project.getHoverColumnIndex()
: project.getHoverRowIndex();
var value = viewVector.getValue(hoverIndex);
if (this.settings.highlightMatchingValues
&& hoverIndex !== -1
&& this.heatmap.mousePositionOptions
&& this.heatmap.mousePositionOptions.name === viewVector
.getName()) {
var valueToModelIndices = this.getFullVector().getProperties().get(
phantasus.VectorKeys.VALUE_TO_INDICES);
if (!valueToModelIndices) {
var fullVector = this.getFullVector();
valueToModelIndices = phantasus.VectorUtil
.createValueToIndicesMap(fullVector);
fullVector.getProperties().set(
phantasus.VectorKeys.VALUE_TO_INDICES,
valueToModelIndices);
}
var indices = valueToModelIndices.get(value);
if (indices == null) {
// console.log('valueToModelIndices error');
return;
}
if (indices.length <= 1) {
return;
}
if (this.isColumns) {
if (project.getHoverColumnIndex() !== -1) {
var height = this.getUnscaledHeight();
// context.fillStyle = '#ffffb3';
context.beginPath();
for (var i = 0, nindices = indices.length; i < nindices; i++) {
var viewIndex = project
.convertModelColumnIndexToView(indices[i]);
if (viewIndex >= start && viewIndex < end) {
var size = positions.getItemSize(viewIndex);
var pix = positions.getPosition(viewIndex);
context.rect(pix, 0, size, height);
}
}
context.stroke();
}
} else {
context.beginPath();
var width = this.getUnscaledWidth();
var indices = valueToModelIndices.get(value);
for (var i = 0, nindices = indices.length; i < nindices; i++) {
var viewIndex = project
.convertModelRowIndexToView(indices[i]);
if (viewIndex >= start && viewIndex < end) {
var size = positions.getItemSize(viewIndex);
var pix = positions.getPosition(viewIndex);
context.rect(0, pix, width, size);
}
}
context.stroke();
}
}
}
,
drawSelection: function (options) {
var project = this.project;
var positions = this.positions;
var context = options.context;
var start = options.start;
var end = options.end;
context.lineWidth = 1;
context.fillStyle = this.settings.selectionColor;
if (this.isColumns) {
var height = this.getUnscaledHeight();
var viewIndices = project.getColumnSelectionModel()
.getViewIndices();
viewIndices.forEach(function (i) {
if (i >= start && i <= end) {
var size = positions.getItemSize(i);
var pix = positions.getPosition(i);
context.fillRect(pix, 0, size, height);
}
});
} else {
var width = this.getUnscaledWidth();
if (!this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)) {
var viewIndices = project.getRowSelectionModel()
.getViewIndices();
viewIndices.forEach(function (i) {
if (i >= start && i <= end) {
var size = Math.ceil(positions.getItemSize(i));
var pix = positions.getPosition(i);
context.fillRect(0, pix, width, size);
}
});
}
}
}
,
prePaint: function (clip, context) {
// draw selection
var project = this.project;
var positions = this.positions;
var setup = this._setup(context, clip);
var start = setup.start;
var end = setup.end;
this.drawSelection({
context: context,
start: start,
end: end
});
if (this.invalid || start !== this.lastPosition.start
|| end !== this.lastPosition.end) {
this.lastPosition.start = start;
this.lastPosition.end = end;
this.invalid = true;
}
}
,
drawRowBorder: function (context, positions, index, gridSize) {
var size = positions.getItemSize(index);
var pix = positions.getPosition(index);
// top and bottom lines
context.beginPath();
context.moveTo(0, pix + size);
context.lineTo(gridSize, pix + size);
context.stroke();
context.beginPath();
context.moveTo(0, pix);
context.lineTo(gridSize, pix);
context.stroke();
}
,
drawColumnBorder: function (context, positions, index, gridSize) {
var size = positions.getItemSize(index);
var pix = positions.getPosition(index);
// top and bottom lines
context.beginPath();
context.moveTo(pix + size, 0);
context.lineTo(pix + size, gridSize);
context.stroke();
context.beginPath();
context.moveTo(pix, 0);
context.lineTo(pix, gridSize);
context.stroke();
}
,
isSquished: function () {
return this.settings.squished;
}
,
_setup: function (context, clip) {
var start = 0;
var vector = this.getVector();
var end = vector.size();
var settings = this.settings;
var positions = this.positions;
var width = clip.width;
var height = clip.height;
if (!settings.squished) {
if (this.isColumns) {
start = phantasus.Positions.getLeft(clip, positions);
end = phantasus.Positions.getRight(clip, positions);
} else {
start = phantasus.Positions.getTop(clip, positions);
end = phantasus.Positions.getBottom(clip, positions);
}
}
if (settings.squished) {
var total = positions.getPosition(positions.getLength() - 1)
+ positions.getItemSize(positions.getLength() - 1);
if (!this.isColumns) {
var squishFactor = height / total;
context.scale(1, squishFactor);
} else {
var squishFactor = width / total;
context.scale(squishFactor, 1);
}
} else {
context.translate(-clip.x, -clip.y);
}
return {
start: start,
end: end,
vector: vector
};
}
,
draw: function (clip, context) {
var setup = this._setup(context, clip);
this._draw({
start: setup.start,
end: setup.end,
vector: setup.vector,
context: context,
availableSpace: this.isColumns ? this.getUnscaledHeight()
: this.getUnscaledWidth(),
clip: clip
});
}
,
print: function (clip, context) {
var vector = this.getVector();
this._draw({
start: 0,
end: vector.size(),
vector: vector,
context: context,
availableSpace: this.isColumns ? clip.height
: clip.width,
clip: clip
});
}
,
/**
* @param options.vector
* @param options.context
* @param options.start
* @param options.end
* @param options.availableSpace
*/
_draw: function (options) {
var _this = this;
var context = options.context;
var vector = options.vector;
var availableSpace = options.availableSpace;
var fullAvailableSpace = options.availableSpace;
var start = options.start;
var end = options.end;
var clip = options.clip;
var positions = this.positions;
if (this.settings.autoscaleAlways) {
this._setChartMinMax();
}
context.textAlign = 'left';
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
var fontSize = Math.min(phantasus.VectorTrack.MAX_FONT_SIZE, positions.getSize() - 2);
var size = 0;
context.font = fontSize + 'px ' + phantasus.CanvasUtil.getFontFamily(context);
context.strokeStyle = phantasus.HeatMapElementCanvas.GRID_COLOR;
context.lineWidth = 0.1;
// grid lines
if (this.heatmap.heatmap.isDrawGrid() && !this.settings.squished) {
if (this.isColumns) {
var gridSize = availableSpace;
context.beginPath();
for (var i = start; i < end; i++) {
var size = positions.getItemSize(i);
var pix = positions.getPosition(i);
if (size > 7) {
context.moveTo(pix + size, 0);
context.lineTo(pix + size, gridSize);
}
}
context.stroke();
} else {
if (!this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)) {
var gridSize = availableSpace;
context.beginPath();
for (var i = start; i < end; i++) {
var size = positions.getItemSize(i);
var pix = positions.getPosition(i);
if (size > 7) {
context.moveTo(0, pix + size);
context.lineTo(gridSize, pix + size);
}
}
context.stroke();
}
}
}
context.lineWidth = 1;
var offset = 1;
if (this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)) {
this.renderColor(context, vector, start, end, clip,
this.isColumns ? availableSpace : 0,
!this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE));
offset += this.settings.colorBarSize + 2;
availableSpace -= offset;
}
if (!this.settings.squished
&& this.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)) {
this.renderShape(context, vector, start, end, clip,
this.isColumns ? availableSpace - offset : offset);
offset += this.settings.colorBarSize + 2;
availableSpace -= offset;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.ARC)) {
this.renderArc(context, vector, start, end, clip,
this.settings.arcSize);
offset += this.settings.arcSize + 2;
availableSpace -= offset;
}
if (!this.settings.squished
&& this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)) {
this.renderMolecule(context, vector, start, end, clip, offset,
availableSpace);
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.BOX_PLOT)) {
var barSize = !this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT) ? (availableSpace - 2)
: this.settings.barSize;
offset++;
this.renderBoxPlot(context, vector, start, end, clip, offset,
barSize);
offset += barSize + 2;
availableSpace -= offset;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.HEAT_MAP)) {
var barSize = !this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT) ? (availableSpace - 2)
: this.settings.barSize;
offset++;
this.renderHeatMap(context, vector, start, end, clip,
barSize);
offset += barSize + 2;
availableSpace -= offset;
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.BAR)) {
var barSize = !this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT) ? (availableSpace - 1)
: this.settings.barSize;
if (this.settings.stackedBar) {
this.renderStackedBar(context, vector, start, end, clip,
offset, barSize);
} else {
var fields = vector.getProperties().get(
phantasus.VectorKeys.FIELDS);
var visibleFieldIndices = vector.getProperties().get(
phantasus.VectorKeys.VISIBLE_FIELDS);
if (fields != null && visibleFieldIndices == null) {
visibleFieldIndices = phantasus.Util.seq(fields.length);
}
if (fields != null) {
this.renderUnstackedBar(context, vector, start, end, clip,
offset, barSize, visibleFieldIndices);
} else {
this.renderBar(context, vector, start, end, clip, this.isColumns ? (fullAvailableSpace - offset - barSize) : offset,
barSize);
}
}
offset += barSize + 2;
availableSpace -= offset;
}
if (!this.settings.squished
&& this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT) &&
(this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR) || this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT))) {
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
this.renderText(context, vector, this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR), this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT),
start, end, clip, this.isColumns ? (fullAvailableSpace - offset) : offset);
offset += this.settings.maxTextWidth + 2;
availableSpace -= offset;
}
if (!this.settings.squished
&& this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT) && !this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR)
&& !this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT)) {
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
var dataType = phantasus.VectorUtil.getDataType(vector);
if (dataType === 'url') {
context.fillStyle = 'blue';
this.canvas.style.cursor = 'pointer';
}
this.renderText(context, vector, false, false, start, end, clip, this.isColumns ? (fullAvailableSpace - offset) : offset);
}
}
,
showPopup: function (e, isHeader) {
var _this = this;
var project = this.project;
var isColumns = this.isColumns;
var hasSelection = isColumns ? project.getColumnSelectionModel()
.count() > 0 : project.getRowSelectionModel().count() > 0;
var ANNOTATE_SELECTION = 'Annotate Selection';
var INVERT_SELECTION = 'Invert Selection';
var SELECT_ALL = 'Select All';
var SHOW_SELECTION_ONLY = 'Show Selection Only';
var CLEAR_SELECTION = 'Clear Selection';
var HIGHLIGHT_MATCHING_VALUES = 'Highlight Matching Values';
var FIELDS = 'Choose Fields...';
var RENAME = 'Rename...';
var DELETE = 'Delete...';
var TOOLTIP = 'Show In Tooltip';
var HIDE = 'Hide';
var HIDE_OTHERS = 'Hide Others';
var REMOVE_SHOW_SELECTION_ONLY = 'Show All';
var SORT_ASC = 'Sort Ascending';
var SORT_DESC = 'Sort Descending';
var MOVE_TO_TOP = 'Move To Top';
var SORT_SEL_ASC = 'Sort Heat Map Ascending \u2191';
var SORT_SEL_DESC = 'Sort Heat Map Descending \u2193';
var SORT_SEL_TOP_N = 'Sort Heat Map Descending/Ascending';
var DISPLAY_BAR = 'Show Bar Chart';
var NUMBER_FORMAT = 'Format';
var DISPLAY_STACKED_BAR = 'Show Stacked Bar Chart';
var DISPLAY_BOX_PLOT = 'Show Box Plot';
var DISPLAY_COLOR = 'Show Color';
var COLOR_BAR_SIZE = 'Color Bar Size...';
var DISPLAY_TEXT = 'Show Text';
var DISPLAY_SHAPE = 'Show Shape';
var DISPLAY_ARC = 'Show Arc';
var DISPLAY_TEXT_AND_COLOR = 'Encode Text Using Color';
var DISPLAY_TEXT_AND_FONT = 'Encode Text Using Font';
var DISPLAY_STRUCTURE = 'Show Chemical Structure';
var DISPLAY_CONTINUOUS = 'Continuous';
var VIEW_STRING = "View as string";
var VIEW_NUMBER = "View as number";
var COPY_VALUES = "Copy selected values from " + this.name;
var CUSTOM_ORDER = "Change sort order";
var isGrouped = isColumns ?
_.size(project.getGroupColumns()) > 0 && _.first(project.getGroupColumns()).name === this.name :
_.size(project.getGroupRows()) > 0 && _.first(project.getGroupRows()).name === this.name;
var GROUP_BY_VALUES = "Group by this " + (this.isColumns ? "column" : "row");
var positions = this.positions;
var heatmap = this.heatmap;
var sectionToItems = {
'Sort': [],
'Selection': [],
'Display': []
};
var customItems = this.heatmap.getPopupItems();
if (customItems && customItems.length > 0) {
customItems.forEach(function (item) {
if (item.columns === isColumns) {
sectionToItems[item.section].push(item);
}
});
}
if (sectionToItems.Selection.length > 0) {
sectionToItems.Selection.push({
separator: true
});
}
sectionToItems.Selection.push({
name: MOVE_TO_TOP
});
if (this.heatmap.options.menu.Edit && this.heatmap.options.menu.Edit.indexOf('Annotate' +
' Selected Rows') !== -1) {
sectionToItems.Selection.push({
name: ANNOTATE_SELECTION
});
}
sectionToItems.Selection.push({
name: COPY_VALUES,
class: 'copy'
});
sectionToItems.Selection.push({
name: INVERT_SELECTION
});
sectionToItems.Selection.push({
name: SELECT_ALL
});
sectionToItems.Selection.push({
name: CLEAR_SELECTION
});
// sectionToItems.Selection.push({
// name : SHOW_SELECTION_ONLY
// });
var combinedFilter = isColumns ? project.getColumnFilter() : project
.getRowFilter();
var showSelectionOnlyIndex = combinedFilter
.indexOf(SHOW_SELECTION_ONLY);
if (showSelectionOnlyIndex !== -1) {
sectionToItems.Selection.push({
name: REMOVE_SHOW_SELECTION_ONLY
});
}
if (!isHeader) {
sectionToItems['Sort'].push({
name: SORT_SEL_ASC,
disabled: !hasSelection
});
sectionToItems['Sort'].push({
name: SORT_SEL_DESC,
disabled: !hasSelection
});
sectionToItems['Sort'].push({
name: SORT_SEL_TOP_N,
disabled: !hasSelection
});
}
var dataType = phantasus.VectorUtil.getDataType(this.getFullVector());
var arrayFields = this.getFullVector().getProperties().get(
phantasus.VectorKeys.FIELDS);
var isArray = arrayFields !== undefined;
var isNumber = dataType === 'number' || dataType === '[number]';
if (isNumber || isArray) {
sectionToItems.Display.push({
name: NUMBER_FORMAT
});
sectionToItems.Display.push({
separator: true
});
sectionToItems.Display.push({
name: DISPLAY_BAR,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.BAR)
});
}
if (isArray) {
sectionToItems.Display.push({
name: DISPLAY_STACKED_BAR,
checked: this.settings.stackedBar
});
sectionToItems.Display.push({
name: DISPLAY_BOX_PLOT,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.BOX_PLOT)
});
sectionToItems.Display.push({
name: FIELDS
});
}
if (dataType !== 'url') {
// text and text_and_color are mutually exclusive
sectionToItems.Display.push({
name: DISPLAY_TEXT,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT)
});
if (this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT)) {
sectionToItems.Display.push({
name: DISPLAY_TEXT_AND_COLOR,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR)
});
sectionToItems.Display.push({
name: DISPLAY_TEXT_AND_FONT,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT)
});
}
sectionToItems.Display.push({
separator: true
});
sectionToItems.Display.push({
name: DISPLAY_COLOR,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)
});
if (this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)) {
sectionToItems.Display.push({
name: COLOR_BAR_SIZE
});
}
}
if (!isArray && dataType !== 'url') {
sectionToItems.Display.push({
name: DISPLAY_SHAPE,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)
});
// sectionToItems.Display.push({
// name : DISPLAY_ARC,
// checked : this.isRenderAs(phantasus.VectorTrack.RENDER.ARC)
// });
}
if (!isArray && !isNumber && !this.isColumns
&& name.toLowerCase().indexOf('smile') !== -1) {
sectionToItems.Display.push({
name: DISPLAY_STRUCTURE,
checked: this.isRenderAs(phantasus.VectorTrack.RENDER.MOLECULE)
});
}
sectionToItems.Display.push({
name: TOOLTIP,
checked: this.settings.inlineTooltip
});
if (!isArray && dataType !== 'url') {
sectionToItems.Display.push({
name: HIGHLIGHT_MATCHING_VALUES,
checked: this.settings.highlightMatchingValues
});
}
if (dataType !== 'url') {
// sectionToItems.Display.push({
// name: 'Squished',
// checked: this.settings.squished
// });
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.BAR)
|| this.isRenderAs(phantasus.VectorTrack.RENDER.BOX_PLOT)) {
sectionToItems.Display.push({
separator: true
});
if (this.isRenderAs(phantasus.VectorTrack.RENDER.BAR)) {
sectionToItems.Display.push({
name: 'Edit Bar Color...'
});
}
sectionToItems.Display.push({
name: 'Auto Range'
});
sectionToItems.Display.push({
name: 'Custom Range...'
});
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)
|| this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR) || this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT)
|| this.isRenderAs(phantasus.VectorTrack.RENDER.BAR)
|| this.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)) {
sectionToItems.Display.push({
separator: true
});
if (isNumber) {
sectionToItems.Display.push({
name: DISPLAY_CONTINUOUS,
checked: !this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE)
});
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)
|| this
.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR)
|| (this.isRenderAs(phantasus.VectorTrack.RENDER.BAR) && isArray)) {
sectionToItems.Display.push({
name: 'Edit Colors...'
});
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)) {
sectionToItems.Display.push({
name: 'Edit Shapes...'
});
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT)) {
sectionToItems.Display.push({
name: 'Edit Fonts...'
});
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT)) {
sectionToItems.Display.push({
name: 'Edit Fonts...'
});
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.COLOR)
|| this
.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_COLOR)
|| (this.isRenderAs(phantasus.VectorTrack.RENDER.BAR) && isArray)) {
sectionToItems.Display.push({
name: 'Color Key',
icon: 'fa fa-key'
});
}
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.SHAPE)) {
sectionToItems.Display.push({
name: 'Shape Key',
icon: 'fa fa-key'
});
}
if (this.isRenderAs(phantasus.VectorTrack.RENDER.TEXT_AND_FONT)) {
sectionToItems.Display.push({
name: 'Font Key',
icon: 'fa fa-key'
});
}
sectionToItems.Display.push({
separator: true
});
sectionToItems.Display.push({
name: GROUP_BY_VALUES,
checked: isGrouped
});
sectionToItems.Display.push({
name: CUSTOM_ORDER,
checked: this.getFullVector().isFactorized()
});
sectionToItems.Display.push({
separator: true
});
sectionToItems.Display.push({
name: HIDE
});
sectionToItems.Display.push({
name: HIDE_OTHERS,
disabled: heatmap.getVisibleTrackNames(this.isColumns).length <= 1
});
if (!isColumns) {
sectionToItems.Display.push({
name: VIEW_STRING,
checked: !isNumber
});
sectionToItems.Display.push({
name: VIEW_NUMBER,
checked: isNumber
});
}
sectionToItems.Display.push({
separator: true
});
sectionToItems.Display.push({
name: RENAME
});
sectionToItems.Display.push({
name: DELETE
});
var items = [];
function addSection(name) {
if (items.length > 0) {
items.push({
separator: true
});
}
items = items.concat(sectionToItems[name]);
}
addSection('Sort');
_.each(sectionToItems.Selection, function (item) {
if (item.name !== REMOVE_SHOW_SELECTION_ONLY
&& item.name !== SELECT_ALL) {
item.disabled = !hasSelection;
}
});
if (!isHeader) {
addSection('Selection');
} else if (this.heatmap.options.toolbar.options) {
addSection('Display');
}
if (e.preventDefault) {
e.preventDefault();
}
if (items.length === 0) {
return;
}
phantasus.Popup
.showPopup(
items,
{
x: e.pageX,
y: e.pageY
},
e.target,
function (event, item) {
var customItem;
if (item === NUMBER_FORMAT) {
var vector = _this.getFullVector();
var formatter = vector
.getProperties().get(phantasus.VectorKeys.FORMATTER);
if (formatter != null) {
if (typeof formatter === 'object') { // convert to function
formatter = phantasus.Util.createNumberFormat(formatter.pattern);
vector.getProperties().set(phantasus.VectorKeys.FORMATTER, formatter);
}
}
var pattern = formatter != null ? formatter.toJSON().pattern : '.5g';
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'number_of_fraction_digits',
type: 'number',
value: phantasus.Util.getNumberFormatPatternFractionDigits(pattern),
required: true,
style: 'max-width:60px;'
});
formBuilder.find('number_of_fraction_digits').on(
'keyup input', _.debounce(
function () {
var n = parseInt($(this)
.val());
if (n >= 0) {
vector.getProperties().set(phantasus.VectorKeys.FORMATTER, {pattern: '.' + n + 'g'});
_this.setInvalid(true);
_this.repaint();
}
}, 100));
phantasus.FormBuilder.showInModal({
title: 'Format',
close: 'Close',
html: formBuilder.$form,
focus: heatmap.getFocusEl()
});
} else if (item === COPY_VALUES) {
heatmap.getActionManager().execute(isColumns ? 'Copy Selected Columns' : 'Copy' +
' Selected Rows');
} else if (item === FIELDS) {
var visibleFieldIndices = _this
.getFullVector()
.getProperties()
.get(phantasus.VectorKeys.VISIBLE_FIELDS);
var visibleFields;
if (visibleFieldIndices == null) {
visibleFields = arrayFields.slice(0);
} else {
visibleFields = [];
for (var i = 0; i < visibleFieldIndices.length; i++) {
visibleFields
.push(arrayFields[visibleFieldIndices[i]]);
}
}
var availableFields = [];
for (var i = 0; i < arrayFields.length; i++) {
if (visibleFields.indexOf(arrayFields[i]) === -1) {
availableFields.push(arrayFields[i]);
}
}
var leftOptions = [];
var rightOptions = [];
for (var i = 0; i < availableFields.length; i++) {
leftOptions.push(new Option(
availableFields[i],
availableFields[i]));
}
for (var i = 0; i < visibleFields.length; i++) {
rightOptions
.push(new Option(visibleFields[i],
visibleFields[i]));
}
var list = new phantasus.DualList(leftOptions,
rightOptions);
phantasus.FormBuilder
.showOkCancel({
title: 'Fields',
okCallback: function () {
var visibleFields = list
.getOptions(false);
var visibleFieldIndices = [];
for (var i = 0; i < visibleFields.length; i++) {
visibleFieldIndices
.push(arrayFields
.indexOf(visibleFields[i]));
}
var fullVector = _this
.getFullVector();
fullVector
.getProperties()
.set(
phantasus.VectorKeys.VISIBLE_FIELDS,
visibleFieldIndices);
var summaryFunction = fullVector
.getProperties()
.get(
phantasus.VectorKeys.ARRAY_SUMMARY_FUNCTION);
if (summaryFunction) {
summaryFunction.indices = visibleFieldIndices;
}
var updatedVector = _this.isColumns ? _this.project
.getFullDataset()
.getColumnMetadata()
.add(_this.name)
: _this.project
.getFullDataset()
.getRowMetadata()
.add(_this.name);
// remove cached summary field
for (var i = 0; i < updatedVector
.size(); i++) {
var array = fullVector
.getValue(i);
if (array != null) {
array.summary = undefined;
}
}
_this.setInvalid(true);
_this.repaint();
},
content: list.$el
});
} else if (item === 'Edit Bar Color...') {
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'bar_color',
type: 'color',
value: _this.settings.barColor,
required: true,
style: 'max-width:50px;'
});
formBuilder.find('bar_color').on(
'change',
function () {
_this.settings.barColor = $(this)
.val();
_this.setInvalid(true);
_this.repaint();
});
phantasus.FormBuilder.showInModal({
title: 'Bar Color',
close: 'Close',
html: formBuilder.$form,
focus: heatmap.getFocusEl()
});
} else if (item === COLOR_BAR_SIZE) {
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'size',
type: 'text',
value: _this.settings.colorBarSize,
required: true,
style: 'max-width:50px;'
});
formBuilder.find('size').on(
'change',
function () {
var val = parseFloat($(this)
.val());
if (val > 0) {
_this.settings.colorBarSize = val;
_this.setInvalid(true);
_this.repaint();
}
});
phantasus.FormBuilder.showInModal({
title: 'Color Bar Size',
close: 'Close',
html: formBuilder.$form,
focus: heatmap.getFocusEl()
});
} else if (item === VIEW_STRING || item === VIEW_NUMBER) {
var vectorName = _this.name;
var dataset = _this.project.getFullDataset();
var v = dataset.getRowMetadata().getByName(vectorName);
v.getProperties().set("phantasus.dataType", item === VIEW_STRING ? "string" : "number");
_this.project.trigger('trackChanged', {
vectors: [v],
display: _this.settings.display,
columns: isColumns
});
} else if (item === ANNOTATE_SELECTION) {
heatmap.getActionManager().execute(isColumns ? 'Annotate Selected Columns' : 'Annotate' +
' Selected Rows');
} else if (item === GROUP_BY_VALUES) {
if (isColumns) {
_this.project.setGroupColumns(isGrouped ? [] : [new phantasus.SortKey(_this.name, phantasus.SortKey.SortOrder.UNSORTED)], true);
} else {
_this.project.setGroupRows(isGrouped ? [] : [new phantasus.SortKey(_this.name, phantasus.SortKey.SortOrder.UNSORTED)], true);
}
} else if (item === RENAME) {
var formBuilder = new phantasus.FormBuilder();
formBuilder.append({
name: 'Name',
type: 'text',
value: _this.name,
required: true,
});
phantasus.FormBuilder.showInModal({
title: 'Rename...',
close: 'Apply',
html: formBuilder.$form,
focus: heatmap.getFocusEl(),
onClose: function () {
var oldName = _this.name;
var newName = formBuilder.getValue('Name');
var dataset = _this.project.getFullDataset();
if (oldName !== newName) {
var target = isColumns ?
dataset.getColumnMetadata() :
dataset.getRowMetadata();
var currentSessionPromise = dataset.getESSession();
var v = target.getByName(oldName);
v.setName(newName);
if (currentSessionPromise) {
dataset.setESSession(new Promise(function (resolve, reject) {
currentSessionPromise.then(function (essession) {
var args = {
es: essession,
isFeature: !isColumns,
oldName: oldName,
newName: newName
};
var req = ocpu.call("renameColumn", args, function (newSession) {
resolve(newSession);
}, false, "::es");
req.fail(function () {
reject();
throw new Error("renameColumn call to OpenCPU failed" + req.responseText);
});
});
}));
}
_this.project.trigger(isColumns? 'columnTrackRemoved' : 'rowTrackRemoved', {
vector: _this.getFullVector()
});
_this.project.trigger('trackChanged', {
vectors: [v],
display: _this.settings.display,
columns: isColumns
});
}
}
});
} else if (item === CUSTOM_ORDER) {
phantasus.factorizeColumn(_this.getFullVector());
} else if (item === DELETE) {
phantasus.FormBuilder
.showOkCancel({
title: 'Delete',
content: 'Are you sure you want to delete '
+ _this.name + '?',
okCallback: function () {
var metadata = isColumns ? project
.getFullDataset()
.getColumnMetadata()
: project
.getFullDataset()
.getRowMetadata();
metadata.remove(phantasus.MetadataUtil.indexOf(metadata, _this.name));
var sortKeys = isColumns ?
project.getColumnSortKeys() :
project.getRowSortKeys();
var sortKeyIndex = _.indexOf(
sortKeys.map(function (key) {
return key.field;
}), _this.name);
if (sortKeyIndex !== -1) {
sortKeys.splice(
sortKeyIndex, 1);
if (isColumns) {
project
.setColumnSortKeys(
sortKeys,
true);
} else {
project.setRowSortKeys(
sortKeys, true);
}
}
var groupByKeys = isColumns ?
project.getGroupColumns() :
project.getGroupRows();
var groupByKeyIndex = _
.indexOf(
groupByKeys
.map(function (key) {
return key.field;
}),
_this.name);
if (groupByKeyIndex !== -1) {
groupByKeys.splice(
groupByKeyIndex, 1);
if (isColumns) {
project
.setGroupColumns(
groupByKeys,
true);
} else {
project.setGroupRows(
groupByKeys,
true);
}
}
var dataset = _this.project.getFullDataset();
var currentSessionPromise = dataset.getESSession();
if (currentSessionPromise) {
dataset.setESSession(new Promise(function (resolve, reject) {
currentSessionPromise.then(function (essession) {
var args = {
es: essession,
isFeature: !isColumns,
oldName: _this.name,
newName: _this.name
};
var req = ocpu.call("renameColumn", args, function (newSession) {
resolve(newSession);
}, false, "::es");
req.fail(function () {
reject();
throw new Error("renameColumn call to OpenCPU failed" + req.responseText);
});
});
}));
}
_this.project.trigger(isColumns? 'columnTrackRemoved' : 'rowTrackRemoved', {
vector: _this.getFullVector()
});
}
});
} else if (item === CLEAR_SELECTION) {
heatmap.getActionManager().execute(isColumns ? 'Clear Selected Columns' : 'Clear' +
' Selected Rows');
} else if (item === INVERT_SELECTION) {
heatmap.getActionManager().execute(isColumns ? 'Invert Selected Columns' : 'Invert' +
' Selected Rows');
} else if (item === MOVE_TO_TOP) {
heatmap.getActionManager().execute(isColumns ? 'Move Selected Columns To Top' : 'Move' +
' Selected Rows To Top');
} else if (item === SORT_ASC || item === SORT_DESC) {
var sortKey = new phantasus.SortKey(
_this.name,
item === SORT_ASC ? phantasus.SortKey.SortOrder.ASCENDING
: phantasus.SortKey.SortOrder.DESCENDING);
if (_this.isColumns) {
_this.project
.setColumnSortKeys(
phantasus.SortKey
.keepExistingSortKeys(
[sortKey],
project
.getColumnSortKeys()),
true);
} else {
_this.project
.setRowSortKeys(
phantasus.SortKey
.keepExistingSortKeys(
[sortKey],
project
.getRowSortKeys()),
true);
}
} else if (item == SORT_SEL_ASC
|| item == SORT_SEL_DESC
|| item === SORT_SEL_TOP_N) {
var sortOrder;
if (item === SORT_SEL_ASC) {
sortOrder = phantasus.SortKey.SortOrder.ASCENDING;
} else if (item === SORT_SEL_DESC) {
sortOrder = phantasus.SortKey.SortOrder.DESCENDING;
} else {
sortOrder = phantasus.SortKey.SortOrder.TOP_N;
}
heatmap.sortBasedOnSelection(sortOrder,
isColumns, e && e.shiftKey);
} else if (item === SELECT_ALL) {
heatmap.getActionManager().execute(isColumns ? 'Select All Columns' : 'Select All Rows');
} else if (item === 'Auto Range') {
delete _this.settings.min;
delete _this.settings.max;
delete _this.settings.mid;
_this._update();
heatmap.revalidate();
} else if (item === 'Custom Range...') {
var formBuilder = new phantasus.FormBuilder();
var items = [
{name: 'min',
required: true,
type: 'number',
value: _this.settings.min
}, {
name: 'mid',
required: true,
type: 'number',
value: _this.settings.mid
}, {
name: 'max',
required: true,
type: 'number',
value: _this.settings.max
}];
_.each(items, function (item) {
formBuilder.append(item);
});
phantasus.FormBuilder
.showOkCancel({
title: 'Range',
content: formBuilder.$form,
okCallback: function () {
_this.settings.min = parseFloat(formBuilder
.getValue('min'));
_this.settings.mid = parseFloat(formBuilder
.getValue('mid'));
_this.settings.max = parseFloat(formBuilder
.getValue('max'));
_this._update();
heatmap.revalidate();
}
});
} else if (item === 'Squished') {
_this.settings.squished = !_this.settings.squished;
heatmap.revalidate();
} else if (item === 'Color Key') {
var legend = new phantasus.HeatMapTrackColorLegend(
[_this], isColumns ? _this.project
.getColumnColorModel()
: _this.project
.getRowColorModel());
var size = legend.getPreferredSize();
legend.setBounds(size);
legend.repaint();
phantasus.FormBuilder.showInModal({
title: 'Color Key',
html: legend.canvas,
focus: heatmap.getFocusEl()
});
} else if (item === 'Shape Key') {
var legend = new phantasus.HeatMapTrackShapeLegend(
[_this], isColumns ? _this.project
.getColumnShapeModel()
: _this.project
.getRowShapeModel());
var size = legend.getPreferredSize();
legend.setBounds(size);
legend.repaint();
phantasus.FormBuilder.showInModal({
title: 'Shape Key',
html: legend.canvas,
focus: heatmap.getFocusEl()
});
} else if (item === 'Font Key') {
var legend = new phantasus.HeatMapTrackFontLegend(
[_this], isColumns ? _this.project
.getColumnFontModel()
: _this.project
.getRowFontModel());
var size = legend.getPreferredSize();
legend.setBounds(size);
legend.repaint();
phantasus.FormBuilder.showInModal({
title: 'Font Key',
html: legend.canvas,
focus: heatmap.getFocusEl()
});
} else if (item === 'Edit Fonts...') {
heatmap.getActionManager().execute('Edit Fonts');
} else if (item === 'Edit Shapes...') {
var shapeFormBuilder = new phantasus.FormBuilder();
var shapeModel = isColumns ? _this.project
.getColumnShapeModel() : _this.project
.getRowShapeModel();
var chooser = new phantasus.ShapeChooser({
map: shapeModel.getMap(_this.name)
});
chooser.on('change', function (event) {
shapeModel.setMappedValue(_this
.getFullVector(), event.value,
event.shape);
_this.setInvalid(true);
_this.repaint();
});
phantasus.FormBuilder.showInModal({
title: 'Edit Shapes',
html: chooser.$div,
close: 'Close',
focus: heatmap.getFocusEl()
});
} else if (item === 'Edit Colors...') {
var colorModel = isColumns ? _this.project
.getColumnColorModel() : _this.project
.getRowColorModel();
var colorSchemeChooser = new phantasus.ColorSchemeChooser({track: _this, heatMap: _this.heatmap, colorModel: colorModel});
phantasus.FormBuilder.showInModal({
title: 'Edit Colors',
html: colorSchemeChooser.$div,
close: 'Close',
focus: heatmap.getFocusEl()
});
} else if (item === TOOLTIP) {
_this.settings.inlineTooltip = !_this.settings.inlineTooltip;
} else if (item === HIGHLIGHT_MATCHING_VALUES) {
_this.settings.highlightMatchingValues = !_this.settings.highlightMatchingValues;
} else if ((customItem = _
.find(
customItems,
function (customItem) {
return customItem.name === item
&& customItem.columns === isColumns;
}))) {
if (customItem.task) {
// add task
var task = {
tabId: _this.heatmap.getTabManager()
.getActiveTabId()
};
_this.heatmap.getTabManager().addTask(task);
setTimeout(function () {
customItem.callback(heatmap);
_this.heatmap.getTabManager()
.removeTask(task);
}, 1);
} else {
customItem.callback(heatmap);
}
} else if (item === DISPLAY_CONTINUOUS) {
var discrete = _this.getFullVector().getProperties().get(phantasus.VectorKeys.DISCRETE) || false;
_this.getFullVector().getProperties().set(phantasus.VectorKeys.DISCRETE, !discrete);
_this._setChartMinMax();
_this.setInvalid(true);
_this.repaint();
} else if (item === HIDE) {
heatmap.setTrackVisible(_this.name, false,
_this.isColumns);
heatmap.revalidate();
} else if (item === HIDE_OTHERS) {
var names = heatmap.getVisibleTrackNames(_this.isColumns);
for (var i = 0; i < names.length; i++) {
if (names[i] !== _this.name) {
heatmap.setTrackVisible(names[i], false,
_this.isColumns);
}
}
heatmap.revalidate();
} else if (item === DISPLAY_STACKED_BAR) {
_this.settings.stackedBar = !_this.settings.stackedBar;
_this._update();
heatmap.revalidate();
} else {
if (item === DISPLAY_BAR) {
item = phantasus.VectorTrack.RENDER.BAR;
} else if (item === DISPLAY_COLOR) {
item = phantasus.VectorTrack.RENDER.COLOR;
} else if (item === DISPLAY_TEXT) {
item = phantasus.VectorTrack.RENDER.TEXT;
} else if (item === DISPLAY_TEXT_AND_COLOR) {
item = phantasus.VectorTrack.RENDER.TEXT_AND_COLOR;
} else if (item === DISPLAY_TEXT_AND_FONT) {
item = phantasus.VectorTrack.RENDER.TEXT_AND_FONT;
} else if (item === DISPLAY_STRUCTURE) {
item = phantasus.VectorTrack.RENDER.MOLECULE;
} else if (item === DISPLAY_SHAPE) {
item = phantasus.VectorTrack.RENDER.SHAPE;
} else if (item === DISPLAY_ARC) {
item = phantasus.VectorTrack.RENDER.ARC;
} else if (item === DISPLAY_BOX_PLOT) {
item = phantasus.VectorTrack.RENDER.BOX_PLOT;
} else {
console.log('Unknown item ' + item);
}
var show = !_this.isRenderAs(item);
var remove;
if (!show) {
_this.settings.display.splice(_this.settings.display.indexOf(item), 1);
// if no longer rendering as text, remove TEXT_AND_COLOR and TEXT_AND_FONT
if (item === phantasus.VectorTrack.RENDER.TEXT) {
remove = [phantasus.VectorTrack.RENDER.TEXT_AND_FONT, phantasus.VectorTrack.RENDER.TEXT_AND_COLOR];
}
} else {
if (item === phantasus.VectorTrack.RENDER.COLOR) {
remove = [phantasus.VectorTrack.RENDER.TEXT_AND_COLOR];
} else if (item === phantasus.VectorTrack.RENDER.TEXT_AND_COLOR) {
remove = [phantasus.VectorTrack.RENDER.COLOR];
}
_this.settings.display.push(item);
}
if (remove) {
remove.forEach(function (key) {
var index = _this.settings.display.indexOf(key);
if (index !== -1) {
_this.settings.display.splice(index, 1);
}
});
}
_this._update();
heatmap.revalidate();
if (show && item === phantasus.VectorTrack.RENDER.TEXT_AND_FONT) {
heatmap.getActionManager().execute('Edit Fonts');
}
}
});
}
,
renderColor: function (context, vector, start, end, clip, offset, continuous) {
var isColumns = this.isColumns;
var positions = this.positions;
var colorModel = isColumns ? this.project.getColumnColorModel() : this.project.getRowColorModel();
var settings = this.settings;
var canvasSize = isColumns ? this.getUnscaledHeight() : this.getUnscaledWidth();
var colorBarSize = settings.colorBarSize;
if (colorBarSize > canvasSize) {
colorBarSize = canvasSize >= 5 ? (canvasSize - 1)
: canvasSize;
}
var getColor;
if (!continuous) {
getColor = _.bind(colorModel.getMappedValue, colorModel);
} else {
getColor = _.bind(colorModel.getContinuousMappedValue, colorModel);
}
if (vector.getProperties().get(
phantasus.VectorKeys.FIELDS) != null) {
var visibleFieldIndices = vector.getProperties().get(
phantasus.VectorKeys.VISIBLE_FIELDS);
if (visibleFieldIndices == null) {
visibleFieldIndices = phantasus.Util.seq(vector.getProperties().get(
phantasus.VectorKeys.FIELDS).length);
}
colorBarSize /= visibleFieldIndices.length;
var nvisibleFieldIndices = visibleFieldIndices.length;
for (var i = start; i < end; i++) {
var array = vector.getValue(i);
var position = positions.getPosition(i);
var size = positions.getItemSize(i);
var _offset = offset;
if (array != null) {
for (var j = 0; j < nvisibleFieldIndices; j++) {
var value = array[visibleFieldIndices[j]];
var color = getColor(vector, value);
context.fillStyle = color;
if (isColumns) {
context.beginPath();
context.rect(position, _offset - colorBarSize, size,
colorBarSize);
context.fill();
} else {
context.beginPath();
context.rect(_offset, position, colorBarSize, size);
context.fill();
}
_offset += colorBarSize;
}
}
}
} else {
for (var i = start; i < end; i++) {
var value = vector.getValue(i);
var position = positions.getPosition(i);
var size = positions.getItemSize(i);
var color = getColor(vector, value);
context.fillStyle = color;
if (isColumns) {
context.beginPath();
context.rect(position, offset - colorBarSize, size,
settings.colorBarSize);
context.fill();
} else {
context.beginPath();
context.rect(offset, position, colorBarSize, size);
context.fill();
}
}
}
}
,
renderShape: function (context, vector, start, end, clip, offset) {
var isColumns = this.isColumns;
var positions = this.positions;
var shapeModel = isColumns ? this.project.getColumnShapeModel()
: this.project.getRowShapeModel();
var settings = this.settings;
var canvasSize = isColumns ? this.getUnscaledHeight() : this
.getUnscaledWidth();
var colorBarSize = settings.colorBarSize;
if (colorBarSize > canvasSize) {
colorBarSize = canvasSize >= 5 ? (canvasSize - 1)
: canvasSize;
}
context.fillStyle = 'black';
context.strokeStyle = 'black';
var lineWidth = context.lineWidth;
context.lineWidth = 1;
for (var i = start; i < end; i++) {
var value = vector.getValue(i);
var position = positions.getPosition(i);
var itemSize = positions.getItemSize(i);
var minSize = Math.min(colorBarSize, itemSize);
var size2 = minSize / 2;
var shape = shapeModel.getMappedValue(vector, value);
// x and y are at center
var x = isColumns ? position + itemSize / 2 : offset + size2;
var y = isColumns ? offset - size2 : position + itemSize / 2;
size2 -= 0.5; // small border between cells
phantasus.CanvasUtil.drawShape(context, shape, x, y, size2);
}
context.lineWidth = lineWidth;
}
,
renderHeatMap: function (context, vector, start, end, clip, size) {
var isColumns = this.isColumns;
var positions = this.positions;
var project = this.project;
context.save();
context.lineWidth = 1;
// context.translate(clip.x, clip.y);
var width = clip.width;
var height = clip.height;
var colorScheme = this.heatmap.getHeatMapElementComponent().getColorScheme();
var drawGrid = this.heatmap.getHeatMapElementComponent().isDrawGrid();
var gridColor = this.heatmap.getHeatMapElementComponent().getGridColor();
var gridThickness = this.heatmap.getHeatMapElementComponent().getGridThickness();
for (var i = start; i < end; i++) {
var value = vector.getValue(i); // value is an array of values to display as a heat map
if (value != null) {
var pix = positions.getPosition(i);
var itemSize = positions.getItemSize(i);
var currentPix = 0;
var nvalues = value.length;
var pixPer = size / nvalues;
for (var j = 0; j < nvalues; j++) {
var val = value[j];
context.fillStyle = colorScheme.getColor(i, -1, val);
context.fillRect(j * pixPer, pix, pixPer, itemSize);
}
if (drawGrid && itemSize > 10) {
context.strokeStyle = gridColor;
context.lineWidth = gridThickness;
context.beginPath();
for (var j = 0; j < nvalues; j++) {
var val = value[j];
context.rect(j * pixPer, pix, pixPer, itemSize);
}
context.stroke();
}
}
}
context.restore();
}
,
renderArc: function (context, vector, start, end, clip, size) {
var isColumns = this.isColumns;
var positions = this.positions;
var project = this.project;
context.save();
context.lineWidth = 1;
// var scale = d3.scale.linear().domain([1, size]).range([0.8, 1])
// .clamp(true);
// var fill = d3.scale.category20b();
var total = positions.getPosition(positions.getLength() - 1)
+ positions.getItemSize(positions.getLength() - 1);
context.translate(clip.x, clip.y);
var width = clip.width;
var height = clip.height;
// if (!isColumns) {
// var squishFactor = height / total;
// context.scale(1, squishFactor);
// } else {
// var squishFactor = width / total;
// context.scale(squishFactor, 1);
// }
for (var i = start; i < end; i++) {
var value = vector.getValue(i); // value is an array of other indices to link to
if (value != null) {
var startPix = positions.getPosition(i) + positions.getItemSize(i)
/ 2;
for (var j = 0, nindices = value.length; j < nindices; j++) {
var viewIndex = value[j];
var endPix = positions.getPosition(viewIndex)
+ positions.getItemSize(viewIndex) / 2;
var midPix = (endPix + startPix) / 2;
var distance = Math.abs(i - viewIndex);
var arcRadius = size; // scale(distance) * size;
if (isColumns) {
context.beginPath();
context.moveTo(startPix, arcRadius);
context.quadraticCurveTo(midPix, 1, endPix, arcRadius);
} else {
// console.log(i, viewIndex, startPix, endPix);
context.beginPath();
context.moveTo(1, startPix);
context.quadraticCurveTo(arcRadius, midPix, 1, endPix);
}
context.stroke();
}
}
}
context.restore();
}
,
sdfToSvg: function (sdf, width, height) {
if (!this.jsme && typeof JSApplet !== 'undefined') {
this.jsmeId = _.uniqueId('m');
this.$jsmeDiv = $(
'<div id="'
+ this.jsmeId
+ '" style="position:absolute;left:-10000px;top:-10000px;"></div>')
.appendTo($(document.body));
this.jsme = new JSApplet.JSME(this.jsmeId, '380px', '340px', {});
}
// this.$jsmeDiv.css('width', width + 'px').css('height', height +
// 'px');
// this.jsme.setSize(width + 'px', height + 'px');
this.jsme.readMolFile(sdf);
var svg = $('#' + this.jsmeId + ' > div > div > div:nth-child(2) > svg');
var svgWidth = svg.width.baseVal.value;
var svgHeight = svg.height.baseVal.value;
var scale = Math.min(width / svgWidth, height / svgHeight);
var text = '<svg><g transform="scale(' + scale + ')">' + svg.innerHTML
+ '</g></svg>';
return text;
}
,
renderMolecule: function (context, vector, start, end, clip, offset) {
var isColumns = this.isColumns;
var positions = this.positions;
context.fillStyle = phantasus.CanvasUtil.FONT_COLOR;
context.strokeStyle = 'black';
var width = this.getUnscaledWidth();
var customUrlProvider = this.heatmap.options.structureUrlProvider !== undefined;
var dummyTarget = {
childNodes: [],
getContext: function () {
return context;
}
};
for (var i = start; i < end; i++) {
var spanEnd = this.spanMap.get(i);
if (spanEnd !== undefined) {
var startPix = positions.getPosition(i);
var endPix = positions.getPosition(spanEnd - 1)
+ positions.getSize();
var size = endPix - startPix;
var value = vector.getValue(i);
var cache = this.moleculeCache[value];
if (cache) {
if (customUrlProvider) {
if (cache.complete) {
// 800 x 400
var scaleFactor = Math.min(size / cache.height,
width / cache.width);
var scaledWidth = cache.width * scaleFactor;
var scaledHeight = cache.height * scaleFactor;
var diff = cache.height - scaledHeight;
startPix += diff / 2;
try {
context.drawImage(cache, offset, startPix,
scaledWidth, scaledHeight);
}
catch (x) {
}
}
} else {
var text = this.sdfToSvg(cache, width, size);
canvg(dummyTarget, text, {
ignoreMouse: true,
ignoreAnimation: true,
offsetY: startPix,
ignoreClear: true,
ignoreDimensions: true
});
}
}
}
}
}
,
createChartScale: function (availableSpace) {
var domain;
var range;
if (this.settings.mid !== this.settings.min
&& this.settings.mid !== this.settings.max) {
domain = [this.settings.min, this.settings.mid, this.settings.max];
range = this.isColumns ? [availableSpace, availableSpace / 2, 0]
: [0, availableSpace / 2, availableSpace];
} else {
domain = [this.settings.min, this.settings.max];
range = this.isColumns ? [availableSpace, 0] : [
0,
availableSpace];
}
var scale = d3.scale.linear().domain(domain).range(range).clamp(true);
return scale;
}
,
renderBar: function (context, vector, start, end, clip, offset, availableSpace) {
var isColumns = this.isColumns;
var positions = this.positions;
context.fillStyle = this.settings.barColor;
var scale = this.createChartScale(availableSpace);
var midPix = scale(this.settings.mid);
var settings = this.settings;
var discrete = vector.getProperties().get(phantasus.VectorKeys.DISCRETE) && this.discreteValueMap != null;
var colorByVector = this.settings.colorByField != null ? this
.getVector(this.settings.colorByField) : null;
var colorModel = isColumns ? this.project.getColumnColorModel()
: this.project.getRowColorModel();
for (var i = start; i < end; i++) {
var value = vector.getValue(i);
if (discrete) {
value = this.discreteValueMap.get(value);
}
var position = positions.getPosition(i);
var size = positions.getItemSize(i);
var scaledValue = scale(value);
if (colorByVector !== null) {
context.fillStyle = colorModel.getMappedValue(colorByVector, colorByVector.getValue(i));
}
if (isColumns) {
context.beginPath();
context.rect(position, Math.min(midPix, scaledValue) + offset, size,
Math.abs(midPix - scaledValue));
context.fill();
} else {
context.beginPath();
context.rect(offset + Math.min(midPix, scaledValue), position,
Math.abs(midPix - scaledValue), size);
context.fill();
}
}
}
,
renderBoxPlot: function (context, vector, start, end, clip, offset, availableSpace) {
var isColumns = this.isColumns;
var positions = this.positions;
context.strokeStyle = 'black';
context.save();
context.translate(offset, 0);
var scale = this.createChartScale(availableSpace);
var visibleFieldIndices = vector.getProperties().get(
phantasus.VectorKeys.VISIBLE_FIELDS);
var colorByVector = this.settings.colorByField != null ? this
.getVector(this.settings.colorByField) : null;
var colorModel = isColumns ? this.project.getColumnColorModel()
: this.project.getRowColorModel();
for (var i = start; i < end; i++) {
var array = vector.getValue(i);
if (array != null) {
var itemSize = positions.getItemSize(i);
if (itemSize <= 3) {
continue;
}
var radius = 2;
var pix = positions.getPosition(i);
var start = pix + 1;
var end = pix + itemSize - 1;
var center = (start + end) / 2;
var _itemSize = itemSize - 2;
var lineHeight = Math.max(2, _itemSize - 8);
var box = array.summary;
if (box == null) {
var v = phantasus.VectorUtil.arrayAsVector(array);
box = phantasus
.BoxPlotItem(visibleFieldIndices != null ? new phantasus.SlicedVector(
v, visibleFieldIndices)
: v);
array.summary = box;
}
context.fillStyle = '#bdbdbd';
if (!isColumns) {
// box from q1 (25th q) to q3
context.fillRect(Math.min(scale(box.q1), scale(box.q3)),
start, Math.abs(scale(box.q1) - scale(box.q3)),
_itemSize);
// draw line from q1 to lav
context.fillRect(Math.min(scale(box.q1),
scale(box.lowerAdjacentValue)), center - lineHeight
/ 2, Math.abs(scale(box.q1)
- scale(box.lowerAdjacentValue)), lineHeight);
// draw line from q3 to uav
context.fillRect(Math.min(scale(box.q3),
scale(box.upperAdjacentValue)), center - lineHeight
/ 2, Math.abs(scale(box.q3)
- scale(box.upperAdjacentValue)), lineHeight);
context.fillStyle = '#31a354';
// highlight median
context.fillRect(scale(box.median) - 3, start, 3, end - start);
context.fillStyle = '#636363';
// draw individual points
// for (var j = 0, length = visibleFieldIndices == null ? array.length : visibleFieldIndices.length; j < length; j++) {
// var index = visibleFieldIndices == null ? j : visibleFieldIndices[j];
// var value = array[index];
// if (value != null) {
// if (colorByVector != null) {
// var colorByArray = colorByVector.getValue(i);
// if (colorByArray != null) {
// var color = colorModel
// .getMappedValue(
// colorByVector,
// colorByArray[index]);
// context.fillStyle = color;
// } else {
// context.fillStyle = '#636363';
// }
//
// }
// var pix = scale(value);
// context.beginPath();
// context
// .arc(pix, center, radius, Math.PI * 2,
// false);
// context.fill();
// }
// }
} else { // TOD implement for columns
}
}
}
context.restore();
}
,
renderStackedBar: function (context, vector, start, end, clip, offset, availableSpace) {
var isColumns = this.isColumns;
var positions = this.positions;
var scale = this.createChartScale(availableSpace);
var midPix = scale(this.settings.mid);
var settings = this.settings;
var colorModel = isColumns ? this.project.getColumnColorModel()
: this.project.getRowColorModel();
context.strokeStyle = 'black';
context.lineWidth = 2;
for (var i = start; i < end; i++) {
var array = vector.getValue(i);
if (array != null) {
var position = positions.getPosition(i);
var size = positions.getItemSize(i);
var positivePairs = [];
var negativePairs = [];
for (var j = 0, length = array.length; j < length; j++) {
var value = array[j];
if (value >= this.settings.mid) {
positivePairs.push({
value: value,
index: j
});
} else if (value < 0) {
negativePairs.push({
value: value,
index: j
});
}
}
// array.sort(function (a, b) {
// return (a.value < b.value ? 1 : (a.value === b.value ? 0 : -1));
// });
// var positiveIndices = [];
// positivePairs.forEach(function (item) {
// positiveIndices.push(item.index);
// });
//
var positiveIndices = phantasus.Util.indexSortPairs(
positivePairs, false);
for (var j = 0, length = positiveIndices.length; j < length; j++) {
var index = positiveIndices[j];
var value = array[index];
var color = colorModel.getMappedValue(vector, index);
context.fillStyle = color;
var scaledValue = scale(value);
var nextScaledValue = j === (length - 1) ? midPix
: scale(array[positiveIndices[j + 1]]);
if (isColumns) {
context.beginPath();
context.rect(position, Math.min(nextScaledValue,
scaledValue), size, Math.abs(nextScaledValue
- scaledValue));
context.fill();
} else {
context.beginPath();
context.rect(offset
+ Math.min(nextScaledValue, scaledValue),
position, Math.abs(nextScaledValue
- scaledValue), size);
context.fill();
}
}
var negativeIndices = phantasus.Util.indexSortPairs(
negativePairs, true); // draw smaller (more negative)
// values 1st
for (var j = 0, length = negativeIndices.length; j < length; j++) {
var index = negativeIndices[j];
var value = array[index];
var color = colorModel.getMappedValue(vector, index);
context.fillStyle = color;
var scaledValue = scale(value);
var nextScaledValue = j === (length - 1) ? midPix
: scale(array[negativeIndices[j + 1]]);
if (isColumns) {
context.beginPath();
context.rect(position, Math.min(nextScaledValue,
scaledValue), size, Math.abs(nextScaledValue
- scaledValue));
context.fill();
} else {
context.beginPath();
context.rect(offset
+ Math.min(nextScaledValue, scaledValue),
position, Math.abs(nextScaledValue
- scaledValue), size);
context.fill();
}
}
}
}
context.lineWidth = 1;
}
,
renderUnstackedBar: function (context, vector, start, end, clip, offset, availableSpace, fieldIndices) {
var isColumns = this.isColumns;
var positions = this.positions;
var nvalues = fieldIndices.length;
var settings = this.settings;
var colorModel = isColumns ? this.project.getColumnColorModel()
: this.project.getRowColorModel();
context.fillStyle = this.settings.barColor;
// context.strokeStyle = '0000f0';
var barSpacer = 0;
var barWidth = (availableSpace - (nvalues - 1) * barSpacer) / nvalues;
var colorByVector = this.settings.colorByField != null ? this
.getVector(this.settings.colorByField) : null;
context.strokeStyle = 'white';
for (var i = start; i < end; i++) {
var array = vector.getValue(i);
if (array != null) {
var position = positions.getPosition(i);
var itemSize = positions.getItemSize(i);
var scale = this.createChartScale(itemSize - 1);
var midPix = scale(this.settings.mid); // need to set mid pix
// for each item
var xpix = 0;
for (var j = 0; j < nvalues; j++) {
var value = array[fieldIndices[j]];
if (colorByVector != null) {
var colorByArray = colorByVector.getValue(i);
var color = colorModel
.getMappedValue(
colorByVector,
colorByArray != null ? colorByArray[fieldIndices[j]]
: null);
context.fillStyle = color;
}
var scaledValue = scale(value);
if (isColumns) {
context.beginPath();
context.rect(Math.min(midPix, scaledValue), offset
+ xpix, Math.abs(midPix - scaledValue),
barWidth);
context.fill();
} else {
// bar always goes to midpix
context.beginPath();
var barHeight = Math.abs(midPix - scaledValue);
var ypix = position + itemSize
- Math.max(midPix, scaledValue);
context.rect(offset + xpix, ypix, barWidth, barHeight);
context.fill();
}
xpix += barWidth + barSpacer;
}
}
}
}
,
renderText: function (context, vector, isColor, isFont, start, end, clip, offset,
canvasSize) {
context.textBaseline = 'middle';
if (typeof C2S !== 'undefined' && context instanceof C2S) { // FIXME hack for svg text alignment
context.translate(this.isColumns ? 2 : 0, this.isColumns ? 0 : 2);
}
var positions = this.positions;
var isColumns = this.isColumns;
var colorModel = isColumns ? this.project.getColumnColorModel()
: this.project.getRowColorModel();
var fontModel = isColumns ? this.project.getColumnFontModel()
: this.project.getRowFontModel();
if (isColumns) {
context.translate(clip.x, clip.y); // reset transform, needed for export to svg
}
var colorByVector = this.settings.colorByField != null ? this
.getVector(this.settings.colorByField) : vector;
var getColor;
if (colorByVector.getProperties().get(phantasus.VectorKeys.DISCRETE)) {
getColor = _.bind(colorModel.getMappedValue, colorModel);
} else {
getColor = _.bind(colorModel.getContinuousMappedValue, colorModel);
}
var fontVector = this.settings.fontField != null ? this
.getVector(this.settings.fontField) : vector;
var toStringFunction = phantasus.VectorTrack.vectorToString(vector);
var font = context.font;
for (var i = start; i < end; i++) {
var size = this.positions.getItemSize(i);
if (size < 6) {
continue;
}
var value = vector.getValue(i);
if (value != null) {
var stringValue = toStringFunction(value);
var position = positions.getPosition(i);
if (isColor) {
context.fillStyle = getColor(colorByVector, colorByVector.getValue(i));
}
if (isFont) {
context.font = fontModel.getMappedValue(fontVector, fontVector.getValue(i)).weight + ' ' + font;
}
if (isColumns) {
context.save();
context.translate(position + size / 2 - clip.x,
offset - clip.y);
context.rotate(-Math.PI / 2);
context.fillText(stringValue, 0, 0);
context.restore();
} else {
context.fillText(stringValue, offset, position + size / 2);
}
}
}
context.font = font;
}
};
phantasus.Util.extend(phantasus.VectorTrack, phantasus.AbstractCanvas);
phantasus.VectorTrack.MAX_FONT_SIZE = 18;
/**
* Performs clustering using pairwise average linking on the given distance
* matrix.
*
* @return array of nodes. Each node object contains a left, right, and
* distance.
*/
phantasus.AverageLinkage = function (nelements, distmatrix) {
var j;
var n;
var clusterid;
var number;
var result;
clusterid = []; // nelements;
number = []; // nelements;
result = []; // nelements - 1;
for (var i = 0; i < nelements - 1; i++) {
result[i] = {
left: 0,
right: 0,
distance: 0
};
}
/*
* Setup a list specifying to which cluster an element belongs, and keep
* track of the number of elements in each cluster (needed to calculate the
* average).
*/
for (j = 0; j < nelements; j++) {
number[j] = 1;
clusterid[j] = j;
}
// ip, jp, and distance;
var r = {};
// result array contains array of int left, int right, float distance;
for (n = nelements; n > 1; n--) {
phantasus.HCluster.findClosestPair(n, distmatrix, r);
result[nelements - n] = {};
result[nelements - n].distance = r.distance;
var is = r.ip;
var js = r.jp;
/* Save result */
result[nelements - n].left = clusterid[is];
result[nelements - n].right = clusterid[js];
/* Fix the distances */
var sum = number[is] + number[js];
for (j = 0; j < js; j++) {
distmatrix[js][j] = distmatrix[is][j] * number[is]
+ distmatrix[js][j] * number[js];
distmatrix[js][j] /= sum;
}
for (j = js + 1; j < is; j++) {
distmatrix[j][js] = distmatrix[is][j] * number[is]
+ distmatrix[j][js] * number[js];
distmatrix[j][js] /= sum;
}
for (j = is + 1; j < n; j++) {
distmatrix[j][js] = distmatrix[j][is] * number[is]
+ distmatrix[j][js] * number[js];
distmatrix[j][js] /= sum;
}
for (j = 0; j < is; j++) {
distmatrix[is][j] = distmatrix[n - 1][j];
}
for (j = is + 1; j < n - 1; j++) {
distmatrix[j][is] = distmatrix[n - 1][j];
}
/* Update number of elements in the clusters */
number[js] = sum;
number[is] = number[n - 1];
/* Update clusterids */
clusterid[js] = n - nelements - 1;
clusterid[is] = clusterid[n - 1];
}
return result;
};
phantasus.CollapseDataset = function (dataset, collapseToFields,
summarizeFunction, shallowCopy) {
var vectors = [];
var nfields = collapseToFields.length;
for (var i = 0; i < nfields; i++) {
var v = dataset.getRowMetadata().getByName(collapseToFields[i]);
if (!v) {
throw collapseToFields[i]
+ ' not found. Available fields are '
+ phantasus.MetadataUtil.getMetadataNames(dataset
.getRowMetadata());
}
vectors.push(v);
}
var idToIndices = phantasus.VectorUtil.createValuesToIndicesMap(vectors);
var collapsedDataset = new phantasus.Dataset({
name: dataset.getName(),
rows: idToIndices.size(),
columns: dataset.getColumnCount(),
dataType: 'Float32'
});
var nseries = dataset.getSeriesCount();
for (var series = 1; series < nseries; series++) {
collapsedDataset.addSeries({
name: dataset.getName(i),
dataType: 'Float32'
});
}
if (shallowCopy) {
collapsedDataset.setColumnMetadata(dataset.getColumnMetadata());
} else {
phantasus.MetadataUtil.copy(dataset.getColumnMetadata(),
collapsedDataset.getColumnMetadata());
}
var collapseToVectors = [];
for (var i = 0; i < nfields; i++) {
collapseToVectors.push(collapsedDataset.getRowMetadata().add(
collapseToFields[i]));
}
var counter = 0;
idToIndices
.forEach(function (rowIndices, key) {
// collapse each column separately
var slice = phantasus.DatasetUtil.slicedView(dataset,
rowIndices, null);
var view = new phantasus.DatasetColumnView(slice);
for (var series = 0; series < nseries; series++) {
view.setSeriesIndex(series);
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
view.setIndex(j);
collapsedDataset.setValue(counter, j,
summarizeFunction(view), series);
}
}
for (var i = 0; i < nfields; i++) {
var collapsedToVector = collapseToVectors[i];
var vector = vectors[i];
collapsedToVector.setValue(counter, vector
.getValue(rowIndices[0]));
}
counter++;
});
if (nfields === 1) {
var newVector = collapseToVectors[0];
vectors[0].getProperties().forEach(function (val, key) {
if (!phantasus.VectorKeys.COPY_IGNORE.has(key)) {
newVector.properties.set(key, val);
}
});
}
return collapsedDataset;
};
phantasus.SelectRow = function (dataset, collapseToFields,
summarizeFunction, shallowCopy) {
var vectors = [];
var nfields = collapseToFields.length;
for (var i = 0; i < nfields; i++) {
var v = dataset.getRowMetadata().getByName(collapseToFields[i]);
if (!v) {
throw collapseToFields[i]
+ ' not found. Available fields are '
+ phantasus.MetadataUtil.getMetadataNames(dataset
.getRowMetadata());
}
vectors.push(v);
}
var idToIndices = phantasus.VectorUtil.createValuesToIndicesMap(vectors);
var collapsedDataset = new phantasus.Dataset({
name: dataset.getName(),
rows: idToIndices.size(),
columns: dataset.getColumnCount(),
dataType: 'Float32'
});
var nseries = dataset.getSeriesCount();
for (var series = 1; series < nseries; series++) {
collapsedDataset.addSeries({
name: dataset.getName(i),
dataType: 'Float32'
});
}
if (shallowCopy) {
collapsedDataset.setColumnMetadata(dataset.getColumnMetadata());
} else {
phantasus.MetadataUtil.copy(dataset.getColumnMetadata(),
collapsedDataset.getColumnMetadata());
}
/*var collapseToVectors = [];
for (var i = 0; i < nfields; i++) {
collapseToVectors.push(collapsedDataset.getRowMetadata().add(
collapseToFields[i]));
}*/
var names = phantasus.MetadataUtil.getMetadataNames(dataset.getRowMetadata());
var collapseToVectors = [];
var allvectors = [];
for (var i = 0; i < names.length; i++) {
var v = dataset.getRowMetadata().getByName(names[i]);
collapseToVectors.push(collapsedDataset.getRowMetadata().add(names[i]));
allvectors.push(v);
}
var counter = 0;
idToIndices
.forEach(function (rowIndices, key) {
var slice = phantasus.DatasetUtil.slicedView(dataset,
rowIndices, null);
var view = new phantasus.DatasetRowView(slice);
var indexInDataset;
for (var series = 0; series < nseries; series++) {
view.setSeriesIndex(series);
var chosenIndex = summarizeFunction(view).index;
view.setIndex(chosenIndex);
indexInDataset = view.dataset.rowIndices[chosenIndex];
for (var j = 0, ncols = dataset.getColumnCount(); j < ncols; j++) {
collapsedDataset.setValue(counter, j,
view.getValue(j), series);
}
}
for (var i = 0; i < names.length; i++) {
var collapsedToVector = collapseToVectors[i];
var vector = allvectors[i];
collapsedToVector.setValue(counter, vector
.getValue(indexInDataset));
}
counter++;
});
return collapsedDataset;
};
phantasus.CompleteLinkage = function (nelements, distmatrix) {
var j;
var n;
var clusterid = []; // new var[nelements];
var result = [];// new Node[nelements - 1];
for (var i = 0; i < nelements - 1; i++) {
result[i] = {
left: 0,
right: 0,
distance: 0
};
}
/* Setup a list specifying to which cluster a gene belongs */
for (j = 0; j < nelements; j++) {
clusterid[j] = j;
}
var r = {};
for (n = nelements; n > 1; n--) {
phantasus.HCluster.findClosestPair(n, distmatrix, r);
result[nelements - n].distance = r.distance;
var is = r.ip;
var js = r.jp;
/* Fix the distances */
for (j = 0; j < js; j++) {
distmatrix[js][j] = Math.max(distmatrix[is][j], distmatrix[js][j]);
}
for (j = js + 1; j < is; j++) {
distmatrix[j][js] = Math.max(distmatrix[is][j], distmatrix[j][js]);
}
for (j = is + 1; j < n; j++) {
distmatrix[j][js] = Math.max(distmatrix[j][is], distmatrix[j][js]);
}
for (j = 0; j < is; j++) {
distmatrix[is][j] = distmatrix[n - 1][j];
}
for (j = is + 1; j < n - 1; j++) {
distmatrix[j][is] = distmatrix[n - 1][j];
}
/* Update clusterids */
result[nelements - n].left = clusterid[is];
result[nelements - n].right = clusterid[js];
clusterid[js] = n - nelements - 1;
clusterid[is] = clusterid[n - 1];
}
return result;
};
phantasus.HClusterGroupBy = function (dataset, groupByFieldNames, distanceFunction, linkageMethod) {
var model = dataset.getRowMetadata();
var vectors = phantasus.MetadataUtil.getVectors(dataset.getRowMetadata(),
groupByFieldNames);
var idToIndices = phantasus.VectorUtil.createValuesToIndicesMap(vectors);
var reorderedIndices = [];
var offset = 0;
var root = {
id: -1,
children: [],
height: 0
};
var tree = {
maxHeight: 0,
rootNode: root,
leafNodes: [],
nLeafNodes: 0
};
idToIndices
.forEach(function (rowIndices, id) {
var originalIndicesForGroup = idToIndices.get(id);
var subset = phantasus.DatasetUtil.slicedView(dataset,
originalIndicesForGroup, null);
var hcl;
var distanceMatrix = phantasus.HCluster.computeDistanceMatrix(
subset, distanceFunction);
hcl = new phantasus.HCluster(distanceMatrix, linkageMethod);
var reorderedGroupIndices = hcl.reorderedIndices;
for (var i = 0, rows = subset.getRowCount(); i < rows; i++) {
var originalIndex = originalIndicesForGroup[reorderedGroupIndices[i]];
reorderedIndices.push(originalIndex);
}
phantasus.DendrogramUtil.dfs(hcl.tree.rootNode, function (node) {
node.index += offset;
node.minIndex += offset;
node.maxIndex += offset;
node.id += offset;
return true;
});
if (hcl.tree.leafNodes.length === 0) {
tree.leafNodes = tree.leafNodes
.concat([hcl.tree.rootNode]);
} else {
tree.leafNodes = tree.leafNodes.concat(hcl.tree.leafNodes);
}
root.children.push(hcl.tree.rootNode);
if (!isNaN(hcl.tree.maxHeight)) {
tree.maxHeight = Math.max(tree.maxHeight,
hcl.tree.maxHeight);
}
offset += subset.getRowCount();
});
tree.nLeafNodes = tree.leafNodes.length;
tree.rootNode.height = tree.maxHeight;
this.tree = tree;
this.reorderedIndices = reorderedIndices;
};
/**
*
* @param distmatrix
* @param linkageAlgorithm {Function}
* @constructor
*/
phantasus.HCluster = function (distmatrix, linkageAlgorithm) {
var nelements = distmatrix.length;
var nNodes = nelements - 1;
if (nNodes === -1) {
var root = {
id: 0,
height: 0,
index: 0,
minIndex: 0,
maxIndex: 0,
depth: 0
};
this.tree = {
maxHeight: 0,
rootNode: root,
leafNodes: [],
nLeafNodes: 0
};
this.reorderedIndices = [0];
return;
}
// tree array contains array of int left, int right, float distance;
var tree = linkageAlgorithm(nelements, distmatrix);
var nodeorder = []; // nNodes;
var nodecounts = [];// nNodes;
var order = []; // nelements;
var nodeID = []; // nNodes;
for (var i = 0; i < nelements; i++) {
order[i] = i;
}
var leftIds = []; // nNodes
var rightIds = []; // nNodes
for (var i = 0; i < nNodes; i++) {
var min1 = tree[i].left;
var min2 = tree[i].right;
/* min1 and min2 are the elements that are to be joined */
var order1;
var order2;
var counts1;
var counts2;
var ID1;
var ID2;
nodeID[i] = nNodes + (i + 2);
if (min1 < 0) {
var index1 = -min1 - 1;
order1 = nodeorder[index1];
counts1 = nodecounts[index1];
ID1 = nodeID[index1];
tree[i].distance = Math
.max(tree[i].distance, tree[index1].distance);
} else {
order1 = order[min1];
counts1 = 1;
ID1 = min1;
}
if (min2 < 0) {
var index2 = -min2 - 1;
order2 = nodeorder[index2];
counts2 = nodecounts[index2];
ID2 = nodeID[index2];
tree[i].distance = Math
.max(tree[i].distance, tree[index2].distance);
} else {
order2 = order[min2];
counts2 = 1;
ID2 = min2;
}
leftIds[i] = ID1;
rightIds[i] = ID2;
nodecounts[i] = counts1 + counts2;
nodeorder[i] = (counts1 * order1 + counts2 * order2)
/ (counts1 + counts2);
}
var reorderedIndices = phantasus.HCluster.treeSort(nNodes, order, nodeorder,
nodecounts, tree);
var idToIndex = {};
for (var i = 0, length = reorderedIndices.length; i < length; i++) {
var index = reorderedIndices[i];
idToIndex[index] = i;
}
var nodeIdToNode = {};
var node;
for (var i = 0, length = nodeID.length; i < length; i++) {
var id = nodeID[i];
var leftId = leftIds[i];
var lnode = nodeIdToNode[leftId];
if (lnode === undefined) {
lnode = {
id: leftId
};
var index = idToIndex[leftId];
lnode.index = index;
lnode.minIndex = index;
lnode.maxIndex = index;
nodeIdToNode[lnode.id] = lnode;
}
var rightId = rightIds[i];
var rnode = nodeIdToNode[rightId];
if (rnode === undefined) {
rnode = {
id: rightId
};
var index = idToIndex[rightId];
rnode.index = index;
rnode.minIndex = index;
rnode.maxIndex = index;
nodeIdToNode[rnode.id] = rnode;
}
node = {
id: id,
children: lnode.index < rnode.index ? [lnode, rnode] : [rnode, lnode],
height: tree[i].distance,
index: (rnode.index + lnode.index) / 2.0
};
node.minIndex = Math.min(rnode.minIndex, lnode.minIndex);
node.maxIndex = Math.max(rnode.maxIndex, lnode.maxIndex);
lnode.parent = node;
rnode.parent = node;
nodeIdToNode[node.id] = node;
}
this.reorderedIndices = reorderedIndices;
var leafNodes = [];
for (var i = 0, length = reorderedIndices.length; i < length; i++) {
var leaf = nodeIdToNode[reorderedIndices[i]];
leaf.height = 0;
leafNodes.push(leaf);
}
phantasus.DendrogramUtil.setNodeDepths(node);
this.tree = {
maxHeight: node.height,
rootNode: node,
leafNodes: leafNodes,
nLeafNodes: leafNodes.length
};
};
/*
* Searches the distance matrix to find the pair with the shortest distance
* between them. The indices of the pair are returned in ip and jp; the distance
* itself is returned by the function.
*
* @param n The number of elements in the distance matrix.
*
* @param distmatrix. A ragged array containing the distance matrix. The number
* of columns in each row is one less than the row index.
*
* @return The first and second indices of the pair with the shortest distance.
*/
phantasus.HCluster.findClosestPair = function (n, distmatrix, r) {
var i, j;
var temp;
var distance = distmatrix[1][0];
var ip = 1;
var jp = 0;
for (i = 1; i < n; i++) {
for (j = 0; j < i; j++) {
temp = distmatrix[i][j];
if (temp < distance) {
distance = temp;
ip = i;
jp = j;
}
}
}
r.distance = distance;
r.ip = ip;
r.jp = jp;
};
/**
* Creates a ragged array with the number of rows equal to the number of rows in
* the dataset. Each row in the array has n columns where n is the row index.
*
* @param dataset
* @param distanceFunction
* The distance function. Use 0 to assume dataset is already a
* distance matrix, 1 to assume dataset is already a similarity
* matrix.
* @return the distance matrix
*/
phantasus.HCluster.computeDistanceMatrix = function (dataset, distanceFunction) {
/* Set up the ragged array */
var matrix = [];
var n = dataset.getRowCount();
for (var i = 1; i < n; i++) {
matrix[i] = new Float32Array(i);
}
// assume dataset is already a distance matrix
if (distanceFunction === 0) {
for (var i = 1; i < n; i++) {
for (var j = 0; j < i; j++) {
matrix[i][j] = dataset.getValue(i, j);
}
}
} else if (distanceFunction === 1) { // already a similarity matrix
var max = -Number.MAX_VALUE;
for (var i = 1; i < n; i++) {
for (var j = 0; j < i; j++) {
var value = dataset.getValue(i, j);
if (!isNaN(value)) {
max = Math.max(value, max);
}
}
}
for (var i = 1; i < n; i++) {
for (var j = 0; j < i; j++) {
matrix[i][j] = max - dataset.getValue(i, j);
}
}
} else {
var list1 = new phantasus.DatasetRowView(dataset);
var list2 = new phantasus.DatasetRowView(dataset);
/* Calculate the distances and save them in the ragged array */
for (var i = 1; i < n; i++) {
list1.setIndex(i);
for (var j = 0; j < i; j++) {
matrix[i][j] = distanceFunction(list1, list2.setIndex(j));
}
}
}
return matrix;
};
phantasus.HCluster.treeSort = function (nNodes, order, nodeorder, nodecounts,
tree) {
var nElements = nNodes + 1;
var i;
var neworder = []; // nElements;
var clusterids = []; // nElements;
for (i = 0; i < nElements; i++) {
clusterids[i] = i;
neworder[i] = 0;
}
for (i = 0; i < nNodes; i++) {
var i1 = tree[i].left;
var i2 = tree[i].right;
var order1 = (i1 < 0) ? nodeorder[-i1 - 1] : order[i1];
var order2 = (i2 < 0) ? nodeorder[-i2 - 1] : order[i2];
var count1 = (i1 < 0) ? nodecounts[-i1 - 1] : 1;
var count2 = (i2 < 0) ? nodecounts[-i2 - 1] : 1;
/*
* If order1 and order2 are equal, their order is determined by the
* order in which they were clustered
*/
if (i1 < i2) {
var increase = (order1 < order2) ? count1 : count2;
var j;
for (j = 0; j < nElements; j++) {
var clusterid = clusterids[j];
if ((clusterid == i1) && (order1 >= order2)) {
neworder[j] += increase;
}
if ((clusterid == i2) && (order1 < order2)) {
neworder[j] += increase;
}
if ((clusterid == i1) || (clusterid == i2)) {
clusterids[j] = -i - 1;
}
}
} else {
var increase = (order1 <= order2) ? count1 : count2;
var j;
for (j = 0; j < nElements; j++) {
var clusterid = clusterids[j];
if ((clusterid == i1) && (order1 > order2)) {
neworder[j] += increase;
}
if ((clusterid == i2) && (order1 <= order2)) {
neworder[j] += increase;
}
if ((clusterid == i1) || (clusterid == i2)) {
clusterids[j] = -i - 1;
}
}
}
}
return phantasus.Util.indexSort(neworder, true);
};
/**
* Clustering algorithm based on David Arthur and Sergei Vassilvitski k-means++ algorithm.
* @param <T> type of the points to cluster
* @see <a href="http://en.wikipedia.org/wiki/K-means%2B%2B">K-means++ (wikipedia)</a>
* @since 3.2
*/
/**
* @param dataset The dataset to cluster the rows of
* @param distance
* @param k The number of clusters
* @param maxIterations The maximum number of iterations
* @constructor
*/
phantasus.KMeansPlusPlusClusterer = function (k, maxIterations, distanceFunction) {
var distance = function (points1, points2) {
return distanceFunction(phantasus.VectorUtil.arrayAsVector(points1.getPoint()), phantasus.VectorUtil.arrayAsVector(points2.getPoint()));
};
function nextInt(upperBound) {
return (Math.floor(Math.random() * upperBound)) | 0;
}
function nextDouble() {
return Math.random();
}
function PointWrapper(point) {
this.getPoint = function () {
return point;
};
}
function CentroidCluster(center) {
var centroidPoints = [];
this.addPoint = function (p) {
centroidPoints.push(p);
};
this.getPoints = function () {
return centroidPoints;
};
this.getCenter = function () {
return center;
};
}
/**
* Runs the K-means++ clustering algorithm.
*
* @param points the points to cluster
* @return a list of clusters containing the points
*/
function cluster(points) {
// number of clusters has to be smaller or equal the number of data points
if (points.length < k) {
throw 'Too many clusters';
}
// create the initial clusters
var clusters = chooseInitialCenters(points);
// create an array containing the latest assignment of a point to a cluster
// no need to initialize the array, as it will be filled with the first assignment
var assignments = new Int32Array(points.length);
assignPointsToClusters(clusters, points, assignments);
// iterate through updating the centers until we're done
var max = (maxIterations < 0) ? Number.MAX_VALUE : maxIterations;
for (var count = 0; count < max; count++) {
var emptyCluster = false;
var newClusters = [];
for (var clusterIndex = 0; clusterIndex < clusters.length; clusterIndex++) {
var cluster = clusters[clusterIndex];
var newCenter;
if (cluster.getPoints().length === 0) {
newCenter = getPointFromLargestVarianceCluster(clusters);
emptyCluster = true;
} else {
newCenter = centroidOf(cluster.getPoints(), cluster.getCenter().getPoint().size());
}
newClusters.push(new CentroidCluster(newCenter));
}
var changes = assignPointsToClusters(newClusters, points, assignments);
clusters = newClusters;
// if there were no more changes in the point-to-cluster assignment
// and there are no empty clusters left, return the current clusters
if (changes === 0 && !emptyCluster) {
return clusters;
}
}
return clusters;
}
/**
* Adds the given points to the closest {@link Cluster}.
*
* @param clusters the {@link Cluster}s to add the points to
* @param points the points to add to the given {@link Cluster}s
* @param assignments points assignments to clusters
* @return the number of points assigned to different clusters as the iteration before
*/
function assignPointsToClusters(clusters, points, assignments) {
var assignedDifferently = 0;
var pointIndex = 0;
for (var i = 0; i < points.length; i++) {
var p = points[i];
var clusterIndex = getNearestCluster(clusters, p);
if (clusterIndex != assignments[pointIndex]) {
assignedDifferently++;
}
var cluster = clusters[clusterIndex];
cluster.addPoint(p);
assignments[pointIndex++] = clusterIndex;
}
return assignedDifferently;
}
/**
* Use K-means++ to choose the initial centers.
*
* @param points the points to choose the initial centers from
* @return the initial centers
*/
function chooseInitialCenters(points) {
// Convert to list for indexed access. Make it unmodifiable, since removal of items
// would screw up the logic of this method.
var pointList = points;
// The number of points in the list.
var numPoints = pointList.length;
// Set the corresponding element in this array to indicate when
// elements of pointList are no longer available.
var taken = new Array(numPoints);
for (var i = 0; i < taken.length; i++) {
taken[i] = false;
}
// The resulting list of initial centers.
var resultSet = [];
// Choose one center uniformly at random from among the data points.
var firstPointIndex = nextInt(numPoints);
var firstPoint = pointList[firstPointIndex];
resultSet.push(new CentroidCluster(firstPoint));
// Must mark it as taken
taken[firstPointIndex] = true;
// To keep track of the minimum distance squared of elements of
// pointList to elements of resultSet.
var minDistSquared = new Float32Array(numPoints);
// Initialize the elements. Since the only point in resultSet is firstPoint,
// this is very easy.
for (var i = 0; i < numPoints; i++) {
if (i !== firstPointIndex) { // That point isn't considered
var d = distance(firstPoint, pointList[i]);
minDistSquared[i] = d * d;
}
}
while (resultSet.length < k) {
// Sum up the squared distances for the points in pointList not
// already taken.
var distSqSum = 0.0;
for (var i = 0; i < numPoints; i++) {
if (!taken[i]) {
distSqSum += minDistSquared[i];
}
}
// Add one new data point as a center. Each point x is chosen with
// probability proportional to D(x)2
var r = nextDouble() * distSqSum;
// The index of the next point to be added to the resultSet.
var nextPointIndex = -1;
// Sum through the squared min distances again, stopping when
// sum >= r.
var sum = 0.0;
for (var i = 0; i < numPoints; i++) {
if (!taken[i]) {
sum += minDistSquared[i];
if (sum >= r) {
nextPointIndex = i;
break;
}
}
}
// If it's not set to >= 0, the point wasn't found in the previous
// for loop, probably because distances are extremely small. Just pick
// the last available point.
if (nextPointIndex === -1) {
for (var i = numPoints - 1; i >= 0; i--) {
if (!taken[i]) {
nextPointIndex = i;
break;
}
}
}
// We found one.
if (nextPointIndex >= 0) {
var p = pointList[nextPointIndex];
resultSet.push(new CentroidCluster(p));
// Mark it as taken.
taken[nextPointIndex] = true;
if (resultSet.length < k) {
// Now update elements of minDistSquared. We only have to compute
// the distance to the new center to do this.
for (var j = 0; j < numPoints; j++) {
// Only have to worry about the points still not taken.
if (!taken[j]) {
var d = distance(p, pointList[j]);
var d2 = d * d;
if (d2 < minDistSquared[j]) {
minDistSquared[j] = d2;
}
}
}
}
} else {
// None found --
// Break from the while loop to prevent
// an infinite loop.
break;
}
}
return resultSet;
}
/**
* Get a random point from the {@link Cluster} with the largest distance variance.
*
* @param clusters the {@link Cluster}s to search
* @return a random point from the selected cluster
* @throws ConvergenceException if clusters are all empty
*/
function getPointFromLargestVarianceCluster(clusters) {
var maxVariance = -Number.MAX_VALUE;
var selected = null;
for (var clusterIndex = 0; clusterIndex < clusters.length; clusterIndex++) {
var cluster = clusters[clusterIndex];
if (cluster.getPoints().length > 0) {
// compute the distance variance of the current cluster
var center = cluster.getCenter();
var points = cluster.getPoints();
var distances = new Float32Array(points.length);
for (var i = 0; i < points.length; i++) {
distances[i] = distance(points[i], center);
}
var variance = phantasus.Variance(phantasus.VectorUtil.arrayAsVector(distances));
// select the cluster with the largest variance
if (variance > maxVariance) {
maxVariance = variance;
selected = cluster;
}
}
}
// did we find at least one non-empty cluster ?
if (selected == null) {
throw 'All clusters are empty';
}
// extract a random point from the cluster
var selectedPoints = selected.getPoints();
return selectedPoints.splice(nextInt(selectedPoints.length), 1);
}
// /**
// * Get the point farthest to its cluster center
// *
// * @param clusters the {@link Cluster}s to search
// * @return point farthest to its cluster center
// * @throws ConvergenceException if clusters are all empty
// */
// function getFarthestPoint(clusters) {
//
// var maxDistance = Number.NEGATIVE_INFINITY;
// var selectedCluster = null;
// var selectedPoint = -1;
// for (var clusterIndex = 0; clusterIndex < clusters.length; clusterIndex++) {
// var c = clusters[clusterIndex];
// // get the farthest point
// var center = cluster.getCenter();
// var points = cluster.getPoints();
// for (var i = 0; i < points.length; ++i) {
// var d = distance(points[i], center);
// if (d > maxDistance) {
// maxDistance = d;
// selectedCluster = cluster;
// selectedPoint = i;
// }
// }
//
// }
//
// // did we find at least one non-empty cluster ?
// if (selectedCluster == null) {
// throw 'Empty cluster';
// }
//
// return selectedCluster.getPoints().splice(selectedPoint, 1);
//
// }
/**
* Returns the nearest {@link Cluster} to the given point
*
* @param clusters the {@link Cluster}s to search
* @param point the point to find the nearest {@link Cluster} for
* @return the index of the nearest {@link Cluster} to the given point
*/
function getNearestCluster(clusters, point) {
var minDistance = Number.MAX_VALUE;
var clusterIndex = 0;
var minCluster = 0;
for (var i = 0; i < clusters.length; i++) {
var c = clusters[i];
var d = distance(point, c.getCenter());
if (d < minDistance) {
minDistance = d;
minCluster = clusterIndex;
}
clusterIndex++;
}
return minCluster;
}
/**
* Computes the centroid for a set of points.
*
* @param points the set of points
* @param dimension the point dimension
* @return the computed centroid for the set of points
*/
function centroidOf(points, dimension) {
var centroid = new Float32Array(dimension);
for (var i = 0; i < centroid.length; i++) {
var sum = 0;
var count = 0;
for (var pointIndex = 0; pointIndex < points.length; pointIndex++) {
var p = points[pointIndex];
var point = p.getPoint();
var val = point.getValue(i);
if (!isNaN(val)) {
sum += val;
count++;
}
}
centroid[i] = (sum / count);
}
return new PointWrapper(phantasus.VectorUtil.arrayAsVector(centroid));
}
this.execute = function (vectors) {
var points = [];
// cluster rows
var npoints = vectors.length;
for (var i = 0; i < npoints; i++) {
var p = new PointWrapper(vectors[i]);
p.i = i;
points.push(p);
}
return cluster(points);
};
this.cluster = cluster;
};
phantasus.PermutationPValues = function (dataset, aIndices, bIndices,
numPermutations, f, continuousList) {
var numRows = dataset.getRowCount();
/** unpermuted scores */
var scores = new Float32Array(numRows);
/** Whether to smooth p values */
var smoothPValues = true;
var permuter;
var permutationScore;
if (aIndices != null) {
var list1 = new phantasus.DatasetRowView(new phantasus.SlicedDatasetView(
dataset, null, aIndices));
var list2 = new phantasus.DatasetRowView(new phantasus.SlicedDatasetView(
dataset, null, bIndices));
dataset = new phantasus.SlicedDatasetView(dataset, null, aIndices
.concat(bIndices));
permuter = new phantasus.UnbalancedPermuter(aIndices.length,
bIndices.length);
permutationScore = new phantasus.TwoClassPermutationScore();
permutationScore.init(dataset, f);
for (var i = 0; i < numRows; i++) {
scores[i] = f(list1.setIndex(i), list2.setIndex(i));
}
} else { // continuous
permuter = new phantasus.UnbalancedContinuousPermuter(continuousList.size());
permutationScore = new phantasus.ContinuousPermutationScore(continuousList);
permutationScore.init(dataset, f);
var list = new phantasus.DatasetRowView(dataset);
for (var i = 0; i < numRows; i++) {
scores[i] = f(continuousList, list.setIndex(i));
}
}
var rowSpecificPValues = new Float32Array(numRows);
for (var permutationIndex = 0; permutationIndex < numPermutations; permutationIndex++) {
permutationScore.setPermutation(permuter.next());
for (var i = 0; i < numRows; i++) {
var permutedScore = permutationScore.getScore(i);
var score = scores[i];
if (permutedScore >= score) {
rowSpecificPValues[i]++;
}
}
}
var N = numPermutations;
var kArray = new Uint32Array(numRows);
for (var i = 0; i < numRows; i++) {
var k = rowSpecificPValues[i];
kArray[i] = k;
var p;
if (smoothPValues) {
p = (k + 1) / (N + 2);
} else {
p = k / N;
}
// 2-sided p-value
var oneMinusP = 1 - p;
if (oneMinusP < p) {
p = oneMinusP;
}
p *= 2;
if (p === 0) {
// ensure not degenerate case where profile is
// completely
// flat
// TODO handle cases where profile is flat (but not
// completely)
var val = dataset.getValue(i, 0);
var flat = true;
for (var j = 1, cols = dataset.getColumnCount(); j < cols && flat; j++) {
if (dataset.getValue(i, j) != val) {
flat = false;
}
}
if (flat) {
p = 1;
}
}
rowSpecificPValues[i] = p;
}
this.rowSpecificPValues = rowSpecificPValues;
this.k = kArray;
this.fdr = phantasus.FDR_BH(rowSpecificPValues);
this.scores = scores;
};
phantasus.PermutationPValues.prototype = {
getBonferroni: function (index) {
return Math.min(this.rowSpecificPValues[index] * this.numRows, 1);
}
};
phantasus.UnbalancedContinuousPermuter = function (size) {
var indices = new Uint32Array(size);
for (var i = 0; i < indices.length; i++) {
indices[i] = i;
}
var n = indices.length;
// Returns a random integer between min (included) and max (included)
// Using Math.round() will give you a non-uniform distribution!
function getRandomIntInclusive(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
this.next = function () {
// shuffle indices array
for (var i = n - 1; i >= 1; i--) {
var j = getRandomIntInclusive(0, i); // random integer such that
// 0 ≤ j ≤ i
// exchange a[j] and a[i]
var tmp = indices[j];
indices[j] = indices[i];
indices[i] = tmp;
}
return indices;
};
};
phantasus.UnbalancedPermuter = function (numClassZero, numClassOne) {
var assignments = new Uint32Array(numClassZero + numClassOne);
var indices = new Uint32Array(numClassZero + numClassOne);
for (var i = 0; i < indices.length; i++) {
indices[i] = i;
}
var n = indices.length;
// Returns a random integer between min (included) and max (included)
// Using Math.round() will give you a non-uniform distribution!
function getRandomIntInclusive(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
this.next = function () {
// shuffle indices array
for (var i = n - 1; i >= 1; i--) {
var j = getRandomIntInclusive(0, i); // random integer such that
// 0 ≤ j ≤ i
// exchange a[j] and a[i]
var tmp = indices[j];
indices[j] = indices[i];
indices[i] = tmp;
}
// pick 1st numClassOne indices to be class one
for (var i = 0; i < n; i++) {
assignments[i] = 0;
}
for (var i = 0; i < numClassOne; i++) {
assignments[indices[i]] = 1;
}
return assignments;
};
};
phantasus.TwoClassPermutationScore = function () {
this.classZeroView = null;
this.classOneView = null;
};
phantasus.TwoClassPermutationScore.prototype = {
getScore: function (index) {
this.classZeroView.setIndex(index);
this.classOneView.setIndex(index);
return this.f(this.classZeroView, this.classOneView);
},
init: function (dataset, f) {
this.dataset = dataset;
this.classZeroView = new phantasus.DatasetRowView(dataset);
this.classOneView = new phantasus.DatasetRowView(dataset);
this.f = f;
},
setPermutation: function (permutedAssignments) {
var zeroIndices = [];
var oneIndices = [];
for (var i = 0, length = permutedAssignments.length; i < length; i++) {
if (permutedAssignments[i] === 0) {
zeroIndices.push(i);
} else {
oneIndices.push(i);
}
}
this.classZeroView.setDataset(new phantasus.SlicedDatasetView(
this.dataset, null, zeroIndices));
this.classOneView.setDataset(new phantasus.SlicedDatasetView(
this.dataset, null, oneIndices));
}
};
phantasus.ContinuousPermutationScore = function (referenceList) {
this.referenceList = referenceList;
};
phantasus.ContinuousPermutationScore.prototype = {
getScore: function (index) {
this.datasetRowView.setIndex(index);
return this.f(this.referenceListView, this.datasetRowView);
},
init: function (dataset, f) {
this.dataset = dataset;
this.referenceListView = null;
this.datasetRowView = new phantasus.DatasetRowView(dataset);
this.f = f;
},
setPermutation: function (indices) {
this.referenceListView = new phantasus.SlicedVector(this.referenceList, indices);
}
};
phantasus.Ranking = function (values) {
var ranks = [];
for (var i = 0, length = values.length; i < length; i++) {
ranks.push({
value: values[i],
position: i
});
}
if (ranks.length === 0) {
return [];
}
ranks.sort(function (a, b) {
return (a.value < b.value ? -1 : (a.value === b.value ? 0 : 1));
});
var out = [];
var pos = 1; // position in sorted array
out[ranks[0].position] = pos;
var tiesTrace = [];
tiesTrace.push(ranks[0].position);
for (var i = 1; i < ranks.length; i++) {
if (ranks[i].value > ranks[i - 1].value) {
// tie sequence has ended (or had length 1)
pos = i + 1;
if (tiesTrace.length > 1) { // if seq is nontrivial, resolve
phantasus.Ranking.fillAverage(out, tiesTrace);
}
tiesTrace = [];
tiesTrace.push(ranks[i].position);
} else {
// tie sequence continues
tiesTrace.push(ranks[i].position);
}
out[ranks[i].position] = pos;
}
if (tiesTrace.length > 1) { // handle tie sequence at end
phantasus.Ranking.fillAverage(out, tiesTrace);
}
return out;
};
phantasus.Ranking.fill = function (data, tiesTrace, value) {
for (var i = 0, length = tiesTrace.length; i < length; i++) {
data[tiesTrace[i]] = value;
}
};
phantasus.Ranking.fillAverage = function (ranks, tiesTrace) {
var c = ranks[tiesTrace[0]];
// length of sequence of tied ranks
var length = tiesTrace.length;
phantasus.Ranking.fill(ranks, tiesTrace, (2 * c + length - 1) / 2);
};
phantasus.SingleLinkage = function (nelements, distmatrix) {
var i, j, k;
var nnodes = nelements - 1;
var temp = []; // var[nnodes];
var index = []; // new var[nelements];
var vector = []; // var[nnodes];
var result = []; // new Node[nelements];
for (i = 0; i < nelements; i++) {
result[i] = {
left: 0,
right: 0,
distance: 0
};
}
for (i = 0; i < nnodes; i++) {
vector[i] = i;
}
for (i = 0; i < nelements; i++) {
result[i].distance = Number.MAX_VALUE;
for (j = 0; j < i; j++) {
temp[j] = distmatrix[i][j];
}
for (j = 0; j < i; j++) {
k = vector[j];
if (result[j].distance >= temp[j]) {
if (result[j].distance < temp[k]) {
temp[k] = result[j].distance;
}
result[j].distance = temp[j];
vector[j] = i;
} else if (temp[j] < temp[k]) {
temp[k] = temp[j];
}
}
for (j = 0; j < i; j++) {
if (result[j].distance >= result[vector[j]].distance) {
vector[j] = i;
}
}
}
for (i = 0; i < nnodes; i++) {
result[i].left = i;
}
result.sort(function (node1, node2) {
var term1 = node1.distance;
var term2 = node2.distance;
if (term1 < term2)
return -1;
if (term1 > term2)
return +1;
return 0;
});
for (i = 0; i < nelements; i++) {
index[i] = i;
}
for (i = 0; i < nnodes; i++) {
j = result[i].left;
k = vector[j];
result[i].left = index[j];
result[i].right = index[k];
index[k] = -i - 1;
}
var result2 = []; // new Node[nelements - 1];
for (i = 0; i < nelements - 1; i++) {
result2[i] = result[i];
}
return result2;
};
})(typeof window !== 'undefined' ? window : this);