cb4c22a814d2a23d93d63cd621333be7529a36b2
chmalee
Fri Jul 22 15:24:08 2022 -0700
Make gene selections on hgGateway automatically go to hgTracks
diff --git src/hg/js/hgGateway.js src/hg/js/hgGateway.js
index 4ad291a..a942dfb 100644
--- src/hg/js/hgGateway.js
+++ src/hg/js/hgGateway.js
@@ -1,1579 +1,1585 @@
// hgGateway - autocomplete + graphical interface to select species, assembly & position.
// Copyright (C) 2016 The Regents of the University of California
// Several modules are defined in this file -- if some other code needs them someday,
// they can be moved out to lib files.
// function svgCreateEl: convenience function for creating new SVG elements, used by
// speciesTree and rainbow modules.
// 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
window.hgsid = '';
window.activeGenomes = {};
window.surveyLink=null;
window.surveyLabel=null;
window.surveyLabelImage=null;
function svgCreateEl(type, config) {
// Helper function for creating a new SVG element and initializing its
// properties and attributes. Type is something like 'rect', 'text', 'g', etc;
// config is an object like { id: 'newThingie', x: 0, y: 10, title: 'blah blah' }.
var svgns = 'http://www.w3.org/2000/svg';
var xlinkns = 'http://www.w3.org/1999/xlink';
var el = document.createElementNS(svgns, type);
var title, titleEl;
if (el) {
_.forEach(config, function(value, setting) {
if (setting === 'textContent') {
// Text content (the text in a text element or title element) is a property:
el.textContent = value;
} else if (setting === 'href') {
// href comes from a different namespace so must use setAttributeNS:
el.setAttributeNS(xlinkns, 'href', value);
} else if (setting === 'title') {
title = value;
} else if (setting === 'className') {
el.setAttribute('class', value);
} else {
// Most of the time we're just setting an attribute:
el.setAttribute(setting, value);
}
});
}
// Mouseover title actually requires creating a child element.
// Strangely, if I did this in the above loop, the child element was lost if
// props/attributes were set afterwards!! So save title for last.
if (title) {
titleEl = document.createElementNS(svgns, 'title');
titleEl.textContent = title;
el.appendChild(titleEl);
}
return el;
}
///////////////////////////// Module: speciesTree /////////////////////////////
var speciesTree = (function() {
// SVG phylogenetic tree of species in the browser (with connected assembly hubs above tree)
// Layout parameters/configuration (object passed into draw can override these defaults):
var cfg = { labelRightX: 230,
labelStartY: 18,
speciesLineOffsetX: 8,
branchLength: 5,
halfTextHeight: 4,
hubLineOffset: 100,
labelLineHeight: 18,
paddingRight: 5,
paddingBottom: 5,
branchPadding: 2,
trackHubsUrl: '',
containerWidth: 370
};
function checkTree(node) {
// Return true if node and its descendants are of the form
// Array[ label:String, taxId:Number, sciName:String, Array[ [node]...] ]
// e.g. ['root', taxId0, sciName0,
// [ ['leaf1', taxId1, sciName1, []],
// ['leaf2', taxId2, sciName2, []] ]
// ]
if (! _.isString(node[0])) {
console.log('label is not a string', node);
return false;
}
if (! _.isNumber(node[1])) {
console.log('taxId is not a number', node);
return false;
}
if (! (node[2] === null || _.isString(node[2]))) {
console.log('sciName is not null or string', node);
return false;
}
return _.every(node[3], checkTree);
}
function addDepth(node) {
// Each node is of the form [ label, taxId, sciName, node[] ]
// e.g. ['root', taxId0, sciName0,
// [ ['leaf1', taxId1, sciName1, []],
// ['leaf2', taxId2, sciName2, []] ]
// ]
// Add a fifth property to node: depth, i.e. the maximum number of
// branching nodes along any path from node to a leaf.
// Returns depth.
var kids = node[3];
var depth, deepestKid;
if (!kids || kids.length === 0) {
// Leaf: depth is 0
depth = 0;
} else if (kids.length === 1) {
// Node with one child: pass-through, depth is child's depth
depth = addDepth(kids[0]);
} else {
// Node with multiple children: depth is 1 + max child depth
deepestKid = _.max(kids, addDepth);
depth = 1 + deepestKid[4];
}
node[4] = depth;
return depth;
}
function addSpeciesLabel(svg, label, taxId, sciName, y) {
// Add a species label to svg at y offset
var text = svgCreateEl('text', { x: cfg.labelRightX, y: y,
id: 'textEl_' + taxId,
name: 'textEl_' + taxId,
title: sciName,
textContent: label
});
// CSP2 will not allow setAttribute with events like onclick,
// no matter whether the value is string or function.
text.onclick = function(){hgGateway.onClickSpeciesLabel(taxId);};
svg.appendChild(text);
}
function addLine(svg, x1, y1, x2, y2, mouseover) {
// Add optionally with mouseover title
var config = { x1: x1, y1: y1,
x2: x2, y2: y2,
title: mouseover };
var line;
if (x1 === x2) {
// vertical lines get special styling
config.className = 'vert';
}
line = svgCreateEl('line', config);
svg.appendChild(line);
}
function calcBranchX(kidRetList, depth, parentDepth, parentNodeDepth) {
// Return the x offset used for drawing lines from children and a vertical line
// connecting them.
// If the branch from parent is longer than the minimum length, we can scale myX
// to reflect how many nodes are skipped in branches to the right vs. to the left,
// ensuring the minimum branch length on either side. Without this adjustment,
// we sometimes get very long branches that skip only one node to the right of short
// branches that skip many nodes.
var myX;
var kidMaxX = _.max(kidRetList, 'x').x;
var branchSteps = parentDepth - depth;
var kidMinNodeDepth, ratioSkipped, stepsX;
if (branchSteps > 1) {
kidMinNodeDepth = _.min(kidRetList, 'nodeDepth').nodeDepth;
ratioSkipped = kidMinNodeDepth / (kidMinNodeDepth + parentNodeDepth);
stepsX = (branchSteps+1) * ratioSkipped;
// Make sure there is at least one step (min branch width) before and after
// stepsX.
if (stepsX < 1) {
stepsX = 1;
} else if (stepsX > branchSteps) {
stepsX = branchSteps;
}
myX = kidMaxX + cfg.branchLength * stepsX;
} else {
// Only one change in depth level, so short branch:
myX = kidMaxX + cfg.branchLength;
}
return myX;
}
function drawNode(svg, node, leafY, leafTops, parentDepth, parentNodeDepth) {
// Each node is of the form [ label, taxId, sciName, node[], depth ]
// e.g. ['root', taxId0, sciName0,
// [ ['leaf1', taxId1, sciName1, [], 0],
// ['leaf2', taxId2, sciName2, [], 0] ],
// 1,
// ]
// depth is the max number of branching nodes under this node.
// Recursively draw the tree by generating new SVG elements and adding them to svg.
// Returns {x:, y:, leafY:} where x and y are the endpoint for the parent's branch
// to this node and leafY is the Y offset for the next leaf label.
// For leaf nodes, draw labels, store y in leafTops, and return label coordinates.
// Nodes with a single child don't draw anything, they simply return the
// child's info.
// Nodes with multiple children draw a horizontal line to each child and
// a vertical line connecting the horizontal lines. They return the {X,Y} of
// the midpoint of the vertical line.
// optional arg parentDepth is the depth of the parent node as defined above.
// optional arg parentNodeDepth is 1 plus the number of single-child nodes that were
// skipped on the way from the last branching ancestor.
// For internal use, the return object also includes nodeDepth: 1 + number of
// nodes skipped between this node and the next branching descendant or leaf
// and mouseover: horizontal line labels showing child label plus any skipped
// nodes' labels.
var label = node[0], taxId = node[1], sciName = node[2], kids = node[3], depth = node[4];
var myX, myY;
var kidRet, kidRetList, kidMinY, kidMaxY, extraSpace;
parentDepth = parentDepth || 1;
parentNodeDepth = parentNodeDepth || 1;
if (!kids || kids.length === 0) {
// leaf node: draw species label, store myY in leafTops
addSpeciesLabel(svg, label, taxId, sciName, leafY);
myX = cfg.labelRightX + cfg.speciesLineOffsetX;
myY = leafY - cfg.halfTextHeight;
leafTops[label] = leafY - cfg.labelLineHeight;
return { x: myX, y: myY, leafY: leafY + cfg.labelLineHeight, nodeDepth: 1 };
} else if (kids.length === 1) {
// Single child: don't draw anything (keep the rendering compact),
// but make a note that we skipped a node so we can use it for mouseover text
kidRet = drawNode(svg, kids[0], leafY, leafTops, parentDepth, parentNodeDepth + 1);
kidRet.nodeDepth++;
if (kidRet.mouseover) {
kidRet.mouseover += ' - ' + label;
} else {
kidRet.mouseover = label;
}
return kidRet;
} else {
// Multiple children. First pass to draw kids and gather their coords,
// second pass to draw lines connecting kids.
extraSpace = (depth - 1) * cfg.branchPadding;
kidRetList = _.map(kids, function(kid) {
var kidRet = drawNode(svg, kid, leafY, leafTops, depth, 1);
if (! kidRet.mouseover) {
// If nothing was skipped, use kid's sciName / label (same as kid's vert line)
var kidLabel = kid[0], kidSciName = kid[2];
kidRet.mouseover = kidSciName ? kidSciName : kidLabel;
}
leafY = kidRet.leafY + extraSpace;
return kidRet;
});
myX = calcBranchX(kidRetList, depth, parentDepth, parentNodeDepth);
// Draw horizontal lines from kids
_.forEach(kidRetList, function(kidRet) {
addLine(svg, kidRet.x, kidRet.y, myX, kidRet.y, kidRet.mouseover);
});
// Draw vertical line connecting kids
kidMinY = kidRetList[0].y;
kidMaxY = kidRetList[kids.length-1].y;
addLine(svg, myX, kidMinY, myX, kidMaxY, label);
myY = (kidMinY + kidMaxY) / 2;
return { x: myX, y: myY, leafY: leafY, nodeDepth: 1, mouseover: label };
}
}
function addTrackHubsLink(svg, x, y) {
// Add a label with link to hgHubConnect at the given position.
var a = svgCreateEl('a', { 'href': cfg.hgHubConnectUrl });
var text = svgCreateEl('text', { x: x, y: y,
textContent: 'Hub Genomes',
className: 'trackHubsLink' });
a.appendChild(text);
svg.appendChild(a);
}
function doubleQuote(string) {
return '"' + string + '"';
}
function addHubLabel(svg, hub, y) {
// Add a track hub label to svg at y offset
var label = hub.shortLabel + ' (' + hub.assemblyCount + ')';
// There are a bunch of hub properties to pass to the onClick handler;
// too bad we can't pass a bound function but instead must build a string:
var text = svgCreateEl('text', { x: cfg.labelRightX, y: y,
textContent: label,
id: 'textEl_' + hub.name,
name: 'textEl_' + hub.name,
title: hub.longLabel
});
text.onclick = function() {hgGateway.onClickHubName(hub.hubUrl, hub.taxId, hub.defaultDb, hub.name);};
svg.appendChild(text);
}
function drawHubs(svg, hubList, yIn) {
// Add a label for each hub in hubList, with a separator line below and
// "Hub Genomes" link instead of a tree.
var y = yIn;
var hub, i, textX, textY, lineX1, lineY, lineX2;
if (hubList && hubList.length) {
for (i = 0; i < hubList.length; i++) {
hub = hubList[i];
addHubLabel(svg, hub, y);
y += cfg.labelLineHeight;
}
textX = cfg.labelRightX + cfg.speciesLineOffsetX;
textY = (yIn + y - cfg.labelLineHeight) / 2;
addTrackHubsLink(svg, textX, textY);
lineX1 = cfg.hubLineOffset;
lineY = y - cfg.halfTextHeight;
lineX2 = cfg.containerWidth - cfg.speciesLineOffsetX;
addLine(svg, lineX1, lineY, lineX2, lineY);
y += cfg.labelLineHeight;
}
return y;
}
function draw(svg, dbDbTree, hubList, cfgOverrides) {
// dbDbTree is the root node of a phylogenetic tree that we render in svg.
// cfgOverrides should be used to provide (names of) meaningful onclick functions
// and track hub URL.
// Return the width and height of the tree, the top offset of the tree (below hubs
// if any), and an object with the top coords of each label/leaf.
// Instead of tacking a bunch of children directly onto svg, make a (group)
// and append that to svg when done.
// First see if there's already something there that we will replace:
var oldG = svg.getElementById('hubsAndTree');
var newG = svgCreateEl('g', { id: 'hubsAndTree' });
// y offsets of tops of species labels (leaves of dbDbTree), filled in by drawNode.
var leafTops = {};
var hubBottomY, treeInfo, width, height;
if (! checkTree(dbDbTree)) {
console.error('dbDbTree in wrong format', dbDbTree);
return;
}
_.assign(cfg, cfgOverrides);
hubBottomY = drawHubs(newG, hubList, cfg.labelStartY);
addDepth(dbDbTree);
treeInfo = drawNode(newG, dbDbTree, hubBottomY, leafTops);
width = treeInfo.x + cfg.paddingRight;
if (width < cfg.containerWidth) {
width = cfg.containerWidth;
}
height = treeInfo.leafY - cfg.labelLineHeight + cfg.paddingBottom;
if (oldG) {
svg.removeChild(oldG);
}
svg.appendChild(newG);
return { width: width, height: height,
yTree: hubBottomY - cfg.labelLineHeight,
leafTops: leafTops };
}
return { draw: draw };
}()); // speciesTree
///////////////////////////// Module: rainbow /////////////////////////////
var rainbow = (function() {
// Add rainbow stripes with cute species icons on the left of an SVG element that
// already has a phylogenetic tree drawn by speciesTree.
// Layout parameters/configuration (object passed into draw can override these defaults):
var cfg = { stripeWidth: 69,
iconX: 2,
iconYOffset: -14,
iconWidth: 65,
iconHeight: 65,
iconSpriteUrl: '../images/jWestIconsAlpha65px.png',
iconSpriteWidth: 325,
iconSpriteHeight: 325
};
// Color spectrum for slider and tree display:
var stripeColors = [ '#7E1F16',
'#A12321',
'#C15026',
'#DF6933',
'#EB8734',
'#B57E2A',
'#CD9C2A',
'#CFB32B',
'#959E38',
'#3A8349',
'#216D6D',
'#4C749B',
'#31469A',
'#7E4475',
'#231F1F' ];
// Hubs go above the species rainbow -- give them their own color of stripe:
var hubColor = '#60180C';
// Taxonomy IDs for assigning rainbow stripes to species groups:
var stripeTaxIds = [ 9443, // Primates
314146, // Euarchontoglires
9362, // Insectivora
91561, // Cetartiodactyla
314145, // Laurasiatheria
9397, // Chiroptera
9347, // Eutheria
40674, // Mammalia
32523, // Tetrapoda
7742, // Vertebrata
33511, // Deuterostomia
33392, // Endopterygota
6072, // Eumetazoa
2759, // Eukaryota
1 ]; // root
// Cute species icons placed along the stripe also help to orient.
// This maps leaf labels to icon names:
var iconSpeciesToName = { Human: 'Human',
Mouse: 'Mouse',
'D. melanogaster': 'Fly',
'C. elegans': 'Worm',
'S. cerevisiae': 'Yeast',
'Rhesus': 'Monkey',
Hedgehog: 'Hedgehog',
// Pig: 'Pig',
Cow: 'Cow',
'Killer whale': 'Orca',
Horse: 'Horse',
Dog: 'Dog',
'Pacific walrus': 'Walrus',
'Megabat': 'Bat',
Elephant: 'Elephant',
Manatee: 'Manatee',
Armadillo: 'Armadillo',
'Wallaby': 'Kangaroo',
'Zebra finch': 'Bird',
Lizard: 'Lizard',
'X. tropicalis': 'Frog',
'Fugu': 'Fish',
'Zaire ebolavirus': 'Ebola', // on hgwdev April 2016
'Ebola virus': 'Ebola' // on RR April 2016
};
// The icon sprite image has 5 rows and 5 columns:
var spriteRowCol = { Human: [0,0],
Mouse: [0,1],
Fly: [0,3],
Worm: [0,4],
Yeast: [1,0],
Monkey: [1,1],
Hedgehog: [1,2],
Pig: [1,3],
Cow: [1,4],
Orca: [2,0],
Horse: [2,1],
Dog: [2,2],
Walrus: [2,3],
Bat: [2,4],
Elephant: [3,0],
Manatee: [3,1],
Armadillo: [3,2],
Kangaroo: [3,3],
Bird: [3,4],
Lizard: [4,0],
Frog: [4,1],
Fish: [4,2],
Ebola: [4,3]
};
// Some icon drawings are shorter than others, and some need to be moved up to make space
// for close neighbors.
var iconFudgeY = { Human: 20,
Mouse: 0,
Fly: 0,
Worm: 0,
Monkey: 0,
Hedgehog: -18,
Pig: -5,
Cow: 0,
Orca: 0,
Horse: -8,
Dog: 15,
Walrus: 40,
Bat: 0,
Elephant: -20,
Manatee: 20,
Armadillo: 0,
Kangaroo: 0,
Bird: 0,
Lizard: -20,
Frog: -10,
Fish: 0,
Yeast: -15,
Ebola: 0
};
function findStripeTops(node, parentStripeIx, leafTops, stripeTops, yPrev) {
// Each node is of the form [ label, taxId, sciName, node[], ... ]
// Recursively find the top coordinate of each stripe in stripeTaxIds,
// using node taxId. Modifies stripeTops. Returns the y of the top of the
// last leaf visited.
var label = node[0], taxId = node[1], kids = node[3];
var stripeIx = stripeTaxIds.indexOf(taxId);
var i;
// Inherit parent stripe unless this node is found in stripeTaxIds:
if (stripeIx < 0) {
stripeIx = parentStripeIx;
}
if (!kids || kids.length === 0) {
// leaf node: if this stripe's top coord has not yet been assigned,
// assign it.
if (stripeTops[stripeIx] === undefined) {
stripeTops[stripeIx] = (leafTops[label] + yPrev) / 2;
}
yPrev = leafTops[label];
} else {
// descend to children
for (i = 0; i < kids.length; i++) {
yPrev = findStripeTops(kids[i], stripeIx, leafTops, stripeTops, yPrev);
}
}
return yPrev;
}
function addRectFill(svg, x, y, width, height, color) {
// Add filled rectangle to svg
var rect = svgCreateEl('rect', { x: x, y: y,
width: width, height: height,
style: 'fill:' + color + '; stroke:' + color });
svg.appendChild(rect);
}
function drawStripes(svg, dbDbTree, yTop, height, leafTops) {
var stripeCount = stripeColors.length;
var lastStripeIx = stripeCount - 1;
var stripeTops = [];
var i, stripeHeight;
findStripeTops(dbDbTree, lastStripeIx, leafTops, stripeTops, yTop);
// Add an extra "stripe" coord so we have the coord for the bottom of the last stripe:
stripeTops[stripeCount] = height;
// Initialize missing stripes to 0-height (top = next stripe's top), if any:
for (i = stripeCount - 1; i >= 0; i--) {
if (stripeTops[i] === undefined) {
stripeTops[i] = stripeTops[i+1];
}
}
for (i = 0; i < stripeCount; i++) {
stripeHeight = stripeTops[i+1] - stripeTops[i];
addRectFill(svg, 0, stripeTops[i], cfg.stripeWidth, stripeHeight, stripeColors[i]);
}
// Add stripe for hubs, if any:
if (yTop > 0) {
addRectFill(svg, 0, 0, cfg.stripeWidth, yTop, hubColor);
}
return stripeTops;
}
function drawOneIcon(svg, name, y) {
// Create an image, offset so that the icon is positioned where we need it,
// and use a clip-path to limit display to just that icon, not the whole sprite image.
var iconY = y + cfg.iconYOffset + iconFudgeY[name];
var rowCol = spriteRowCol[name];
var row = rowCol[0], column = rowCol[1];
var clipPathId = 'clip' + name;
var img = svgCreateEl('image', { x: cfg.iconX - (column * cfg.iconWidth),
y: iconY - (row * cfg.iconHeight),
width: cfg.iconSpriteWidth,
height: cfg.iconSpriteHeight,
style: 'clip-path: url(#' + clipPathId + ')',
href: cfg.iconSpriteUrl });
// Set the y of the pre-existing clip path:
var rect = $('#' + clipPathId + ' rect')[0];
rect.setAttribute('y', iconY);
svg.appendChild(img);
}
function drawIcons(svg, leafTops) {
// For each icon listed in iconSpeciesToName, look up the species' y offset in
// the tree and add the icon to svg.
_.forEach(iconSpeciesToName, function (name, species) {
var y = leafTops[species];
if (y >= 0) {
drawOneIcon(svg, name, y);
}
});
}
function draw(svg, dbDbTree, yTree, height, leafTops) {
// Draw stripes with colors corresponding to species groups and cute-species icons.
// Return y offsets of stripes so that a slider widget can be drawn accordingly.
// Instead of tacking a bunch of children directly onto svg, make a (group)
// and append that to svg when done.
// First see if there's already something there that we will replace:
var oldG = svg.getElementById('stripesAndIcons');
var newG = svgCreateEl('g', { id: 'stripesAndIcons' });
var stripeTops = drawStripes(newG, dbDbTree, yTree, height, leafTops);
drawIcons(newG, leafTops);
if (oldG) {
svg.removeChild(oldG);
}
svg.appendChild(newG);
return stripeTops;
}
return { draw: draw,
colors: stripeColors,
hubColor: hubColor
};
}()); // rainbow
///////////////////////////// Module: hgGateway /////////////////////////////
var hgGateway = (function() {
// Interactive parts of the new gateway page: species autocomplete,
// graphical species-picker, db select, and position autocomplete.
// Constants
var speciesWatermark = 'Enter species, common name or assembly ID';
var positionWatermark = 'Enter position, gene symbol or search terms';
// Shortcuts to popular species:
var favIconTaxId = [ ['Human', 9606],
['Mouse', 10090],
['Rat', 10116],
['Zebrafish', 7955],
['Fly', 7227],
['Worm', 6239],
['Yeast', 559292] ];
// Aliases for species autocomplete:
var commonToSciNames = { bats: 'Chiroptera',
bees: 'Apoidea',
birds: 'Aves',
fish: 'Actinopterygii',
fly: 'Diptera',
flies: 'Diptera',
frogs: 'Anura',
fruitfly: 'Drosophila',
'fruit fly': 'Drosophila',
honeybees: 'Apinae',
'honey bees': 'Apinae',
monkeys: 'Simiiformes',
mosquitos: 'Culicidae',
worms: 'Nematoda',
yeast: 'Ascomycota' };
var getBetterBrowserMessage = '
' +
'Our website has detected that you are using ' +
'an outdated browser that will prevent you from ' +
'accessing certain features. An update is not ' +
'required, but it is strongly recommended to ' +
'improve your browsing experience. ' +
'Please use the following links to upgrade your ' +
'existing browser to ' +
'' +
' FireFox or ' +
'' +
'Chrome.' +
'
';
// Globals (within this function scope)
// Set this to true to see server requests and responses in the console.
var debugCartJson = false;
// This is a global (within wrapper function scope) so event handlers can use it
// without needing to bind functions.
var scrollbarWidth = 0;
// This holds everything we need to know to draw the page: taxId, db, hubs, description etc.
var uiState = {};
// This is used to check whether a taxId is found in activeGenomes:
var activeTaxIds = []; // gets set in init() now;
// This is dbDbTree after pruning -- null if dbDbTree has no children left
var prunedDbDbTree = null;
// This keeps track of which gene the user has selected most recently from autocomplete.
var selectedGene = null;
function setupFavIcons() {
// Set up onclick handlers for shortcut buttons and labels
var haveIcon = false;
var i, name, taxId, onClick;
for (i = 0; i < favIconTaxId.length; i++) {
name = favIconTaxId[i][0];
taxId = favIconTaxId[i][1];
if (activeTaxIds[taxId]) {
// When user clicks on icon, set the taxId (default database);
// scroll the image to that species and clear the species autocomplete input.
onClick = setTaxId.bind(null, taxId, null, true, true);
// Onclick for both the icon and its sibling label:
$('.jwIconSprite' + name).parent().children().click(onClick);
haveIcon = true;
} else {
// Inactive on this site -- hide it
$('.jwIconSprite' + name).parent().hide();
}
}
if (! haveIcon) {
$('#popSpeciesTitle').text('Species Search');
}
}
function addCategory(cat, item) {
// Clone item, add category: cat to it and return it (helper function, see below).
var clone = {};
_.assign(clone, item, { category: cat });
return clone;
}
function autocompleteFromNode(node) {
// Traverse dbDbTree to make autocomplete result lists for all non-leaf node labels.
// Returns an object mapping each label of node and descendants to a list of
// result objects with the same structure that we'd get from a server request.
if (! node) {
return;
}
var searchObj = {};
var myResults = [];
var label = node[0], taxId = node[1], kids = node[3];
var addMyLabel;
if (!kids || kids.length === 0) {
// leaf node: return autocomplete result for species
myResults = [ { genome: label,
label: label,
taxId: taxId } ];
} else {
// Accumulate the list of all children's result lists;
// keep each's child searchObj mappings unless the child is a leaf
// (which would be redundant with server autocomplete results).
addMyLabel = addCategory.bind(null, label);
myResults = _.flatten(
_.map(kids, function(kid) {
var kidLabel = kid[0], kidKids = kid[3];
var kidObj = autocompleteFromNode(kid);
// Clone kid's result list and add own label as category:
var kidResults = _.map(kidObj[kidLabel], addMyLabel);
// Add kid's mappings to searchObj only if kid is not a leaf.
if (kidKids && kidKids.length > 0) {
_.assign(searchObj, kidObj);
}
return kidResults;
})
);
}
// Exclude some overly broad categories:
if (label !== 'root' && label !== 'cellular organisms') {
searchObj[label] = myResults;
}
return searchObj;
}
function autocompleteFromTree(node, searchObj) {
// Traverse dbDbTree to make autocomplete result lists for all non-leaf node labels.
// searchObj is extended to map each label of node and descendants to a list of
// result objects with the same structure that we'd get from a server request.
_.assign(searchObj, autocompleteFromNode(node));
// Add aliases for some common names that map to scientific names in the tree.
_.forEach(commonToSciNames, function(sciName, commonName) {
var label, addMyLabel;
if (searchObj[sciName]) {
label = sciName + ' (' + commonName + ')';
addMyLabel = addCategory.bind(null, label);
searchObj[commonName] = _.map(searchObj[sciName], addMyLabel);
}
});
}
function makeStripe(id, color, stripeHeight, scrollTop, onClickStripe) {
// Return an empty div with specified background color and height
var $stripe = $('