151f0489b9e4fa73bdc22226a7a6354a450fecb2 chmalee Wed Feb 4 11:28:20 2026 -0800 Hide species tree by default but put up a link to show it again. Don't put connected assembly hubs into the species tree, instead into their own card section like the recent genomes list, below the recents. refs #34078 diff --git src/hg/js/hgGateway.js src/hg/js/hgGateway.js index a9d15f474df..3889fc2d98f 100644 --- src/hg/js/hgGateway.js +++ src/hg/js/hgGateway.js @@ -11,30 +11,31 @@ // speciesTree: module that exports draw() function for drawing a phylogenetic tree // (and list of hubs above the tree, if any) in a pre-existing SVG element -- see // hg/hgGateway/hgGateway.html. // rainbow: module that exports draw() function and colors. draw() adds stripes using // a spectrum of colors that are associated to species groups. The hgGateway view code // uses coordinates of stripes within the tree image to create a corresponding "rainbow" // slider bar to the left of the phylogenetic tree container. // hgGateway: module of mostly view/controller code (model state comes directly from server). // Globals (pragma for jshint): /* globals dbDbTree, activeGenomes, surveyLink, surveyLabel, surveyLabelImage, cart */ /* globals autoCompleteCat */ /* globals calculateHgTracksWidth */ // function is defined in utils.js +/* jshint esnext: true */ window.hgsid = ''; window.activeGenomes = {}; window.surveyLink=null; window.surveyLabel=null; window.surveyLabelImage=null; function setCopyLinks() { // add onclick to class 'copyLink' buttons, there could be more than one. addOnClick = function(){copyToClipboard(event);}; copySpan = document.getElementsByClassName('copyLinkSpan'); for (i = 0; i < copySpan.length; i++) { dataTarget = copySpan[i].getAttribute('data-target'); hostName = window.location.hostname; targetSpan = document.getElementById(dataTarget); @@ -1070,48 +1071,115 @@ } } } return hasActiveLeaf; } function drawSpeciesPicker(dbDbTree) { // If dbDbTree is nonempty and SVG is supported, draw the tree; if SVG is not supported, // use the space to suggest that the user install a better browser. // If dbDbTree doesn't exist, leave the "Represented Species" section hidden. var svg, spTree, stripeTops; if (dbDbTree) { if (document.createElementNS) { // Draw the phylogenetic tree and do layout adjustments svg = document.getElementById('speciesTree'); - spTree = speciesTree.draw(svg, dbDbTree, uiState.hubs, + spTree = speciesTree.draw(svg, dbDbTree, null, { hgHubConnectUrl: 'hgHubConnect?hgsid=' + window.hgsid, containerWidth: $('#speciesPicker').width() }); setSpeciesPickerSizes(spTree.width, spTree.height); stripeTops = rainbow.draw(svg, dbDbTree, spTree.yTree, spTree.height, spTree.leafTops); } else { $('#speciesTreeContainer').html(getBetterBrowserMessage); } + let showSpeciesTree = document.getElementById("speciesTreeLink"); + showSpeciesTree.addEventListener("click", function() { + let speciesTreeSection = document.getElementById("speciesGraphic"); + if (!$(speciesTreeSection).is(":visible")) { $('#representedSpeciesTitle').show(); $('#speciesGraphic').show(); if (dbDbTree && document.createElementNS) { // These need to be done after things are visible because heights are 0 when hidden. highlightLabelForDb(uiState.db, uiState.taxId); initRainbowSlider(spTree.height, rainbow.colors, stripeTops); } + showSpeciesTree.textContent = "Hide species tree"; + } else { + $('#representedSpeciesTitle').hide(); + $('#speciesGraphic').hide(); + showSpeciesTree.textContent = "Show species tree"; + } + }); + } + } + + function addHubsToList(hubList) { + // Render connected hubs into the connected hubs section + // Only show the section if there are non-curated hubs to display + var $title = $('#connectedHubsTitle'); + var $section = $('#connectedHubsSection'); + var $panel = $('#connectedHubsList'); + + if (!hubList || hubList.length === 0) { + // No hubs, hide the section + $title.hide(); + $section.hide(); + return; + } + + // Filter out curated assembly hubs (those in /gbdb but not /gbdb/genark) + // and convert hub objects to card-compatible format + var displayHubs = []; + hubList.forEach(function(hub) { + // Skip curated assembly hubs (same logic as drawHubs) + if (hub.hubUrl.startsWith("/gbdb") && !hub.hubUrl.startsWith("/gbdb/genark")) { + return; } + // Convert hub object to card-compatible format + var hubItem = { + label: hub.shortLabel + ' (' + hub.assemblyCount + ' assembl' + + (hub.assemblyCount === 1 ? 'y' : 'ies') + ')', + genome: hub.defaultDb, + category: 'Assembly Hub', + hubUrl: hub.hubUrl, + taxId: hub.taxId, + db: hub.defaultDb, + hubName: hub.name, + longLabel: hub.longLabel + }; + displayHubs.push(hubItem); + }); + + if (displayHubs.length === 0) { + // No non-curated hubs, hide the section + $title.hide(); + $section.hide(); + return; + } + + // Show the section and render hub cards + $title.show(); + $section.show(); + + // Custom click handler for hub cards + var onHubClick = function(item) { + setHubDb(item.hubUrl, item.taxId, item.db, item.hubName, null, false); + }; + + renderGenomeCards(displayHubs, $panel, null, onHubClick); } function addCommasToPosition(pos) { // Return seqName:start-end pos with commas inserted in start and end as necessary. var posComma = pos; var fourDigits = /(^.*:.*[0-9])([0-9]{3}\b.*)/; var matches = fourDigits.exec(posComma); while (matches) { posComma = matches[1] + ',' + matches[2]; matches = fourDigits.exec(posComma); } return posComma; } function onSelectGene(item) { @@ -1371,30 +1439,31 @@ function updateStateAndPage(jsonData) { // Update uiState with new values and update the page. var hubsChanged = !_.isEqual(jsonData.hubs, uiState.hubs); // In rare cases, there can be a genome (e.g. Baboon) with multiple species/taxIds // (e.g. Papio anubis for papAnu1 vs. Papio hamadryas for papHam1). Changing the // db can result in changing the taxId too. In that case, update the highlighted // species in the tree image. if (jsonData.taxId !== uiState.taxId) { highlightLabel('textEl_' + jsonData.taxId, false); } _.assign(uiState, jsonData); updateFindPositionSection(uiState); if (hubsChanged) { drawSpeciesPicker(prunedDbDbTree); + addHubsToList(uiState.hubs); } } function handleRefreshState(jsonData) { if (checkJsonData(jsonData, 'handleRefreshState')) { updateStateAndPage(jsonData); } } function isGenomeAtFrontOfRecents(db) { // Check if a genome with this db is already at the front of the recents list. // This is used to detect if autocompleteCat.js just added it (to avoid double-adding). // We compare without hub prefixes since autocompleteCat saves without prefix but // server responses may include prefixes like "hub_123_GCA_xxx". var stored = window.localStorage.getItem("recentGenomes"); @@ -1731,88 +1800,95 @@ "Interactive Tutorials</a>"; $("#help > ul")[0].appendChild(tutorialLinks); $("#hgGatewayHelpTutorialLinks").on("click", function () { // Check to see if the tutorial popup has been generated already var tutorialPopupExists = document.getElementById ("tutorialContainer"); if (!tutorialPopupExists) { // Create the tutorial popup if it doesn't exist createTutorialPopup(); } else { //otherwise use jquery-ui to open the popup $("#tutorialContainer").dialog("open"); } }); } - // Recent Genomes Panel functions (Option C layout) + // Genome Cards Panel functions (shared by recent genomes and connected hubs) - function renderRecentGenomesPanel(genomes) { - // Render recent genomes as vertical scrollable cards - var $panel = $('#recentGenomesList'); + function renderGenomeCards(items, $panel, emptyMessage, onCardClick) { + // Render genome/hub items as vertical scrollable cards + // items: array of genome or hub objects + // $panel: jQuery element to render into + // emptyMessage: message to show when no items + // onCardClick: optional callback for card clicks (defaults to setDbFromAutocomplete) $panel.empty(); - if (!genomes || genomes.length === 0) { - $panel.html('<div class="recentGenomesEmpty">Search for a genome above, ' + - 'or click a popular species icon</div>'); + if (!items || items.length === 0) { + if (emptyMessage) { + $panel.html('<div class="recentGenomesEmpty">' + emptyMessage + '</div>'); + } return; } - // Render each genome as a card (vertical layout) - genomes.forEach(function(item) { + var clickHandler = onCardClick || setDbFromAutocomplete; + + // Render each item as a card (vertical layout) + items.forEach(function(item) { var $card = $('<div class="recentGenomeCard"></div>'); - var label = item.label || item.value || item.genome || item.commonName; + var label = item.label || item.shortLabel || item.value || item.genome || item.commonName; var genome = item.genome || item.db || ''; $card.append('<div class="recentGenomeLabel">' + escapeHtml(label) + '</div>'); if (genome && label.indexOf(genome) < 0) { $card.append('<div class="recentGenomeDb">' + escapeHtml(genome) + '</div>'); } // Add category as small label if (item.category) { var shortCategory = item.category; if (shortCategory.indexOf('UCSC Genome Browser') >= 0) { shortCategory = 'UCSC'; } else if (shortCategory.indexOf('GenArk') >= 0) { shortCategory = 'GenArk'; } else if (shortCategory.indexOf('Assembly Hub') >= 0) { shortCategory = 'Hub'; } $card.append('<div class="recentGenomeCategory">' + escapeHtml(shortCategory) + '</div>'); } // Store item data for click handler $card.data('item', item); $card.on('click', function() { var clickedItem = $(this).data('item'); - setDbFromAutocomplete(clickedItem); - // Highlight selected card - $('.recentGenomeCard').removeClass('selected'); + clickHandler(clickedItem); + // Highlight selected card in this panel + $panel.find('.recentGenomeCard').removeClass('selected'); $(this).addClass('selected'); }); $panel.append($card); }); } function displayRecentGenomesInPanel() { // Display recent genomes in the panel on page load and after genome selection var recentGenomes = getRecentGenomes(); // Show the section (hidden by default in HTML) $('#recentGenomesTitle').show(); $('#recentGenomesSection').show(); - renderRecentGenomesPanel(recentGenomes); + var emptyMessage = 'Search for a genome above, or click a popular species icon'; + renderGenomeCards(recentGenomes, $('#recentGenomesList'), emptyMessage); } function escapeHtml(text) { // Simple HTML escape for display if (!text) return ''; return String(text) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"'); } function init() { // Boot up the page; initialize elements and install event handlers. var searchObj = {};