/** * rows and columns can contain field, renameTo, display, order * */ morpheus.HeatMap = function (options) { morpheus.Util.loadTrackingCode(); var _this = this; options = $ .extend( true, {}, { /** * The element in which to render to the heat map. */ el: null, /** * A File or URL to a GCT * 1.3, ' + 'GCT * 1.2, ' + 'MAF, ' + 'GMT, ' + ' * 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. *

* Example: Annotate rows matching * 'name' field in dataset to 'id' field in * file. *

* [{file:'https://MY_URL', datasetField:'name', fileField:'id'}] */ 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. * *

* Example: Group by the type and gender * metadata field. *

* * ['type', 'gender'] */ columnGroupBy: undefined, /** * Array of row metadata names to group the heat map by. * *

* Example: Group by the gene metadata field. *

* ['gene'] */ rowGroupBy: undefined, /** * Object that describes mapping of values to colors. * Type can be 'fixed' or 'relative'. Stepped indicates * whether color scheme is continuous (false) or * discrete (true). *

* Example: Use a fixed color scheme with color * stops at -100, -90, 90, and 100. *

* { type : 'fixed', stepped:false, map : [ { value : -100, color : * 'blue' }, { value : -90, color : 'white' }, { value : * 90, color : 'white' }, { value : 100, color : 'red' } ] }; */ colorScheme: undefined, /** * Array of metadata names and sort order. Use 0 for * ascending and 1 for descending. To sort by values use * modelIndices. * *

* Example: Sort ascending by gene, and then * descending by stdev *

* [{field:'gene', order:0}, {field:'stdev', * order:1}] */ rowSortBy: undefined, /** * Array of metadata names and sort order. Use 0 for * ascending and 1 for descending. * *

* Example: to sort ascending by gene, and then * descending by stdev *

* [{name:'gene', * order:0}, {name:'stdev', order:1}] */ columnSortBy: undefined, /** * URL to a dendrogram in Newick * format */ rowDendrogram: undefined, /** * URL to a dendrogram in Newick * format */ 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 render 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 render 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: tools : [ { * name : 'Marker Selection', * params : { * field : [ comparisonVector.getName() ], * class_a : [ 'A' ], class_b : [ 'B' ] }} ] */ tools: undefined, /** * Optional array of {name:string, values:[]} */ rowFilter: undefined, columnFilter: undefined, customUrls: undefined, // Custom urls for File>Open. 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 inheritFromParent: true, inheritFromParentOptions: { transpose: false }, structureUrlProvider: undefined, promises: undefined, // additional promises to wait // for renderReady: undefined, datasetReady: undefined, loadedCallback: undefined, name: undefined, rowsSortable: true, columnsSortable: true, popupEnabled: true, symmetric: false, keyboard: true, inlineTooltip: true, $loadingImage: $('
Loading...
'), /** * Whether this heat map tab can be closed */ closeable: true, toolbar: { zoom: true, tools: true, searchRows: true, searchColumns: true, sort: true, options: true, saveImage: true, saveDataset: true, openFile: true, filter: true, colorKey: true, searchValues: false } }, options); this.options = options; this.tooltipProvider = morpheus.HeatMapTooltipProvider; if (!options.el) { this.$el = $('
'); } else { this.$el = $(options.el); } this.$el.addClass('morpheus'); if (!options.landingPage) { options.landingPage = new morpheus.LandingPage(); options.landingPage.$el.prependTo(this.$el); } if (!this.options.name) { this.options.name = morpheus.Util .getBaseFileName(morpheus.Util .getFileName(this.options.dataset.file ? this.options.dataset.file : this.options.dataset)); } var isPrimary = this.options.parent == null; if (this.options.parent == null) { this.tabManager = this.options.tabManager ? this.options.tabManager : new morpheus.TabManager({ landingPage: this.options.landingPage }); // if (window.location.hostname.indexOf('clue.io') === -1 // && window.location.pathname.indexOf('cancer/software/morpheus') === // -1) { if (!morpheus.HelpMenu.ADDED) { // only show once per page morpheus.HelpMenu.ADDED = true; var $a = $(''); $a.tooltip({ placement: 'auto' }); // var $img = $a.find('img'); var $right = $('
'); $a.appendTo($right); new morpheus.HelpMenu().$el.appendTo($right); $right.appendTo(this.tabManager.$nav); } if (!this.options.tabManager) { this.tabManager.$nav.appendTo(this.$el); this.tabManager.$tabContent.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 = $('
'); 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' }); var tab = this.tabManager.add({ $el: this.$content, closeable: this.options.closeable, rename: true, title: this.options.name, object: this, focus: true }); if (options.$loadingImage) { options.$loadingImage.appendTo(this.$content); } this.tabId = tab.id; this.$tabPanel = tab.$panel; this.options.dataSource = !options.dataset ? '' : (options.dataset.file ? options.dataset.file : options.dataset); this._togglingInfoWindow = false; this.tooltipMode = 0; // 0=docked, 1=dialog, 2=follow 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 = morpheus.DatasetUtil.annotate({ annotations: options.rowAnnotations, isColumns: false }); rowDef.done(function (callbacks) { _this.whenLoaded = _this.whenLoaded.concat(callbacks); }); promises.push(rowDef); } if (options.columnAnnotations) { var columnDef = morpheus.DatasetUtil.annotate({ annotations: options.columnAnnotations, isColumns: true }); columnDef.done(function (callbacks) { _this.whenLoaded = _this.whenLoaded.concat(callbacks); }); promises.push(columnDef); } if (options.rowDendrogram !== undefined && _.isString(options.rowDendrogram)) { var rowDendrogramDeferred = morpheus.Util .getText(options.rowDendrogram); rowDendrogramDeferred.done(function (text) { _this.options.rowDendrogram = morpheus.AbstractDendrogram .parseNewick(text); }); promises.push(rowDendrogramDeferred); } if (options.columnDendrogram !== undefined && _.isString(options.columnDendrogram)) { var columnDendrogramDeferred = morpheus.Util .getText(options.columnDendrogram); columnDendrogramDeferred.done(function (text) { _this.options.columnDendrogram = morpheus.AbstractDendrogram .parseNewick(text); }); promises.push(columnDendrogramDeferred); } var heatMapLoaded = function () { if (typeof window !== 'undefined') { var resize = function () { _this.revalidate(); }; $(window).on('orientationchange resize', resize); _this.$content.on('remove', function () { $(window).off('orientationchange resize', resize); }); } _this.revalidate(); if (options.loadedCallback) { options.loadedCallback(_this); } _this.tabManager.setActiveTab(tab.id); _this.$el.trigger('heatMapLoaded', _this); }; if (morpheus.Util.isArray(options.dataset)) { var d = morpheus.DatasetUtil.readDatasetArray(options.dataset); d.fail(function (message) { if (_this.options.$loadingImage) { _this.options.$loadingImage.remove(); } morpheus.FormBuilder.showInModal({ title: 'Error', html: message }); }); 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( morpheus.Util .getBaseFileName(morpheus.Util .getFileName(option.dataset))); _this.heatmap .getColorScheme() .setColorSupplierForCurrentValue( morpheus.HeatMapColorScheme .createColorSupplier(option.colorScheme)); } else { try { _this .autoDisplay({ extension: morpheus.Util .getExtension(morpheus.Util .getFileName(option.dataset)), filename: morpheus.Util .getBaseFileName(morpheus.Util .getFileName(option.dataset)) }); } catch (x) { console .log('Autodisplay errror'); } } }); heatMapLoaded(); }); } else { var deferred = options.dataset.file ? morpheus.DatasetUtil.read( options.dataset.file, options.dataset.options) : morpheus.DatasetUtil.read(options.dataset); deferred.done(function (dataset) { _this.options.dataset = dataset; }); deferred.fail(function (err) { _this.options.$loadingImage.remove(); var message = ['Error opening ' + (options.dataset.file ? morpheus.Util .getFileName(options.dataset.file) : morpheus.Util .getFileName(options.dataset)) + '.']; if (err.message) { message.push('
Cause: '); message.push(err.message); } morpheus.FormBuilder.showInModal({ title: 'Error', html: message.join('') }); }); promises.push(deferred); $.when.apply($, promises).then(function () { if (_this.options.$loadingImage) { _this.options.$loadingImage.remove(); } _this._init(); heatMapLoaded(); }); } }; morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS = 6; morpheus.HeatMap.showTool = function (tool, controller, callback) { if (tool.gui) { var gui = tool.gui(controller.getProject()); var formBuilder = new morpheus.FormBuilder(); _.each(gui, function (item) { formBuilder.append(item); }); var tabId = controller.getTabManager().getActiveTabId(); if (tool.init) { tool.init(controller.getProject(), formBuilder, { controller: controller }); } var okCallback = function () { var task = { name: tool.toString(), tabId: tabId }; controller.getTabManager().addTask(task); var input = {}; _.each(gui, function (item) { input[item.name] = formBuilder.getValue(item.name); }); // give a chance for ui to update setTimeout(function () { try { var value = tool.execute({ controller: controller, project: controller.getProject(), input: input }); if (value instanceof Worker) { value.onerror = function (e) { task.worker.terminate(); morpheus.FormBuilder.showInModal({ title: 'Error', html: e, close: 'Close' }); if (e.stack) { console.log(e.stack); } }; var terminate = _.bind(value.terminate, value); task.worker = value; value.terminate = function () { terminate(); try { controller.getTabManager().removeTask(task); } catch (x) { console.log('Error removing task'); } if (callback) { callback(input); } }; } else { if (callback) { callback(input); } } } catch (e) { morpheus.FormBuilder.showInModal({ title: 'Error', html: e, close: 'Close' }); if (e.stack) { console.log(e.stack); } } finally { if (task.worker === undefined) { try { controller.getTabManager().removeTask(task); } catch (x) { console.log('Error removing task'); } } if (tool.dispose) { tool.dispose(); } } }, 0); }; var $formDiv; tool.ok = function () { okCallback(); $formDiv.modal('hide'); }; var guiOptions = $.extend({}, { ok: true }, gui.options); $formDiv = morpheus.FormBuilder.showOkCancel({ title: tool.toString(), apply: tool.apply, ok: guiOptions.ok, size: guiOptions.size, draggable: true, content: formBuilder.$form, align: 'right', okCallback: okCallback }); } else { // run headless try { var value = tool.execute({ controller: controller, project: controller.getProject(), input: {} }); if (callback) { callback({}); } } catch (e) { morpheus.FormBuilder.showInModal({ title: 'Error', html: e, close: 'Close' }); if (e.stack) { console.log(e.stack); } } finally { if (tool.dispose) { tool.dispose(); } } } var toolName = tool.toString(); var parenIndex = toolName.indexOf('('); if (parenIndex !== -1) { toolName = toolName.substring(0, parenIndex).trim(); } morpheus.Util.trackEvent({ eventCategory: 'Tool', eventAction: toolName }); }; morpheus.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; }; morpheus.HeatMap.createGroupBySpaces = function (dataset, groupByKeys, gapSize) { if (groupByKeys.length > 0) { var nkeys = groupByKeys.length; for (var keyIndex = 0; keyIndex < nkeys; keyIndex++) { groupByKeys[keyIndex].init(dataset); } return morpheus.HeatMap.getSpaces(groupByKeys, dataset.getRowCount(), gapSize); } }; morpheus.HeatMap.isDendrogramVisible = function (project, isColumns) { var sortKeys = isColumns ? project.getColumnSortKeys() : project .getRowSortKeys(); // var filter = isColumns ? this.project.getColumnFilter() // : this.project.getRowFilter(); var size = isColumns ? project.getSortedFilteredDataset().getColumnCount() : project.getSortedFilteredDataset().getRowCount(); if (sortKeys.length === 1) { return sortKeys[0] instanceof morpheus.SpecifiedModelSortOrder && sortKeys[0].name === 'dendrogram' && sortKeys[0].nvisible === size; } }; morpheus.HeatMap.prototype = { gapSize: 10, updatingScroll: false, autoDisplay: function (options) { if (options.filename == null) { options.filename = ''; } var colorScheme; if (options.extension === 'segtab' || options.extension === 'seg') { colorScheme = { type: 'fixed', map: morpheus.HeatMapColorScheme.Predefined.CN().map .map(function (item) { return { value: Math.pow(2, 1 + item.value), color: item.color }; }) }; } else if (options.extension === 'maf') { colorScheme = morpheus.HeatMapColorScheme.Predefined.MAF(); var colorMap = morpheus.HeatMapColorScheme.Predefined.MAF().map; var rowMutProfile = this.project.getFullDataset().getRowMetadata() .getByName('mutation_summary'); var columnMutProfile = this.project.getFullDataset() .getColumnMetadata().getByName('mutation_summary'); var track = this.getTrack('mutation_summary', false); if (track) { track.settingFromConfig('stacked_bar'); } track = this.getTrack('mutation_summary', true); if (track) { track.settingFromConfig('stacked_bar'); } for (var i = 1; i < colorMap.length; i++) { if (rowMutProfile) { this.getProject().getRowColorModel().setMappedValue( rowMutProfile, i - 1, colorMap[i].color); } if (columnMutProfile) { this.getProject().getColumnColorModel().setMappedValue( columnMutProfile, i - 1, colorMap[i].color); } } } else if (options.extension === 'gmt') { colorScheme = morpheus.HeatMapColorScheme.Predefined.BINARY(); } else if (options.filename === 'all_lesions.conf_99' || options.filename === 'all_data_by_genes.txt') { colorScheme = { type: 'fixed', map: [{ value: -0.5, color: 'blue' }, { value: 0, color: 'white' }, { value: 0.5, color: 'red' }] }; } else if (options.filename.toLowerCase().indexOf('copynumber') !== -1 || options.filename.toLowerCase().indexOf('copy number') !== -1) { colorScheme = { type: 'fixed', map: [{ value: -1.5, color: 'blue' }, { value: 0, color: 'white' }, { value: 1.5, color: 'red' }] }; } else if (options.filename.toLowerCase().indexOf('achilles') !== -1) { colorScheme = { type: 'fixed', map: [{ value: -3, color: 'blue' }, { value: -1, color: 'white' }, { value: 1, color: 'white' }, { value: 3, color: 'red' }] }; } if (colorScheme && options.filename) { this.heatmap.getColorScheme().setCurrentValue(options.filename); this.heatmap.getColorScheme().setColorSupplierForCurrentValue( morpheus.HeatMapColorScheme .createColorSupplier(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 morpheus.SortByValuesKey && morpheus.Util.arrayEquals(key.modelIndices, modelIndices)) { priorSortKeyIndex = i; if (key.getSortOrder() === morpheus.SortKey.SortOrder.UNSORTED) { sortOrder = morpheus.SortKey.SortOrder.DESCENDING; // 1st // click } else if (key.getSortOrder() === morpheus.SortKey.SortOrder.DESCENDING) { sortOrder = morpheus.SortKey.SortOrder.ASCENDING; // 2nd // click } else if (key.getSortOrder() === morpheus.SortKey.SortOrder.ASCENDING) { sortOrder = morpheus.SortKey.SortOrder.TOP_N; // 3rd // click } else if (key.getSortOrder() === morpheus.SortKey.SortOrder.TOP_N) { sortOrder = morpheus.SortKey.SortOrder.UNSORTED; // 4th // click } break; } } } if (sortOrder == null) { sortOrder = morpheus.SortKey.SortOrder.DESCENDING; } var sortKeys; if (append) { sortKeys = !isColumns ? project.getColumnSortKeys() : project .getRowSortKeys(); if (priorSortKeyIndex !== -1) { if (sortOrder === morpheus.SortKey.SortOrder.UNSORTED) { // remove existing sort key sortKeys.splice(priorSortKeyIndex, 1); } else { sortKeys[priorSortKeyIndex].setSortOrder(sortOrder); } } else { if (sortOrder !== morpheus.SortKey.SortOrder.UNSORTED) { sortKeys.push(new morpheus.SortByValuesKey(modelIndices, sortOrder, !isColumns)); } // add new sort key } sortKeys = morpheus.SortKey.keepExistingSortKeys(sortKeys, !isColumns ? project.getColumnSortKeys() : project .getRowSortKeys()); } else { var newSortKeys = sortOrder === morpheus.SortKey.SortOrder.UNSORTED ? [] : [new morpheus.SortByValuesKey(modelIndices, sortOrder, !isColumns)]; sortKeys = morpheus.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); } morpheus.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; }, getName: function () { return this.options.name; }, showOptions: function () { new morpheus.HeatMapOptions(this); }, getProject: function () { return this.project; }, /** * @param tree * An object with maxHeight, a rootNode, leafNodes, and * nLeafNodes */ setDendrogram: function (tree, isColumns, viewIndices) { var dendrogram = isColumns ? this.columnDendrogram : this.rowDendrogram; if (dendrogram) { dendrogram.dispose(); dendrogram = null; } if (tree != null) { var modelIndices = []; var modelIndexSet = new morpheus.Set(); var size = isColumns ? this.project.getFullDataset() .getColumnCount() : this.project.getFullDataset() .getRowCount(); for (var i = 0; i < size; i++) { modelIndexSet.add(i); } for (var i = 0, length = viewIndices.length; i < length; i++) { var modelIndex = isColumns ? this.project .convertViewColumnIndexToModel(viewIndices[i]) : this.project .convertViewRowIndexToModel(viewIndices[i]); modelIndices.push(modelIndex); modelIndexSet.remove(modelIndex); } var nvisible = modelIndices.length; // add model indices that weren't visible when clustering if (modelIndexSet.size() > 0) { var indices = modelIndexSet.values(); for (var i = 0, length = indices.length; i < length; i++) { modelIndices.push(indices[i]); } } if (isColumns) { dendrogram = new morpheus.ColumnDendrogram(this, tree, this.heatmap.getColumnPositions(), this.project); dendrogram.filter = this.project.getColumnFilter() .shallowClone(); this.columnDendrogram = dendrogram; this.project.setColumnSortKeys( [new morpheus.SpecifiedModelSortOrder(modelIndices, nvisible, 'dendrogram')], true); } else { dendrogram = new morpheus.RowDendrogram(this, tree, this.heatmap.getRowPositions(), this.project); dendrogram.filter = this.project.getRowFilter().shallowClone(); this.rowDendrogram = dendrogram; this.project.setRowSortKeys( [new morpheus.SpecifiedModelSortOrder(modelIndices, nvisible, 'dendrogram')], 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] instanceof morpheus.SpecifiedModelSortOrder && sortKeys[i].name === 'dendrogram') { 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 }); }, setCustomUrls: function (customUrls) { this._customUrls = customUrls; }, 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(morpheus.Util.nf(dataset.getValue(rowIndex, columnIndex))); rowTracks.forEach(function (track) { tipText.push('\t'); tipText.push(morpheus.Util.formatObject(dataset .getRowMetadata().getByName(track.name).getValue( rowIndex))); }); columnTracks.forEach(function (track) { tipText.push('\t'); tipText.push(morpheus.Util.formatObject(dataset .getColumnMetadata().getByName(track.name) .getValue(columnIndex))); }); tipText.push('\n'); }); return tipText.join(''); } }, _init: function () { var _this = this; morpheus.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 = this.options.symmetric ? new morpheus.SymmetricProject( dataset) : new morpheus.Project(dataset); this.tabManager.setTabTitle(this.tabId, this.project.getFullDataset() .getRowCount() + ' row' + morpheus.Util.s(this.project.getFullDataset().getRowCount()) + ' x ' + this.project.getFullDataset().getColumnCount() + ' column' + morpheus.Util.s(this.project.getFullDataset() .getColumnCount())); if (this.options.inheritFromParent && this.options.parent != null) { morpheus.HeatMap.copyFromParent(this.project, this.options); } var createFiltersFromOptions = function (filters, isColumns) { // name, 1. set if string filter, 2. min, max if range filter // 3. top and isTop if top n filter _.each(filters, function (filter) { if (filter.values) { if ((isColumns ? _this.project.getFullDataset() .getColumnMetadata().getByName(filter.name) : _this.project.getFullDataset().getRowMetadata() .getByName(filter.name)) != null) { var set = new morpheus.Set(); for (var i = 0; i < filter.values.length; i++) { set.add(filter.values[i]); } (isColumns ? _this.project.getColumnFilter() : _this.project.getRowFilter()) .add(new morpheus.VectorFilter(set, -1, filter.name)); } } else { (isColumns ? _this.project.getColumnFilter() : _this.project.getRowFilter()).add(filter); } }); // filter ui will be initialized automatically if (isColumns) { _this.project.setColumnFilter(_this.project.getColumnFilter(), true); } else { _this.project.setRowFilter(_this.project.getRowFilter()); } }; if (this.options.rowFilter) { createFiltersFromOptions(this.options.rowFilter, false); } if (this.options.columnFilter) { createFiltersFromOptions(this.options.columnFilter, true); } this.whenLoaded = null; this.$parent = $('
').css('position', 'relative'); this.$parent.appendTo(this.$content); this.toolbar = new morpheus.HeatMapToolBar(this); if (this.options.customUrls) { this.setCustomUrls(this.options.customUrls); } // 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 morpheus.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 morpheus.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 morpheus.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 morpheus.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 morpheus.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 }); }); var heatmap = new morpheus.HeatMapElementCanvas(this.project); if (this.options.drawCallback) { heatmap.setDrawCallback(this.options.drawCallback); } $(heatmap.canvas) .on( 'contextmenu', function (e) { morpheus.Popup .showPopup( [ { name: 'Copy', disabled: _this.project .getElementSelectionModel() .count() === 0 }, { name: 'Save Image (Ctrl-S)' }, { 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 (Ctrl-S)') { morpheus.HeatMap .showTool( new morpheus.SaveImageTool(), _this); } else if ('Copy') { var text = _this .getSelectedElementsText(); if (text !== '') { event.clipboardData .setData( 'text/plain', text); } } }); e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); }); heatmap.appendTo(this.$parent); this.heatmap = heatmap; var rowDendrogramSortKey = null; if (rowDendrogram != undefined) { var tree = rowDendrogram; if (tree.leafNodes.length !== this.project.getFullDataset() .getRowCount()) { throw '# leaf nodes in row dendrogram ' + tree.leafNodes.length + ' != ' + this.project.getFullDataset().getRowCount(); } if (this.options.rowDendrogramField != null) { var vector = dataset.getRowMetadata().getByName( this.options.rowDendrogramField); var rowIndices = []; var map = new morpheus.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 { for (var i = 0, length = tree.leafNodes.length; i < length; i++) { rowIndices.push(i); } } this.rowDendrogram = new morpheus.RowDendrogram(this, tree, heatmap .getRowPositions(), this.project, true); rowDendrogramSortKey = new morpheus.SpecifiedModelSortOrder( rowIndices, rowIndices.length, 'dendrogram'); this.rowDendrogram.appendTo(this.$parent); this.rowDendrogram.$label.appendTo(this.$parent); this.rowDendrogram.$squishedLabel.appendTo(this.$parent); } var columnDendrogramSortKey = null; if (columnDendrogram !== undefined) { var tree = columnDendrogram; if (tree.leafNodes.length !== this.project.getFullDataset() .getColumnCount()) { throw '# leaf nodes ' + tree.leafNodes.length + ' != ' + this.project.getFullDataset().getColumnCount(); } var columnIndices = []; if (this.options.columnDendrogramField != null) { var vector = dataset.getColumnMetadata().getByName( this.options.columnDendrogramField); var map = new morpheus.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++) { columnIndices.push(i); } } this.columnDendrogram = new morpheus.ColumnDendrogram(this, tree, heatmap.getColumnPositions(), this.project, true); columnDendrogramSortKey = new morpheus.SpecifiedModelSortOrder( columnIndices, columnIndices.length, 'dendrogram'); this.columnDendrogram.appendTo(this.$parent); this.columnDendrogram.$label.appendTo(this.$parent); this.columnDendrogram.$squishedLabel.appendTo(this.$parent); } if (rowDendrogramSortKey !== null) { this.project.setRowSortKeys([rowDendrogramSortKey]); } if (columnDendrogramSortKey !== null) { this.project.setColumnSortKeys([columnDendrogramSortKey]); } if (this.options.rowGroupBy != null) { for (var i = 0; i < this.options.rowGroupBy.length; i++) { var key = new morpheus.SortKey(this.options.rowGroupBy[i], morpheus.SortKey.SortOrder.UNSORTED); this.project.groupRows.push(key); } } if (this.options.rowSortBy) { var keys = []; for (var i = 0; i < this.options.rowSortBy.length; i++) { var sortBy = this.options.rowSortBy[i]; if (sortBy.modelIndices != null && morpheus.Util.isArray(sortBy.modelIndices)) { // sort by values keys.push(new morpheus.SortByValuesKey( this.options.rowSortBy[i].modelIndices, this.options.rowSortBy[i].order, false)); } else { // name is deprecated, use field var name = sortBy.name != null ? sortBy.name : sortBy.field; if (this.project.getFullDataset().getRowMetadata() .getByName(name) != null) { keys.push(new morpheus.SortKey(name, sortBy.order)); } } } this.project.setRowSortKeys(keys, false); } if (this.options.columnGroupBy != null) { for (var i = 0; i < this.options.columnGroupBy.length; i++) { var key = new morpheus.SortKey(this.options.columnGroupBy[i], morpheus.SortKey.SortOrder.UNSORTED); this.project.groupColumns.push(key); } } if (this.options.columnSortBy) { var keys = []; for (var i = 0; i < this.options.columnSortBy.length; i++) { var sortBy = this.options.columnSortBy[i]; if (sortBy.modelIndices != null && morpheus.Util.isArray(sortBy.modelIndices)) { keys.push(new morpheus.SortByValuesKey(sortBy.modelIndices, sortBy.order, true)); } else { // name is deprecated, use field var name = sortBy.name != null ? sortBy.name : sortBy.field; if (this.project.getFullDataset().getColumnMetadata() .getByName(name) != null) { keys.push(new morpheus.SortKey(name, sortBy.order)); } } } this.project.setColumnSortKeys(keys, false); } this.vSortByValuesIndicator = new morpheus.SortByValuesIndicator( this.project, true, heatmap.getRowPositions()); this.vSortByValuesIndicator.appendTo(this.$parent); this.hSortByValuesIndicator = new morpheus.SortByValuesIndicator( this.project, false, heatmap.getColumnPositions()); this.hSortByValuesIndicator.appendTo(this.$parent); this.verticalSearchBar = new morpheus.ScentedSearch(this.project .getRowSelectionModel(), heatmap.getRowPositions(), true, this.vscroll, this); this.horizontalSearchBar = new morpheus.ScentedSearch(this.project .getColumnSelectionModel(), heatmap.getColumnPositions(), false, this.hscroll, this); this.rowTracks = []; this.rowTrackHeaders = []; this.columnTracks = []; this.columnTrackHeaders = []; var setInitialDisplay = function (isColumns, options) { var nameToOption = new morpheus.Map(); // at // least // one // display option // supplied var displaySpecified = false || (_this.options.parent != null && _this.options.inheritFromParent); _.each(options, function (option) { if (!displaySpecified) { displaySpecified = option.display != null; } nameToOption.set(option.renameTo != null ? option.renameTo : option.field, option); }); var displayMetadata = isColumns ? dataset.getColumnMetadata() : dataset.getRowMetadata(); // see if default fields found if (!displaySpecified) { var defaultFieldsToShow = new morpheus.Set(); ['pert_iname', 'moa', 'target', 'description'] .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.set(v.getName(), { display: 'text' }); displaySpecified = true; } } } var isFirst = true; for (var i = 0, metadataCount = displayMetadata.getMetadataCount(); i < metadataCount; i++) { var display = displaySpecified ? 'None' : undefined; var v = displayMetadata.get(i); var name = v.getName(); var option = nameToOption.get(name); if (morpheus.MetadataUtil.DEFAULT_HIDDEN_FIELDS.has(name) && option == null) { continue; } var count = isColumns ? dataset.getColumnCount() : dataset .getRowCount(); if (!option && !displaySpecified && count > 1 && !morpheus.VectorUtil.containsMoreThanOneValue(v)) { continue; } if (option == null) { option = {}; } if (option.title) { v.getProperties().set(morpheus.VectorKeys.TITLE, option.title); } if (option.display) { if (typeof option.display == 'function') { display = option.display(name); } else { display = option.display; } } var add = display !== 'None'; if (add) { if (display == null) { if (name === 'pert_iname' || name === 'id' || isFirst) { display = 'text,tooltip'; } else { display = isColumns ? 'color,highlight' : 'text'; } } isFirst = false; var track = isColumns ? _this.addColumnTrack(name, display) : _this.addRowTrack(name, display); if (track.isRenderAs(morpheus.VectorTrack.RENDER.COLOR) && option.color) { var m = isColumns ? _this.project.getColumnColorModel() : _this.project.getRowColorModel(); if (track.isDiscrete()) { _.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(morpheus.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) { var nameOrderPairs = []; var found = false; _.each(array, function (item) { var name = item.renameTo || item.field; var order = 999; if (item.order != null) { order = item.order; found = true; } nameOrderPairs.push({ name: name, order: order }); }); if (found) { 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); var colorSchemeSpecified = this.options.colorScheme != null; if (this.options.colorScheme == null) { var ext = ''; if (this.options.dataSource) { try { ext = morpheus.Util.getExtension(morpheus.Util .getFileName(this.options.dataSource)); } catch (x) { } } var colorScheme = this.autoDisplay({ 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 morpheus.SortKey('order', morpheus.SortKey.SortOrder.ASCENDING)); } sortKeys.push(new morpheus.SortKey('id', morpheus.SortKey.SortOrder.ASCENDING)); this.project.setRowSortKeys(sortKeys, false); } if (morpheus.DatasetUtil.getSeriesIndex(this.project .getFullDataset(), 'allelic_fraction') !== -1) { this.options.sizeBy = 'allelic_fraction'; } } if (this.options.parent && this.options.inheritFromParent && !colorSchemeSpecified) { heatmap.setColorScheme(this.options.parent.heatmap.getColorScheme() .copy(this.project)); } else { heatmap.setColorScheme(new morpheus.HeatMapColorScheme( this.project, this.options.colorScheme)); if (this.options.dataset.getRowMetadata().getByName('Source') != null) { // separate color scheme for each source file var sourcesSet = morpheus.VectorUtil .getSet(this.options.dataset.getRowMetadata() .getByName('Source')); this.heatmap.getColorScheme() .setSeparateColorSchemeForRowMetadataField('Source'); sourcesSet.forEach(function (source) { _this.autoDisplay({ extension: morpheus.Util.getExtension(source), filename: '' + source }); }); } } if (this.options.sizeBy) { heatmap.getColorScheme().getSizer().setSeriesName( this.options.sizeBy); } this.updateDataset(); if (this.options.uiReady) { this.options.uiReady(this); } if (this.options.tabOpened) { try { this.options.tabOpened(this); } catch (x) { console.log('Error in tabOpened'); if (x.stack) { console.log(x.stack); } } this.updateDataset(); } if (this.options.renderReady) { try { this.options.renderReady(this); } catch (x) { console.log('Error in renderReady'); if (x.stack) { console.log(x.stack); } } this.updateDataset(); } if (this.options.rowSize != null) { if (this.options.rowSize === 'fit') { this.heatmap.getRowPositions().setSize(this.getFitRowSize()); } else { this.heatmap.getRowPositions().setSize(this.options.rowSize); } this.revalidate({ paint: false }); } if (this.options.columnSize != null) { if (this.options.columnSize === 'fit') { this.heatmap.getColumnPositions().setSize( this.getFitColumnSize()); } else { this.heatmap.getColumnPositions().setSize( this.options.columnSize); } this.revalidate({ paint: false }); } if (this.options.rowSize != null && this.options.columnSize != null) { // 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; this.$tipFollow = $('
'); this.$tipFollow.appendTo(this.$parent); this.$tipInfoWindow = $('
'); this.$tipInfoWindow.appendTo(this.$parent); 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 .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) { var columns = e.columns; _.each(e.vectors, function (v, i) { var index = _this.getTrackIndex(v.getName(), columns); if (index === -1) { if (columns) { _this.addColumnTrack(v.getName(), e.render[i]); } else { _this.addRowTrack(v.getName(), e.render[i]); } } else { // repaint var track = _this.getTrackByIndex(index, columns); var render = e.render[i]; if (render) { track.settingFromConfig(render); } 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.settings.stackedBar && track .isRenderAs(morpheus.VectorTrack.RENDER.BAR)) { track.setInvalid(true); track.repaint(); } } _this.verticalSearchBar.update(); _this.paintAll({ paintRows: true, paintColumns: false, invalidateRows: false, invalidateColumns: false }); }); this.getProject().getColumnSelectionModel().on('selectionChanged', function () { _this.horizontalSearchBar.update(); _this.paintAll({ paintRows: false, paintColumns: true, invalidateRows: false, invalidateColumns: false }); }); $(window) .on( 'paste.morpheus', function (e) { if (_this.isActiveComponent()) { var text = e.originalEvent.clipboardData .getData('text/plain'); if (text != null && text.length > 0) { var blob = new Blob([text]); var url = window.URL.createObjectURL(blob); e.preventDefault(); e.stopPropagation(); morpheus.HeatMap.showTool( new morpheus.OpenFileTool({ file: url }), _this); } } }) .on('beforecopy.morpheus', function (e) { if (_this.isActiveComponent()) { e.preventDefault(); } }) .on( 'copy.morpheus', function (ev) { if (_this.isActiveComponent()) { var activeComponent = _this .getActiveComponent(); var project = _this.project; if (activeComponent === 2) { var text = _this.getSelectedElementsText(); if (text !== '') { ev.originalEvent.clipboardData.setData( 'text/plain', text); ev.preventDefault(); ev.stopImmediatePropagation(); return; } } // copy all selected rows and columns var dataset = project.getSelectedDataset({ emptyToAll: false }); 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 = morpheus.MetadataUtil.indexOf( columnMetadata, name); if (index !== -1) { columnFieldIndices.push(index); } }); columnMetadata = new morpheus.MetadataModelColumnView( columnMetadata, columnFieldIndices); var rowMetadata = dataset.getRowMetadata(); // only copy visible tracks var visibleRowFields = _this .getVisibleTrackNames(false); var rowFieldIndices = []; _.each(visibleRowFields, function (name) { var index = morpheus.MetadataUtil.indexOf( rowMetadata, name); if (index !== -1) { rowFieldIndices.push(index); } }); rowMetadata = new morpheus.MetadataModelColumnView( rowMetadata, rowFieldIndices); var text = []; var rowsSelected = dataset.getRowCount() > 0; var columnsSelected = dataset.getColumnCount() > 0; if (rowsSelected && columnsSelected) { // copy // as // gct // 1.3 text = new morpheus.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(morpheus.Util.toString(v .getValue(i))); } text.push('\n'); } text = text.join(''); } ev.originalEvent.clipboardData.setData( 'text/plain', text); ev.preventDefault(); ev.stopImmediatePropagation(); } }); if (this.options.keyboard) { new morpheus.HeatMapKeyListener(this); } var dragStartScrollTop; var dragStartScrollLeft; this.hammer = morpheus.Util .hammer(_this.heatmap.canvas, ['pan', 'pinch', 'tap']) .on('panmove', function (event) { _this.updatingScroll = true; 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; _this.paintAll({ paintRows: rows, paintColumns: rows, invalidateRows: rows, invalidateColumns: columns }); event.preventDefault(); }) .on('panstart', function (event) { dragStartScrollTop = _this.scrollTop(); dragStartScrollLeft = _this.scrollLeft(); }) .on( 'tap', function (event) { var commandKey = morpheus.Util.IS_MAC ? event.srcEvent.metaKey : event.srcEvent.ctrlKey; if (morpheus.Util.IS_MAC && event.srcEvent.ctrlKey) { // right-click // on // Mac return; } var position = morpheus.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', 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 = morpheus.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); _.each(this.options.tools, function (item) { var tool = _this.toolbar.getToolByName(item.name); if (tool == null) { console.log(item.name + ' not found.'); } else { try { var gui = tool.gui(_this.getProject()); var formBuilder = new morpheus.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]; } } tool.execute({ controller: _this, project: _this.getProject(), input: input }); } catch (x) { if (x.stack) { console.log(x.stack); } console.log('Error running ' + item.name); } finally { if (tool.dispose) { tool.dispose(); } } } }); }, setMousePosition: function (i, j, options) { this.mousePositionOptions = options; var updateColumns = this.project.getHoverColumnIndex() !== j; var updateRows = this.project.getHoverRowIndex() !== i; if (updateColumns || updateRows) { 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.tooltipMode === 2 && // (this.project.getHoverColumnIndex() !== -1 || this.project // .getHoverRowIndex() !== -1)) { // // } this.trigger('change', { name: 'setMousePosition', source: this, arguments: arguments }); }, setTooltipMode: function (mode) { this._togglingInfoWindow = true; this.tooltipMode = mode; this.$tipInfoWindow.html(''); this.toolbar.$tip.html(''); this.$tipFollow.html('').css({ left: -1000, top: -1000 }); this.setToolTip(-1, -1); if (this.tooltipMode === 1) { this.$tipInfoWindow.dialog('open'); } else { this.$tipInfoWindow.dialog('close'); } this._togglingInfoWindow = false; }, toggleInfoWindow: function () { this.setTooltipMode(this.tooltipMode == 1 ? 0 : 1); }, _setTipText: function (tipText, tipFollowText, options) { if (this.tooltipMode === 0) { this.toolbar.$tip.html(tipText.join('')); } else if (this.tooltipMode === 1) { this.$tipInfoWindow.html(tipText.join('')); } if (tipFollowText.length > 0) { this.tipFollowHidden = false; this.$tipFollow.html('' + tipFollowText.join('') + ''); this._updateTipFollowPosition(options); } else { this.tipFollowHidden = true; this.$tipFollow.html('').css({ left: -1000, top: -1000 }); } this.trigger('change', { name: 'setToolTip', source: this, arguments: arguments }); }, setToolTip: function (rowIndex, columnIndex, options) { options = options || {}; if (this.options.showSeriesNameInTooltip) { options.showSeriesNameInTooltip = true; } if (options.heatMapLens) { // don't draw lens if currently visible // row lens var $wrapper = $('
'); var wrapperHeight = 0; var wrapperWidth = 0; var found = false; var inline = []; 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)) { found = true; 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 = morpheus.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 + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; canvas.width = canvasWidth * morpheus.CanvasUtil.BACKING_SCALE; canvas.style.width = canvasWidth + 'px'; canvas.height = heatMapHeight * morpheus.CanvasUtil.BACKING_SCALE; canvas.style.height = heatMapHeight + 'px'; var context = canvas.getContext('2d'); morpheus.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 + morpheus.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); } else { inline.push(row); } } if (found) { $wrapper.css({ height: wrapperHeight, width: wrapperWidth }); var rect = this.$parent[0].getBoundingClientRect(); this.$tipFollow.html($wrapper).css({ left: parseFloat(this.heatmap.canvas.style.left) - 1, top: options.event.clientY - rect.top - wrapperHeight / 2 }); return; } else { var tipText = []; var tipFollowText = []; for (var hoverIndex = 0; hoverIndex < inline.length; hoverIndex++) { this.tooltipProvider(this, inline[hoverIndex], -1, options, this.tooltipMode === 0 ? '   ' : '
', false, tipText); if (this.options.inlineTooltip) { this.tooltipProvider(this, inline[hoverIndex], -1, options, '
', true, tipFollowText); } } this._setTipText(tipText, tipFollowText, 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)) { found = true; 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 = morpheus.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 + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; canvas.width = heatMapWidth * morpheus.CanvasUtil.BACKING_SCALE; canvas.style.width = heatMapWidth + 'px'; canvas.height = canvasHeight * morpheus.CanvasUtil.BACKING_SCALE; canvas.style.height = canvasHeight + 'px'; var context = canvas.getContext('2d'); morpheus.CanvasUtil.resetTransform(context); context.translate(-startPix, 0); context.save(); context.rect(startPix, trackHeight + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS, this.heatmap.lastClip.width, this.heatmap.lastClip.height + trackHeight + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS); context.clip(); context.translate(0, trackHeight + morpheus.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); } else { inline.push(column); } } if (found) { $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 - morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS - 1, left: (options.event.clientX - rect.left) - (wrapperWidth / 2) }); return; } else { var tipText = []; var tipFollowText = []; for (var hoverIndex = 0; hoverIndex < inline.length; hoverIndex++) { this.tooltipProvider(this, -1, inline[hoverIndex], options, this.tooltipMode === 0 ? '   ' : '
', false, tipText); if (this.options.inlineTooltip) { this.tooltipProvider(this, -1, inline[hoverIndex], options, '
', true, tipFollowText); } } this._setTipText(tipText, tipFollowText, options); } } // column lens } // tooltipMode=0 top, 1=window, 2=inline var tipText = []; this.tooltipProvider(this, rowIndex, columnIndex, options, this.tooltipMode === 0 ? '   ' : '
', false, tipText); var tipFollowText = []; if (this.options.inlineTooltip) { this.tooltipProvider(this, rowIndex, columnIndex, options, '
', true, tipFollowText); } this._setTipText(tipText, tipFollowText, options); } , _updateTipFollowPosition: function (options) { if (this.tipFollowHidden) { return; } var rect = this.$parent[0].getBoundingClientRect(); var tipWidth = this.$tipFollow.width(); var tipHeight = this.$tipFollow.height(); var left = options.event.clientX - rect.left + 16; // default is bottom-right if ((left + tipWidth) >= rect.right) { // offscreen right left = options.event.clientX - rect.left - 16 - tipWidth; } var top = options.event.clientY - rect.top + 16; if ((top + tipHeight) >= (rect.bottom - rect.top)) { // offscreen top = options.event.clientY - rect.top - 16 - tipHeight; } this.$tipFollow.css({ left: left, top: top }); } , 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.renderSettings).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; } if (!isColumns) { this.addRowTrack(name); } else { this.addColumnTrack(name); } } 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 }); } , addRowTrack: function (name, renderSettings) { if (name === undefined) { throw 'Name not specified'; } if ('None' === renderSettings) { return; } if (renderSettings == null) { renderSettings = morpheus.VectorUtil.getDataType(this.project .getFullDataset().getRowMetadata().getByName(name)) === '[number]' ? 'bar' : morpheus.VectorTrack.RENDER.TEXT; } var track = new morpheus.VectorTrack(this.project, name, this.heatmap .getRowPositions(), false, this); track.settingFromConfig(renderSettings); this.rowTracks.push(track); track.appendTo(this.$parent); $(track.canvas).css('z-index', '0'); var header = new morpheus.VectorTrackHeader(this.project, name, false, this); this.rowTrackHeaders.push(header); header.appendTo(this.$parent); $(header.canvas).css('z-index', '0'); track._selection = new morpheus.TrackSelection(track, this.heatmap .getRowPositions(), this.project.getRowSelectionModel(), false, this); return track; } , addTrack: function (name, isColumns, renderSettings) { if (isColumns) { this.addColumnTrack(name, renderSettings); } else { this.addRowTrack(name, renderSettings); } } , addColumnTrack: function (name, renderSettings) { if (name === undefined) { throw 'Name not specified'; } if ('None' === renderSettings) { return; } if (renderSettings == null) { renderSettings = morpheus.VectorUtil.getDataType(this.project .getFullDataset().getColumnMetadata().getByName(name)) === '[number]' ? 'bar' : morpheus.VectorTrack.RENDER.TEXT; } var track = new morpheus.VectorTrack(this.project, name, this.heatmap .getColumnPositions(), true, this); track.settingFromConfig(renderSettings); this.columnTracks.push(track); track.appendTo(this.$parent); var header = new morpheus.VectorTrackHeader(this.project, name, true, this); this.columnTrackHeaders.push(header); header.appendTo(this.$parent); track._selection = new morpheus.TrackSelection(track, this.heatmap .getColumnPositions(), this.project.getColumnSelectionModel(), true, this); 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(); track._selection.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().spaces = morpheus.HeatMap .createGroupBySpaces(dataset, this.project.getGroupRows(), this.gapSize); this.heatmap.getColumnPositions().spaces = morpheus.HeatMap .createGroupBySpaces( new morpheus.TransposedDatasetView(dataset), this.project.getGroupColumns(), this.gapSize); 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); } , 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 1; } } for (var i = 0, ntracks = this.rowTracks.length; i < ntracks; i++) { if (this.rowTracks[i].canvas === active) { return 0; } } if (this.heatmap.canvas === active) { return 2; } } return -1; } , getVisibleTrackNames: function (isColumns) { var names = []; var tracks = isColumns ? this.columnTracks : this.rowTracks; for (var i = 0, length = tracks.length; i < length; i++) { if (tracks[i].isVisible()) { names.push(tracks[i].name); } } return names; } , resizeTrack: function (name, width, height, isColumns) { var index = this.getTrackIndex(name, isColumns); if (index === -1) { throw name + ' not found in resize track'; } 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 morpheus.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; // FIXME 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(); } this.vscroll.setValue(pos, true); this.trigger('change', { name: 'scrollTop', source: this, arguments: arguments }); } , scrollLeft: function (pos) { if (pos === undefined) { return this.hscroll.getValue(); } this.trigger('change', { name: 'scrollLeft', source: this, arguments: arguments }); this.hscroll.setValue(pos, true); } , setSelectedTrack: function (name, isColumns) { if (name !== this.selectedTrackName || isColumns !== this.selectedTrackIsColumns) { var index = this.getTrackIndex(this.selectedTrackName, this.selectedTrackIsColumns); if (index !== -1) { this.getTrackHeaderByIndex(index, this.selectedTrackIsColumns) .setSelected(false); } this.selectedTrackName = name; this.selectedTrackIsColumns = isColumns; var index = this.getTrackIndex(this.selectedTrackName, this.selectedTrackIsColumns); if (index !== -1) { this.getTrackHeaderByIndex(index, this.selectedTrackIsColumns) .setSelected(true); } this.trigger('change', { name: 'setSelected', source: this, arguments: arguments }); } } , saveImage: function (file, format) { var bounds = this.getTotalSize(); if (format === 'svg') { var context = new C2S(bounds.width, bounds.height); this.snapshot(context); var svg = context.getSerializedSvg(); var blob = new Blob([svg], { type: 'text/plain;charset=utf-8' }); saveAs(blob, file, true); } else { var canvas = $('')[0]; var height = bounds.height; var width = bounds.width; canvas.height = height; canvas.width = width; var context = canvas.getContext('2d'); this.snapshot(context); canvas.toBlob(function (blob) { if (blob == null || blob.size === 0) { morpheus.FormBuilder.showInModal({ title: 'Save Image', html: 'Image is too large to save.' }); 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() + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; } if (this.isDendrogramVisible(true)) { totalSize.height += this.columnDendrogram.getUnscaledHeight() + morpheus.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) + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; totalSize.width += maxColumnHeaderWidth + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; // color legend if (options.legend) { var legendHeight = this.heatmap.getColorScheme().getNames() != null ? this.heatmap .getColorScheme().getNames().length * 14 : 40; totalSize.height += legendHeight + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; } var trackLegendSize = new morpheus.HeatMapTrackColorLegend( _ .filter( this.columnTracks, function (track) { return track.isVisible() && (track .isRenderAs(morpheus.VectorTrack.RENDER.COLOR) || track .isRenderAs(morpheus.VectorTrack.RENDER.TEXT_AND_COLOR)); }), this.getProject().getColumnColorModel()) .getPreferredSize(); totalSize.height += trackLegendSize.height; totalSize.width = Math.max(totalSize.width, trackLegendSize.width); trackLegendSize = new morpheus.HeatMapTrackColorLegend( _ .filter( this.rowTracks, function (track) { return track.isVisible() && (track .isRenderAs(morpheus.VectorTrack.RENDER.COLOR) || track .isRenderAs(morpheus.VectorTrack.RENDER.TEXT_AND_COLOR)); }), this.getProject().getRowColorModel()) .getPreferredSize(); totalSize.height += morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS + trackLegendSize.height; totalSize.width = morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS + 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) { context.save(); context.translate(50, 0); morpheus.HeatMapColorSchemeLegend.drawColorScheme(context, this.heatmap.getColorScheme(), 200, true); context.restore(); legendHeight = this.heatmap.getColorScheme().getNames() != null ? this.heatmap .getColorScheme().getNames().length * 14 : 40; } context.save(); context.translate(4, legendHeight); // column color legend var columnTrackLegend = new morpheus.HeatMapTrackColorLegend( _ .filter( this.columnTracks, function (track) { return track.isVisible() && (track .isRenderAs(morpheus.VectorTrack.RENDER.COLOR) || track .isRenderAs(morpheus.VectorTrack.RENDER.TEXT_AND_COLOR)); }), this.getProject().getColumnColorModel()); columnTrackLegend.draw({}, context); context.restore(); // row color legend to the right of column color legend var columnTrackLegendSize = columnTrackLegend.getPreferredSize(); context.save(); context.translate(4 + columnTrackLegendSize.width, legendHeight); var rowTrackLegend = new morpheus.HeatMapTrackColorLegend( _ .filter( this.rowTracks, function (track) { return track.isVisible() && (track .isRenderAs(morpheus.VectorTrack.RENDER.COLOR) || track .isRenderAs(morpheus.VectorTrack.RENDER.TEXT_AND_COLOR)); }), this.getProject().getRowColorModel()); rowTrackLegend.draw({}, context); context.restore(); legendHeight += Math.max(rowTrackLegend.getPreferredSize().height, columnTrackLegendSize.height); var heatmapY = this.isDendrogramVisible(true) ? (this.columnDendrogram .getUnscaledHeight() + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS) : 0; heatmapY += legendHeight; var columnTrackY = heatmapY; var heatmapX = this.isDendrogramVisible(false) ? (this.rowDendrogram .getUnscaledWidth() + morpheus.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 += morpheus.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 = morpheus.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 (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 (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; } , fitToWindow: function (repaint) { this.heatmap.getRowPositions().setSize(this.getFitRowSize()); this.heatmap.getColumnPositions().setSize(this.getFitColumnSize()); if (repaint) { 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); } } , 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) { 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 = morpheus.CanvasUtil .getPreferredSize(this.columnDendrogram).height; } if (this.rowDendrogram) { rowDendrogramWidth = morpheus.CanvasUtil .getPreferredSize(this.rowDendrogram).width; } var rowTrackWidthSum = 0; for (var i = 0, length = this.rowTracks.length; i < length; i++) { if (this.rowTracks[i].isVisible()) { rowTrackWidthSum += Math .max( morpheus.CanvasUtil .getPreferredSize(this.rowTrackHeaders[i]).width, morpheus.CanvasUtil .getPreferredSize(this.rowTracks[i]).width); } } var ypos = columnDendrogramHeight; var maxHeaderWidth = 0; for (var i = 0, length = this.columnTracks.length; i < length; i++) { if (this.columnTracks[i].isVisible()) { var width = morpheus.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 - rowTrackWidthSum - xpos - morpheus.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 = morpheus.CanvasUtil.getPreferredSize(track); var headerSize = morpheus.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; } } ypos += morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; var heatMapHeight = heatmapPrefSize.height; if (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 + morpheus.HeatMap.SPACE_BETWEEN_HEAT_MAP_AND_ANNOTATIONS; // leave // space // after // afterVerticalScrollBarDivider } var rowAnnotationXStart = xpos; for (var i = 0, length = this.rowTracks.length; i < length; i++) { var track = this.rowTracks[i]; if (track.isVisible()) { var size = morpheus.CanvasUtil.getPreferredSize(track); var headerSize = morpheus.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' }); this.updatingScroll = false; this.trigger('change', { name: 'revalidate', source: this, arguments: arguments }); } }; morpheus.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(); 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; parentColumnTracks = parentRowTracks.slice().reverse(); } if (options.inheritFromParentOptions.columns) { // column similarity matrix project.rowShapeModel = project.columnShapeModel; project.rowColorModel = project.columnColorModel; 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 = 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 }); } } }; morpheus.Util.extend(morpheus.HeatMap, morpheus.Events);