65168641c489d8a747a5abf0fce4ed21a4e9a0af kate Wed Feb 29 22:03:10 2012 -0800 Major refactoring. New JS file encodeMatrix.js will be shared by all 3 data matrix apps diff --git src/hg/js/encodeDataMatrix.js src/hg/js/encodeDataMatrix.js index 8d89e6b..34685df 100644 --- src/hg/js/encodeDataMatrix.js +++ src/hg/js/encodeDataMatrix.js @@ -1,324 +1,263 @@ -/* encodeDataMatrix.js - pull experiment table and metadata from server - and display in data matrix +/* encodeAssayMatrix.js - ENCODE Data Matrix application + + Pulls experiment table and metadata from server + and displays in matrix of assay vs. cell type + + NOTE: $variables are jQuery objects Formatted: jsbeautify.py -j Syntax checked: jslint indent:4, plusplus: true, continue: true, unparam: true, sloppy: true, browser: true */ /*global $, encodeProject */ $(function () { var requests = [ // requests to server API encodeProject.serverRequests.experiment, encodeProject.serverRequests.dataType, encodeProject.serverRequests.cellType, encodeProject.serverRequests.expId ]; - var dataTypeLabelHash = {}, dataTypeTermHash = {}, cellTypeHash = {}; + var $matrixTable = $('#matrixTable'); + + function handleServerData(responses) { + // Main actions, called when loading data from server is complete + + // NOTE: ordering of responses is based on request order + var experiments = responses[0], + dataTypes = responses[1], + cellTypes = responses[2], + expIds = responses[3]; + + var dataGroups, cellTiers, expIdHash; var dataType, cellType; - var organism, assembly, server, header; - var karyotype; - var spinner; + var matrix, dataTypeExps = {}; + + // hide spinner and show table + encodeMatrix.show($matrixTable); + + // set up structures for data types and their groups + // data type labels tucked into their tiers + dataGroups = encodeProject.getDataGroups(dataTypes); + + // set up structures for cell types and their tiers + cellTiers = encodeProject.getCellTiers(cellTypes); + + // use to filter out experiments not in this assembly + expIdHash = encodeProject.getExpIdHash(expIds); + + // gather experiments into matrix + // NOTE: dataTypeExps is populated here + matrix = makeExperimentMatrix(experiments, expIdHash, dataTypeExps); + + // fill in table using matrix + tableOut($matrixTable, matrix, cellTiers, dataGroups, dataTypeExps); + } - function rowAddCells(row, dataGroups, matrix, cellType) { + function makeExperimentMatrix(experiments, expIdHash, dataTypeExps) { + // Populate dataType vs. cellType array with counts of experiments + + var dataType, cellType; + var matrix = {}; + + $.each(experiments, function (i, exp) { + // exclude ref genome annotations + if (exp.cellType === 'None') { + return true; + } + // exclude experiments lacking an expID (not in this assembly) + if (expIdHash[exp.ix] === undefined) { + return true; + } + // count experiments per dataType so we can prune those having none + // (the matrix[cellType] indicates this for cell types + // so don't need hash for those + dataType = exp.dataType; + if (dataTypeExps[dataType] === undefined) { + dataTypeExps[dataType] = 1; + } + dataTypeExps[dataType]++; + + cellType = exp.cellType; + if (!matrix[cellType]) { + matrix[cellType] = {}; + } + if (!matrix[cellType][dataType]) { + matrix[cellType][dataType] = 0; + } + matrix[cellType][dataType]++; + }); + return matrix; + } + + function tableHeaderOut($table, dataGroups, dataTypeExps) { + // Generate table header and add to DOM + // NOTE: relies on hard-coded classes and ids + + var $tableHeader, $thead; + var maxLen, dataType; + + // fill in column headers from dataTypes returned by server + $tableHeader = $('#columnHeaders'); + $thead = $('thead'); + + // 1st column is row headers + // colgroups are needed to support cross-hair hover effect + $thead.before('<colgroup></colgroup>'); + + $.each(dataGroups, function (i, group) { + $tableHeader.append('<th class="groupType"><div class="verticalText">' + + group.label + '</div></th>'); + maxLen = Math.max(maxLen, group.label.length); + + $thead.before('<colgroup></colgroup>'); + $.each(group.dataTypes, function (i, label) { + dataType = encodeProject.getDataTypeByLabel(label); + + // prune out datatypes with no experiments + if (dataTypeExps[dataType.term] !== undefined) { + $tableHeader.append('<th class="elementType" title="' + + dataType.description + + '"><div class="verticalText">' + dataType.label + + '</div></th>'); + // add colgroup element to support cross-hair hover effect + $thead.before('<colgroup class="dataTypeCol"></colgroup>'); + maxLen = Math.max(maxLen, dataType.label.length); + } + }); + }); + // adjust size of headers based on longest label length + // empirically len/2 em's is right + $('#columnHeaders th').css('height', (String((maxLen/2 + 2)).concat('em'))); + $('#columnHeaders th').css('width', '1em'); + } + + function rowAddCells($row, dataGroups, dataTypeExps, matrix, cellType) { // populate a row in the matrix with cells for data groups and data types // null cellType indicates this is a row for a cell group (tier) + var $td; + var dataType, url; + $.each(dataGroups, function (i, group) { // skip group header - td = $('<td></td>'); - td.addClass('matrixCell'); - row.append(td); + $td = $('<td></td>'); + $td.addClass('matrixCell'); + $row.append($td); $.each(group.dataTypes, function (i, dataTypeLabel) { - dataType = dataTypeLabelHash[dataTypeLabel].term; + dataType = encodeProject.getDataTypeByLabel(dataTypeLabel).term; // prune out datatypes with no experiments - if (dataTypeLabelHash[dataTypeLabel].count === undefined) { + if (dataTypeExps[dataType] === undefined) { return true; } - td = $('<td></td>'); - td.addClass('matrixCell'); - row.append(td); + $td = $('<td></td>'); + $td.addClass('matrixCell'); + $row.append($td); if (cellType === null) { return true; } if (!matrix[cellType][dataType]) { - td.addClass('todoExperiment'); + $td.addClass('todoExperiment'); return true; } // this cell represents experiments that // fill in count, mouseover and selection by click - td.addClass('experiment'); - td.text(matrix[cellType][dataType]); - td.data({ + $td.addClass('experiment'); + $td.text(matrix[cellType][dataType]); + $td.data({ 'dataType' : dataType, 'cellType' : cellType }); - td.mouseover(function() { + $td.mouseover(function() { $(this).attr('title', 'Click to select: ' + - dataTypeTermHash[$(this).data().dataType].label + - ' ' + ' in ' + - $(this).data().cellType +' cells'); + encodeProject.getDataType($(this).data().dataType).label + + ' ' + ' in ' + $(this).data().cellType +' cells'); }); - td.click(function() { + $td.click(function() { // TODO: base on preview ? - var url = encodeProject.getSearchUrl(assembly); + var url = encodeMatrix.getSearchUrl(encodeProject.getAssembly()); // TODO: encapsulate var names url += ('&hgt_mdbVar1=dataType&hgt_mdbVal1=' + $(this).data().dataType + '&hgt_mdbVar2=cell&hgt_mdbVal2=' + $(this).data().cellType + '&hgt_mdbVar3=view&hgt_mdbVal3=Any'); // specifying window name limits open window glut window.open(url, "searchWindow"); }); }); }); } - function addFloatingHeader(table) { - // add callback for floating table header feature - - table.floatHeader({ - cbFadeIn: function (header) { - // hide axis labels -- a bit tricky to do so - // as special handling needed for X axis label - $(".floatHeader #headerLabelRow").remove(); - $(".floatHeader #cellHeaderLabel").html(''); - $(".floatHeader #searchTypePanel").remove(); - - // Note: user-defined callback requires - // default actions from floatHeader plugin - // implementation (stop+fadeIn) - header.stop(true, true); - header.fadeIn(100); - } - }); - } - - function rotateCells(table) { - // plugin from David Votrubec, handles IE rotate - // TODO: restrict to IE - table.rotateTableCellContent({ className: 'verticalText'}); - $(this).attr('disabled', 'disabled'); - } - - function tableOut(matrix, cellTiers, dataGroups) { - // create table with rows for each cell types and columns for each data type - var table, thead, tableHeader, row, td; - var maxLen = 0; - - // fill in column headers from dataTypes returned by server - tableHeader = $('#columnHeaders'); - table = $('#matrixTable'); - thead = $('thead'); - - // 1st column is row headers - thead.before('<colgroup></colgroup>'); - - $.each(dataGroups, function (i, group) { - tableHeader.append('<th class="groupType"><div class="verticalText">' + - group.label + '</div></th>'); - maxLen = Math.max(maxLen, group.label.length); - - // add colgroup element to support cross-hair hover effect - thead.before('<colgroup></colgroup>'); - $.each(group.dataTypes, function (i, dataTypeLabel) { - dataType = dataTypeLabelHash[dataTypeLabel].term; - - // prune out datatypes with no experiments - if (dataTypeLabelHash[dataTypeLabel].count !== undefined) { - tableHeader.append('<th class="elementType" title="' + - dataTypeLabelHash[dataTypeLabel].description + - '"><div class="verticalText">' + dataTypeLabel + - '</div></th>'); - // add colgroup element to support cross-hair hover effect - thead.before('<colgroup class="dataTypeCol"></colgroup>'); - maxLen = Math.max(maxLen, dataTypeLabel.length); - } - }); - }); - - // adjust size of headers based on longest label length - // empirically len/2 em's is right - $('#columnHeaders th').css('height', (String((maxLen/2 + 2)).concat('em'))); - $('#columnHeaders th').css('width', '1em'); - - // fill in matrix -- + function tableMatrixOut($table, matrix, cellTiers, dataGroups, dataTypeExps) { + // Fill in matrix -- // add rows with cell type labels (column 1) and cells for experiments // add sections for each Tier of cell type + + var maxLen, karyotype, cellType; + var $row; + $.each(cellTiers, function (i, tier) { //skip bogus 4th tier (not my property ?) if (tier === undefined) { return true; } - row = $('<tr class="matrix"><th class="groupType">' + + $row = $('<tr class="matrix"><th class="groupType">' + "Tier " + tier.term + '</th></td></tr>'); - rowAddCells(row, dataGroups, matrix, null); - table.append(row); + rowAddCells($row, dataGroups, dataTypeExps, matrix, null); + $table.append($row); maxLen = 0; - $.each(tier.cellTypes, function (i, cellType) { - if (!cellType) { + $.each(tier.cellTypes, function (i, term) { + if (!term) { return true; } - if (!matrix[cellType]) { + if (!matrix[term]) { return true; } - karyotype = cellTypeHash[cellType].karyotype; + cellType = encodeProject.getCellType(term); // TODO: recognize cancer* // NOTE: coupled to CSS + karyotype = cellType.karyotype; if (karyotype !== 'cancer' && karyotype !== 'normal') { karyotype = 'unknown'; } // note karyotype bullet layout requires non-intuitive placement // in code before the span that shows to it's left - row = $('<tr>' + + $row = $('<tr>' + '<th class="elementType">' + '<span style="float:right; text-align: right;" title="karyotype: ' + karyotype + '" class="karyotype ' + karyotype + '">•</span>' + - '<span title="' + cellTypeHash[cellType].description + '"><a href="/cgi-bin/hgEncodeVocab?ra=encode/cv.ra&term=' + cellType + '">' + cellType + '</a>' + + '<span title="' + cellType.description + '"><a href="/cgi-bin/hgEncodeVocab?ra=encode/cv.ra&term=' + cellType.term + '">' + cellType.term + '</a>' + '</th>' ); - maxLen = Math.max(maxLen, cellType.length); + maxLen = Math.max(maxLen, cellType.term.length); - rowAddCells(row, dataGroups, matrix, cellType); - - table.append(row); + rowAddCells($row, dataGroups, dataTypeExps, matrix, cellType.term); + $table.append($row); }); // adjust size of row headers based on longest label length $('tbody th').css('height', '1em'); $('tbody th').css('width', (String((maxLen/2 + 2)).concat('em'))); }); - $("body").append(table); - - // NOTE: it may be possible to revive floating header functionality in IE using this plug-in, - // but I've timed out (not able to make it work in simple HTML either). - if (!$.browser.msie) { - addFloatingHeader(table); - } - rotateCells(table); - - // column and row hover (cross-hair effect) - // thanks to Chris Coyier, css-tricks.com - // NOTE: acts on colgroups declared at start of table - // NOTE: second table name is generated from floatheader plugin - // NOTE: too slow on IE, so skip - if ($.browser.msie) { - return; - } - $("#matrixTable, #matrixTableFloatHeaderClone").delegate('.matrixCell, .elementType','mouseover mouseleave', function(e) { - if (!$(this).hasClass('experiment') && !$(this).hasClass('todoExperiment') && - !$(this).hasClass('elementType') && !$(this).hasClass('groupType')) { - return; - } - if (e.type == 'mouseover') { - // refrain from highlighting header row - if (!$(this).parent().is("#columnHeaders")) { - $(this).parent().addClass("crossHair"); - } - col = $("colGroup").eq($(this).index()); - if (col.hasClass("dataTypeCol")) { - col.addClass("crossHair"); - } - } else { - $(this).parent().removeClass("crossHair"); - $("colGroup").eq($(this).index()).removeClass("crossHair"); - } - }); + $('body').append($table); } - function handleServerData(responses) { - // main actions, called when loading data from server is complete - var experiments = responses[0], dataTypes = responses[1], - cellTypes = responses[2], expIds = responses[3]; - var dataGroups, cellTiers, expIdHash, header; - var matrix = {}; + function tableOut($table, matrix, cellTiers, dataGroups, dataTypeExps) { + // Create table with rows for each cell types and columns for each data type, + // based on matrix - hideLoadingImage(spinner); - $('#matrixTable').show(); + tableHeaderOut($table, dataGroups, dataTypeExps); + tableMatrixOut($table, matrix, cellTiers, dataGroups, dataTypeExps); - // set up structures for data types and their groups - $.each(dataTypes, function (i, dataType) { - dataTypeTermHash[dataType.term] = dataType; - dataTypeLabelHash[dataType.label] = dataType; - }); - // data type labels tucked into their tiers - dataGroups = encodeProject.getDataGroups(dataTypes); - - // set up structures for cell types and their tiers - $.each(cellTypes, function (i, cellType) { - cellTypeHash[cellType.term] = cellType; - }); - cellTiers = encodeProject.getCellTiers(cellTypes); - - // use to filter out experiments not in this assembly - expIdHash = encodeProject.getExpIdHash(expIds); - - // gather experiments into matrix - $.each(experiments, function (i, exp) { - // todo: filter out with arg to hgApi - if (exp.organism !== organism) { - return true; - } - // exclude ref genome annotations - if (exp.cellType === 'None') { - return true; - } - if (expIdHash[exp.ix] === undefined) { - return true; - } - // count experiments per dataType so we can prune those having none - // (the matrix[cellType] indicates this for cell types - // so don't need hash for those - dataType = exp.dataType; - if (!dataTypeTermHash[dataType].count) { - dataTypeTermHash[dataType].count = 0; - } - dataTypeTermHash[dataType].count++; - - cellType = exp.cellType; - if (!matrix[cellType]) { - matrix[cellType] = {}; - } - if (!matrix[cellType][dataType]) { - matrix[cellType][dataType] = 0; - } - matrix[cellType][dataType]++; - }); - - // fill in table - tableOut(matrix, cellTiers, dataGroups); + encodeMatrix.addTableFloatingHeader($table); + encodeMatrix.rotateTableCells($table); + encodeMatrix.hoverTableCrossHair($table); } - // initialize - - // get server from calling web page (intended for genome-preview) - if ('encodeDataMatrix_server' in window) { - server = encodeDataMatrix_server; - } else { - server = document.location.hostname; - // or document.domain ? - } - // variables from calling page - organism = encodeDataMatrix_organism; - assembly = encodeDataMatrix_assembly; - $("#assemblyLabel").text(assembly); - header = encodeDataMatrix_pageHeader; - $("#pageHeader").text(header); - document.title = 'ENCODE ' + header; - - encodeProject.setup({ - server: server, - assembly: assembly - }); - - // show only spinner until data is retrieved - $('#matrixTable').hide(); - spinner = showLoadingImage("spinner", true); - - // add radio buttons for search type to specified div on page - encodeProject.addSearchPanel('#searchTypePanel'); + // initialize application + encodeMatrix.start($matrixTable); // load data from server and do callback encodeProject.loadAllFromServer(requests, handleServerData); });