832926811b278d5c656957e1565acdde48522def kate Mon Apr 27 17:59:12 2020 -0700 Update GTEx gene track click handler and track settings to handle V8 (add Kidney Medulla tissue, support TPM and RPKM). refs #25130 diff --git src/hg/js/hgGtexTrackSettings.js src/hg/js/hgGtexTrackSettings.js index 6df6dfb..61fe4bd 100644 --- src/hg/js/hgGtexTrackSettings.js +++ src/hg/js/hgGtexTrackSettings.js @@ -1,424 +1,425 @@ // gexTrackSettings - Interactive features for GTEX Body Map version of GTEx Gene track UI page // Copyright (C) 2016 The Regents of the University of California // This file contains a single module that encapsulates all javascript data and functions used to manage // the hgGtexTrackSettings page, which is initially generated by the hgGtexTrackSettings CGI. // The page contains an SVG format illustration of a human body with tissues, // and a companion list of the same tissues. The tissues are based on the GTEx Project, and total 53. // // Users can interact with either the illustration or the list, and any selections they make will be // reflected in the other. The interactions are click (select/unselect) or hover (highlight the tissue // in the body map). There are 53 total tissues -- the GTEx Consortium set. Tissue abbreviations and // colors used in the UI are from the hgFixed.gtexTissue table. // // Each tissue is represented in the SVG by: // 1. shape <path id=$tissue_Pic_Lo> // 2. highlighted shape <path id=$tissue_Pic_Hi> // 3. highlight surround <path id=$tissue_Aura_Hi> // 4. label <text id=$tissue_Text_Hi> // 5. leader line <line | polyline id=$tissue_Lead_Hi> // // Implementation note: jQuery is used below where effective. Some SVG manipulation // (e.g. add/remove/toggle classes) don't work from jQuery, so code for that is raw javascript var gtexTrackSettings = (function() { // Globals var _svgDoc; // SVG has its own DOM var _svgRoot; // Highlighted tissue is drawn last so it is on top var _topTissueId = 'topTissue'; var _topTissueName = null; var _topTissue; // element var _topAura; // element var _topLine; // element var TEXT_HI = '_Text_Hi'; var LEAD_HI = '_Lead_Hi'; var AURA_HI = '_Aura_Hi'; var PIC_HI = '_Pic_Hi'; var PIC_LO = '_Pic_Lo'; var COLOR_BLACK = 'black'; var COLOR_SELECTED = COLOR_BLACK; var COLOR_BLUE = 'blue'; var COLOR_HIGHLIGHT = COLOR_BLUE; var COLOR_GRAY = '#737373'; var COLOR_UNSELECTED = COLOR_GRAY; var COLOR_PINK = '#F69296'; var COLOR_LEADER = COLOR_PINK; var CLASS_TISSUE_SELECTED = 'gbmTissueSelected'; var CLASS_TISSUE_HOVERED = 'gbmTissueHovered'; var CLASS_TISSUE_LABEL = 'gbmTissueLabel'; var CLASS_TISSUE_COLOR_PATCH = 'gbmTissueColorPatch'; var CLASS_TISSUE_HOVERED_COLOR = 'gbmTissueHoveredColor'; var CLASS_TISSUE_UNSELECTED_COLOR = 'gbmTissueNotSelectedColor'; - // 53 tissues from GTEx, as in hgTracks.gtexTissue table + // 54 tissues from GTEx, as in hgTracks.gtexTissue table + // NOTE: kidneyMedulla was added in V8 // TODO: Consider generating this list during make, to an auxiliary .js file var tissues = [ 'adiposeSubcut', 'adiposeVisceral', 'adrenalGland', 'arteryAorta', 'arteryCoronary', 'arteryTibial', 'bladder', 'brainAmygdala', 'brainAnCinCortex', 'brainCaudate', 'brainCerebelHemi', 'brainCerebellum', 'brainCortex', 'brainFrontCortex', 'brainHippocampus', 'brainHypothalamus', 'brainNucAccumbens', 'brainPutamen', 'brainSpinalcord', 'brainSubstanNigra', 'breastMamTissue', 'xformedlymphocytes', 'xformedfibroblasts', 'ectocervix', 'endocervix', 'colonSigmoid', 'colonTransverse', 'esophagusJunction', 'esophagusMucosa', 'esophagusMuscular', 'fallopianTube', 'heartAtrialAppend', 'heartLeftVentricl', 'kidneyCortex', 'liver', 'lung', 'minorSalivGland', 'muscleSkeletal', 'nerveTibial', 'ovary', 'pancreas', 'pituitary', 'prostate', 'skinNotExposed', 'skinExposed', 'smallIntestine', 'spleen', 'stomach', - 'testis', 'thyroid', 'uterus', 'vagina', 'wholeBlood' + 'testis', 'thyroid', 'uterus', 'vagina', 'wholeBlood', 'kidneyMedulla' ]; // Convenience functions function tissueFromSvgId(svgId) { // Get tissue name from an SVG id. Convention here is <tis>_* return svgId.split('_')[0]; } function setMapTissueElColor(el) { // Change appearance of label in body map. This function is part of setTissue(), // used at initialization time (when other element attributes are already set by CGI) // NOTE: label may be consist of multiple text elements, so traverse children // TODO: Try replacing with CSS (First attempt resulted in black only after mouseover!) if (el === null) { return; } el.style.fill = COLOR_SELECTED; var count = el.childElementCount; for (var i = 0; i < count; i++) { el.children[i].style.fill = COLOR_SELECTED; } } function clearMapTissueElColor(el) { // Change appearance of label in body map. // NOTE: label may be consist of multiple text elements, so traverse children // TODO: Try replacing with CSS if (el === null) { return; } el.style.fill = COLOR_UNSELECTED; var count = el.childElementCount; for (var i = 0; i < count; i++) { el.children[i].style.fill = COLOR_UNSELECTED; } } function setTissue(tis) { // Mark selected in tissue list and body map var $tis = $('#' + tis); $tis.addClass(CLASS_TISSUE_SELECTED); // Change appearance of color patch in tissue list var colorPatch = $tis.prev('.' + CLASS_TISSUE_COLOR_PATCH); var tisColor = colorPatch.data('tissueColor'); // data function prefixes 'data-' to class colorPatch.css('background-color', tisColor); colorPatch.removeClass(CLASS_TISSUE_UNSELECTED_COLOR); // TODO: less obfuscated approach to colored border // Set hidden input checkbox, for cart $('#' + tis + ' > input').attr('checked', true); // Change appearance of label in body map var el = _svgDoc.getElementById(tis + TEXT_HI); if (el !== null) { el.classList.add(CLASS_TISSUE_SELECTED); setMapTissueElColor(el); } } function clearTissue(tis) { // Unselect in tissue table and body map var $tis = $('#' + tis); $tis.removeClass(CLASS_TISSUE_SELECTED); // Change appearance of color patch in tissue list colorPatch = $tis.prev('.' + CLASS_TISSUE_COLOR_PATCH); colorPatch.addClass(CLASS_TISSUE_UNSELECTED_COLOR); // Clear hidden input checkbox $('#' + tis + ' > input').attr('checked', false); // Change appearance of label in body map var el = _svgDoc.getElementById(tis + TEXT_HI); if (el !== null) { el.classList.remove(CLASS_TISSUE_SELECTED); clearMapTissueElColor(el); } } function toggleTissue(tis) { // Select/unselect tissue in list and body map var $tis = $('#' + tis); $tis.toggleClass(CLASS_TISSUE_SELECTED); if ($tis.hasClass(CLASS_TISSUE_SELECTED)) { setTissue(tis); } else { clearTissue(tis); } } function highlightTissue(tis) { // Highlight tissue label and color patch in tissue table toggleHighlightTissue(tis, true); } function unHighlightTissue(tis) { toggleHighlightTissue(tis, false); } function toggleHighlightTissue(tis, isHovered) { // Highlight/unhighlight tissue in body map and tissue table // Highlight tissue label and color patch in tissue table var $tis = $('#' + tis); if (tis === null) { return; } if (isHovered && _topTissueName !== null) { return; } $tis.toggleClass(CLASS_TISSUE_HOVERED, isHovered); var $colorPatch = $tis.prev('.' + CLASS_TISSUE_COLOR_PATCH); $colorPatch.toggleClass(CLASS_TISSUE_HOVERED_COLOR, isHovered); // Highlight tissue in body map by changing text appearance, visually moving organ to top // and adding a white (or sometimes blue if white background) surround ('aura') // Highlight tissue label in body map // TODO: Try jQuery here var textEl = _svgDoc.getElementById(tis + TEXT_HI); if (textEl === null) { return; } // TODO: unify with text styling below. Perhaps just add class to children will do it. textEl.classList.toggle(CLASS_TISSUE_HOVERED, isHovered); var lineEl = _svgDoc.getElementById(tis + LEAD_HI); var pic = $('#' + tis + PIC_HI, _svgRoot); var picEl = _svgDoc.getElementById(tis + PIC_HI); var aura = $('#' + tis + AURA_HI, _svgRoot); var auraEl = _svgDoc.getElementById(tis + AURA_HI); var textLineCount = textEl.childElementCount; var i; if (isHovered) { textEl.style.fill = COLOR_HIGHLIGHT; for (i = 0; i < textLineCount; i++) { textEl.children[i].style.fill = COLOR_HIGHLIGHT; } if (lineEl !== null) { // cell types lack leader lines lineEl.style.stroke = COLOR_HIGHLIGHT; } var topAura = auraEl.cloneNode(true); topAura.id = 'topAura'; _topAura = _svgRoot.appendChild(topAura); $(_topAura).show(); var topTissue = picEl.cloneNode(true); topTissue.addEventListener('mouseleave', onMapLeaveTissue); topTissue.id = _topTissueId; _topTissueName = tis; _topTissue = _svgRoot.appendChild(topTissue); $(_topTissue).show(); var topLine = lineEl.cloneNode(true); topLine.id = 'topLine'; _topLine = _svgRoot.appendChild(topLine); $(_topLine).show(); } else { var color = textEl.classList.contains(CLASS_TISSUE_SELECTED) ? COLOR_SELECTED : COLOR_UNSELECTED; textEl.style.fill = color; for (i = 0; i < textLineCount; i++) { textEl.children[i].style.fill = color; } if (lineEl !== null) { // cell types lack leader lines lineEl.style.stroke = COLOR_LEADER; // pink } _svgRoot.removeChild(_topTissue); _topTissueName = null; _svgRoot.removeChild(_topAura); _svgRoot.removeChild(_topLine); } } // Event handlers function onClickSetAll() { // Set all on body map and tissue list tissues.forEach(setTissue); } function onClickClearAll() { // Clear all on body map and tissue list tissues.forEach(clearTissue); } function onClickToggleTissue(ev) { // Select/unselect from tissue list tis = ev.data; toggleTissue(tis); } function onMapClickToggleTissue(ev) { // Select/unselect from illustration var svgId = ev.currentTarget.id; var tis = tissueFromSvgId(svgId); toggleTissue(tis); } function onEnterTissue(ev) { // Mouseover on label in tissue list highlightTissue(ev.data); } function onLeaveTissue(ev) { // Mouseover on label in tissue list unHighlightTissue(ev.data); } function onMapEnterTissue(ev) { // Mouseover on tissue shape or label in illustration var svgId = ev.currentTarget.id; var tis = (svgId === _topTissueId ? _topTissueName : tissueFromSvgId(svgId)); highlightTissue(tis); } function onMapLeaveTissue(ev) { // Mouseover on tissue shape or label in illustration var toTarget = ev.relatedTarget; var toParent; // Handle case where lower and upper shapes are not the same. If leaving lower to enter upper, we are not really leaving if (toTarget) { if (toTarget.id === _topTissueId) { return; } // Handle case where there are multiple paths for the tissue, and topTissue will be a parent toParent = toTarget.parentElement; if (toParent && toParent.id === _topTissueId) { return; } } var svgId = ev.currentTarget.id; var tis = (svgId === _topTissueId ? _topTissueName : tissueFromSvgId(svgId)); unHighlightTissue(tis); } function submitForm() { // Submit the form (from GO button -- as in hgGateway.js) // Show a spinner -- sometimes it takes a while for hgTracks to start displaying. $('.gbIconGo').removeClass('fa-play').addClass('fa-spinner fa-spin'); $form = $('form'); $form.submit(); } function toggleShowSampleCount() { // Show or hide sample counts in tissue table var sampleCount = $('.gbmTissueSampleCount')[0]; if ($(sampleCount).is(':visible')) { $('.gbmTissueTable').removeClass('gbmTissueTableWithSamples'); ($('.gbmTissueSampleCount').hide()); } else { $('.gbmTissueTable').addClass('gbmTissueTableWithSamples'); ($('.gbmTissueSampleCount').show()); } } // Initialization function initTissue(tis) { // Set tissue to unhighlighted state $('#' + tis + PIC_HI, _svgRoot).hide(); $('#' + tis + AURA_HI, _svgRoot).hide(); // Mark tissue labels in svg var textEl = _svgDoc.getElementById(tis + TEXT_HI); if (textEl !== null) { textEl.classList.add(CLASS_TISSUE_LABEL); if ($('#' + tis).hasClass(CLASS_TISSUE_SELECTED)) { textEl.classList.add(CLASS_TISSUE_SELECTED); setMapTissueElColor(textEl); } } } function initBodyMap() { // Set organs to unhighlighted state tissues.forEach(initTissue); } function animateTissue(tis) { // Add event handlers to body map and tissue list // Add click and mouseover handler to tissue label in tissue list var $tis = $('#' + tis); $tis.click(tis, onClickToggleTissue); $tis.mouseenter(tis, onEnterTissue); $tis.mouseleave(tis, onLeaveTissue); // Add click and mouseover handler to color patch in tissue list var $colorPatch = $tis.prev('.' + CLASS_TISSUE_COLOR_PATCH); $colorPatch.click(tis, onClickToggleTissue); $colorPatch.mouseenter(tis, onEnterTissue); $colorPatch.mouseleave(tis, onLeaveTissue); // Add mouseover and click handlers to tissue label in body map var textEl = _svgDoc.getElementById(tis + TEXT_HI); if (textEl !== null) { textEl.addEventListener('click', onMapClickToggleTissue); textEl.addEventListener('mouseenter', onMapEnterTissue); textEl.addEventListener('mouseleave', onMapLeaveTissue); } // add mouseover and click handlers to tissue shape var picEl = _svgDoc.getElementById(tis + PIC_LO); if (picEl !== null) { picEl.addEventListener('mouseenter', onMapEnterTissue); picEl.addEventListener('mouseleave', onMapLeaveTissue); picEl.addEventListener('mouseup', onMapClickToggleTissue); } } function animateTissues() { // Add event handlers to tissue table and body map SVG $('#setAll').click(onClickSetAll); $('#clearAll').click(onClickClearAll); tissues.forEach(animateTissue); } function initSvg() { var svgEl = document.getElementById('bodyMapSvg'); _svgDoc = svgEl.contentDocument; _svgRoot = _svgDoc.documentElement; initBodyMap(); animateTissues(); } function init() { // cart.setCgi('gtexTrackSettings'); $(function() { // After SVG load, tweak layout and initialize event handlers document.getElementById('bodyMapSvg').addEventListener('load', initSvg, false); $('.gbButtonGoContainer').click(submitForm); // hide/show of sample counts ($('.gbmTissueSampleCount').hide()); $('#showSampleCount').click(toggleShowSampleCount); }); } return { init: init }; }()); // gtexTrackSettings gtexTrackSettings.init();