8c908f948b09826c6cb4452ee5b282aca41be85e galt Tue Dec 8 21:52:59 2015 -0800 Multi-region (exonMostly). This work allows people to look at virtual chromosomes from a list of regions and then navigate and perform all of the usual functions on it. diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index ad65569..eba5e3a 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -16,31 +16,31 @@ * int newWinWidth // new width (in bps) if user clicks on the top ruler * boolean revCmplDisp // true if we are in reverse display * int insideX // width of side-bar (in pixels) * int rulerClickHeight // height of ruler (in pixels) - zero if ruler is hidden * boolean inPlaceUpdate // true if in-place-update is turned on * int imgBox* // various drag-scroll values * boolean measureTiming // true if measureTiming is on * Object trackDb // hash of trackDb entries for tracks which are visible on current page */ function initVars() { // There are various entry points, so we call initVars in several places to make sure all is well if (typeof(hgTracks) !== "undefined" && !genomePos.original) { // remember initial position and size so we can restore it if user cancels genomePos.original = genomePos.getOriginalPos(); - genomePos.originalSize = $('#size').text(); + genomePos.originalSize = $('#size').text().replace(/,/g, ""); // strip out any commas dragSelect.originalCursor = jQuery('body').css('cursor'); imageV2.imgTbl = $('#imgTbl'); // imageV2.enabled === true unless: advancedJavascript===false, or trackSearch, or config pg imageV2.enabled = (imageV2.imgTbl && imageV2.imgTbl.length > 0); // jQuery load function with stuff to support drag selection in track img if (theClient.isSafari()) { // Safari has the following bug: if we update the hgTracks map dynamically, // the browser ignores the changes (even though if you look in the DOM the changes // are there). So we have to do a full form submission when the user changes // visibility settings or track configuration. // As of 5.0.4 (7533.20.27) this is problem still exists in safari. // As of 5.1 (7534.50) this problem appears to have been fixed - unfortunately, // logs for 7/2011 show vast majority of safari users are pre-5.1 (5.0.5 is by far @@ -72,32 +72,32 @@ linkFixup: function (pos, id, reg, endParamName) { // fixup external links (e.g. ensembl) var ele = $(document.getElementById(id)); if (ele.length) { var link = ele.attr('href'); var a = reg.exec(link); if (a && a[1]) { ele.attr('href', a[1] + pos.start + "&" + endParamName + "=" + pos.end); } } }, setByCoordinates: function (chrom, start, end) { - var newPosition = chrom + ":" + commify(start) + "-" + commify(end); - genomePos.set(newPosition, commify(end - start + 1)); + var newPosition = chrom + ":" + start + "-" + end; + genomePos.set(newPosition, end - start + 1); return newPosition; }, getElement: function () { // Return position box object var tags = document.getElementsByName("position"); // There are multiple tags with name === "position" (the visible position text input // and a hidden with id='positionHidden'); we return value of visible element. for (var i = 0; i < tags.length; i++) { var ele = tags[i]; if (ele.id !== "positionHidden") { return ele; } } @@ -114,50 +114,214 @@ return null; }, getOriginalPos: function () { return genomePos.original || genomePos.get(); }, revertToOriginalPos: function () { // undo changes to position (i.e. after user aborts a drag-and-select). this.set(this.original, this.originalSize); this.original = this.originalSize = null; // not sure if this is necessary. }, + undisguisePosition: function(position) // UN-DISGUISE VMODE + { // find the virt position + // position should be real chrom span + //warn("undisguisePosition position="+position); // DEBUG REMOVE + var pos = parsePosition(position); + if (!pos) + return position; // some parsing error, return original + var start = pos.start - 1; + var end = pos.end; + var chromName = hgTracks.windows[0].chromName; + if (pos.chrom !== chromName) + return position; // return original + var newStart = -1; + var newEnd = -1; + var lastW = null; + var windows = null; + //warn("start="+start+" end="+end); // DEBUG REMOVE + for (j=0; j < 3; ++j) { + if (j === 0) windows = hgTracks.windowsBefore; + if (j === 1) windows = hgTracks.windows; + if (j === 2) windows = hgTracks.windowsAfter; + for (i=0,len=windows.length; i < len; ++i) { + var w = windows[i]; + // double check chrom is same thoughout all windows, otherwise warning, return original value + if (w.chromName != chromName) { + return position; // return original + } + // check that the regions are ascending and non-overlapping + if (lastW && w.winStart < lastW.winEnd) { + return position; // return original + } + // overlap with position? + // if intersection, + if (w.winEnd > start && end > w.winStart) { + var s = Math.max(start, w.winStart); + var e = Math.min(end, w.winEnd); + var cs = s - w.winStart + w.virtStart; + var ce = e - w.winStart + w.virtStart; + //warn("cs="+cs+" ce="+ce); // DEBUG REMOVE + if (newStart === -1) + newStart = cs; + newEnd = ce; + } + lastW = w; + } + } + // return new virt undisguised position as a string + var newPos = "virt:" + (newStart+1) + "-" + newEnd; + //warn("undisguisePosition newPos="+newPos); // DEBUG REMOVE + return newPos; + }, + + disguiseSize: function(position) // DISGUISE VMODE + { // find the real size of the windows spanned + // position should be a real chrom span + //warn("disguisePosition position="+position); // DEBUG REMOVE + var pos = parsePosition(position); + if (!pos) + return 0; + var start = pos.start - 1; + var end = pos.end; + var newSize = 0; + var windows = null; + //warn("start="+start+" end="+end); // DEBUG REMOVE + for (j=0; j < 3; ++j) { + if (j === 0) windows = hgTracks.windowsBefore; + if (j === 1) windows = hgTracks.windows; + if (j === 2) windows = hgTracks.windowsAfter; + for (i=0,len=windows.length; i < len; ++i) { + var w = windows[i]; + // overlap with position? + // if intersection, + if (w.winEnd > start && end > w.winStart) { + var s = Math.max(start, w.winStart); + var e = Math.min(end, w.winEnd); + //warn("s="+s+" e="+e); // DEBUG REMOVE + newSize += (e - s); + } + } + } + // return real size of the disguised position + //warn("disguiseSize newSize="+newSize); // DEBUG REMOVE + return newSize; + }, + + disguisePosition: function(position) // DISGUISE VMODE + { // find the single-chrom range spanned + // position should be virt + //warn("disguisePosition position="+position); // DEBUG REMOVE + var pos = parsePosition(position); + if (!pos) + return position; // some parsing error, return original + var start = pos.start - 1; + var end = pos.end; + var chromName = hgTracks.windows[0].chromName; + var newStart = -1; + var newEnd = -1; + var lastW = null; + var windows = null; + //warn("start="+start+" end="+end); // DEBUG REMOVE + for (j=0; j < 3; ++j) { + if (j === 0) windows = hgTracks.windowsBefore; + if (j === 1) windows = hgTracks.windows; + if (j === 2) windows = hgTracks.windowsAfter; + for (i=0,len=windows.length; i < len; ++i) { + var w = windows[i]; + // double check chrom is same thoughout all windows, otherwise warning, return original value + if (w.chromName != chromName) { + return position; // return undisguised original + } + // check that the regions are ascending and non-overlapping + if (lastW && w.winStart < lastW.winEnd) { + return position; // return undisguised original + } + // overlap with position? + // if intersection, + if (w.virtEnd > start && end > w.virtStart) { + var s = Math.max(start, w.virtStart); + var e = Math.min(end, w.virtEnd); + var cs = s - w.virtStart + w.winStart; + var ce = e - w.virtStart + w.winStart; + //warn("cs="+cs+" ce="+ce); // DEBUG REMOVE + if (newStart === -1) + newStart = cs; + newEnd = ce; + } + lastW = w; + } + } + // return new non-virt disguised position as a string + var newPos = chromName + ":" + (newStart+1) + "-" + newEnd; + //warn("disguisePosition newPos="+newPos); // DEBUG REMOVE + return newPos; + }, + set: function (position, size) { // Set value of position and size (in hiddens and input elements). // We assume size has already been commified. // Either position or size may be null. + + // stack dump // DEBUG + //console.trace(); + // NOT work on safari + //var obj = {}; + //Error.captureStackTrace(obj); + //warn("genomePos.set() called "+obj.stack); + + position = position.replace(/,/g, ""); // strip out any commas + + if (position) { + // DISGUISE VMODE + //warn("genomePos.set() called, position = "+position); + if (hgTracks.virtualSingleChrom && (position.search("virt:")===0)) { + var newPosition = genomePos.disguisePosition(position); + //warn("genomePos.set() position = "+position+", newPosition = "+newPosition); + position = newPosition; + } + } if (position) { // There are multiple tags with name === "position" // (one in TrackHeaderForm and another in TrackForm). var tags = document.getElementsByName("position"); for (var i = 0; i < tags.length; i++) { var ele = tags[i]; ele.value = position; } } + var pos = parsePosition(position); if ($('#positionDisplay').length) { - $('#positionDisplay').text(position); + // add commas to positionDisplay + var commaPosition = position; + if (pos) + commaPosition = pos.chrom+":"+commify(pos.start)+"-"+commify(pos.end); + $('#positionDisplay').text(commaPosition); } if (size) { - $('#size').text(size); + if (hgTracks.virtualSingleChrom && (position.search("virt:")!==0)) { + var newSize = genomePos.disguiseSize(position); + //warn("genomePos.set() position = "+position+", newSize = "+newSize); + if (newSize > 0) + size = newSize; + } + $('#size').text(commify(size)); // add commas } - var pos = parsePosition(position); if (pos) { // fixup external static links on page' // Example ensembl link: // http://www.ensembl.org/Homo_sapiens/contigview?chr=21&start=33031934&end=33041241 genomePos.linkFixup(pos, "ensemblLink", new RegExp("(.+start=)[0-9]+"), "end"); // Example NCBI link: // http://www.ncbi.nlm.nih.gov/mapview/maps.cgi?taxid=9606&CHR=21&BEG=33031934&END=33041241 genomePos.linkFixup(pos, "ncbiLink", new RegExp("(.+BEG=)[0-9]+"), "END"); // Example medaka link: // http://utgenome.org/medakabrowser_ens_jump.php?revision=version1.0&chr=chromosome18&start=14435198&end=14444829 genomePos.linkFixup(pos, "medakaLink", new RegExp("(.+start=)[0-9]+"), "end"); @@ -231,71 +395,209 @@ endDelta = startDelta; x2 = Math.min(imgWidth, selEnd); startDelta = Math.floor(mult * (imgWidth - x2)); } else { x2 = Math.max(hgTracks.insideX, selEnd); endDelta = Math.floor(mult * (x2 - hgTracks.insideX)); } var newStart = hgTracks.winStart + startDelta; var newEnd = hgTracks.winStart + 1 + endDelta; if (newEnd > winEnd) { newEnd = winEnd; } return {chromStart : newStart, chromEnd : newEnd}; }, + chromToVirtChrom: function (chrom, chromStart, chromEnd) + { // Convert regular chromosome position to virtual chrom coordinates using hgTracks.windows list + // Consider the first contiguous set of overlapping regions to define the match (for now). + // only works for regions covered by the current hgTracks.windows + var virtStart = -1, virtEnd = -1; + var s,e; + var i, len; + //warn("length of hgTracks.windows = "+hgTracks.windows.length); // DEBUG REMOVE + for (i = 0, len = hgTracks.windows.length; i < len; ++i) { + var w = hgTracks.windows[i]; + var overlap = (chrom == w.chromName && chromEnd > w.winStart && w.winEnd > chromStart); + //warn("w.chromName="+w.chromName+" w.winStart="+w.winStart+" w.winEnd="+w.winEnd+" overlap?="+overlap+" virtStart="+virtStart); // DEBUG REMOVE + if (virtStart == -1) { + if (overlap) { + // when they overlap the first time + s = Math.max(chromStart, w.winStart); + e = Math.min(chromEnd, w.winEnd); + virtStart = w.virtStart + (s - w.winStart); + virtEnd = w.virtStart + (e - w.winStart); + //warn("s="+s+" e="+e+" virtStart="+virtStart+" virtEnd="+virtEnd); // DEBUG REMOVE + } else { + // until they overlap + // do nothing + } + } else { + if (overlap) { + // while they continue to overlap, extend + e = Math.min(chromEnd, w.winEnd); + virtEnd = w.virtStart + (e - w.winStart); + //warn("extend virtEnd="+virtEnd); // DEBUG REMOVE + } else { + // when they do not overlap anymore, stop + break; + } + } + } + return {chromStart : virtStart, chromEnd : virtEnd}; + }, + selectionPixelsToBases: function (img, selection) { // Convert selection x1/x2 coordinates to chromStart/chromEnd. return genomePos.pixelsToBases(img, selection.x1, selection.x2, hgTracks.winStart, hgTracks.winEnd); }, update: function (img, selection, singleClick) { var pos = genomePos.pixelsToBases(img, selection.x1, selection.x2, hgTracks.winStart, hgTracks.winEnd); // singleClick is true when the mouse hasn't moved (or has only moved a small amount). if (singleClick) { var center = (pos.chromStart + pos.chromEnd)/2; pos.chromStart = Math.floor(center - hgTracks.newWinWidth/2); pos.chromEnd = pos.chromStart + hgTracks.newWinWidth; + // clip + if (pos.chromStart < hgTracks.chromStart) + pos.chromStart = hgTracks.chromStart; // usually 1 + if (pos.chromEnd > hgTracks.chromEnd) + pos.chromEnd = hgTracks.chromEnd; // usually virt chrom size + + // save current position so that that it may be restored after highlight or cancel. + genomePos.original = genomePos.getOriginalPos(); + genomePos.originalSize = $('#size').text().replace(/,/g, ""); // strip out any commas + } var newPosition = genomePos.setByCoordinates(hgTracks.chromName, pos.chromStart+1, pos.chromEnd); return newPosition; }, handleChange: function (response, status) { var json = JSON.parse(response); genomePos.set(json.pos); }, changeAssemblies: function (ele) // UNUSED? Larry's experimental code { // code to update page when user changes assembly select list. $.ajax({ type: "GET", url: "../cgi-bin/hgApi", data: cart.varsToUrlData({ 'cmd': 'defaultPos', 'db': getDb() }), dataType: "html", trueSuccess: genomePos.handleChange, success: catchErrorOrDispatch, error: errorHandler, cache: true }); return false; + }, + + convertedVirtCoords : {chromStart : -1, chromEnd : -1}, + + handleConvertChromPosToVirtCoords: function (response, status) + { + var virtStart = -1, virtEnd = -1; + var newJson = scrapeVariable(response, "convertChromToVirtChrom"); + if (!newJson) { + warn("convertChromToVirtChrom object is missing from the response"); + } else { + virtStart = newJson.virtWinStart; + virtEnd = newJson.virtWinEnd; + } + genomePos.convertedVirtCoords = {chromStart : virtStart, chromEnd : virtEnd}; + }, + + convertChromPosToVirtCoords: function (chrom, chromStart, chromEnd) + { // code to convert chrom position to virt coords + genomePos.convertedVirtCoords = {chromStart : -1, chromEnd : -1}; // reset + var pos = chrom+":"+(chromStart+1)+"-"+chromEnd; // easier to pass 1 parameter than 3 + $.ajax({ + type: "GET", + async: false, // wait for result + url: "../cgi-bin/hgTracks", + data: cart.varsToUrlData({ 'hgt.convertChromToVirtChrom': pos, 'hgt.trackImgOnly' : 1, 'hgsid': getHgsid() }), + dataType: "html", + trueSuccess: genomePos.handleConvertChromPosToVirtCoords, + success: catchErrorOrDispatch, + error: errorHandler, + cache: false + }); + return genomePos.convertedVirtCoords; + }, + + positionDisplayDialog: function () + // Show the virtual and real positions of the windows + { + var position = genomePos.get(); + //warn("positionDisplayDialog position="+position); // DEBUG REMOVE + var positionDialog = $("#positionDialog")[0]; + if (!positionDialog) { + $("body").append("
"); + positionDialog = $("#positionDialog")[0]; + } + if (hgTracks.windows) { + var i,len; + var str = position; + if (!(hgTracks.virtualSingleChrom && (hgTracks.windows.length === 1))) { + str += "
\n"; + str += "
\n"; + str += "\n"; + } + $("#positionDisplayPosition").html(str); + } else { + $("#positionDisplayPosition").html(position); + } + $(positionDialog).dialog({ + modal: true, + title: "Window-Positions", + closeOnEscape: true, + resizable: false, + autoOpen: false, + minWidth: 400, + minHeight: 40, // DEBUG GALT + buttons: { + "OK": function() { + $(this).dialog("close"); + } + }, + + open: function () { // Make OK the focus/default action + $(this).parents('.ui-dialog-buttonpane button:eq(0)').focus(); + }, + + close: function() { + // All exits to dialog should go through this + $(imageV2.imgTbl).imgAreaSelect({hide:true}); + $(this).hide(); + $('body').css('cursor', ''); // Occasionally wait cursor got left behind + } + }); + $(positionDialog).dialog('open'); } + }; ///////////////////////////////////// //// Creating items by dragging ///// ///////////////////////////////////// var makeItemsByDrag = { end: function (img, selection) { var image = $(img); var imageId = image.attr('id'); var trackName = imageId.substring('img_data_'.length); var pos = genomePos.selectionPixelsToBases(image, selection); var command = document.getElementById('hgt_doJsCommand'); command.value = "makeItems " + trackName + " " + hgTracks.chromName; @@ -694,97 +996,162 @@ { if (selection.x1 !== selection.x2) { if (genomePos.check(img, selection)) { genomePos.update(img, selection, false); jQuery('body').css('cursor', dragSelect.originalCursor); } else { jQuery('body').css('cursor', 'not-allowed'); } } return true; }, highlightThisRegion: function(newPosition) // set highlighting newPosition in server-side cart and apply the highlighting in local UI. { - var start, end; - if (arguments.length === 2) { - start = arguments[0]; - end = arguments[1]; - } else { var pos = parsePosition(newPosition); - start = pos.start; - end = pos.end; - } - hgTracks.highlight = getDb() + "." + hgTracks.chromName + ":" + start + "-" + end; - hgTracks.highlight += '#AAFFFF'; // Also include highlight color + var start = pos.start; + var end = pos.end; + //warn("highlightThisRegion: newPosition="+newPosition); // DEBUG REMOVE + hgTracks.highlight = getDb() + "." + pos.chrom + ":" + start + "-" + end + '#AAFFFF'; + hgTracks.highlight = imageV2.disguiseHighlight(hgTracks.highlight); // we include enableHighlightingDialog because it may have been changed by the dialog - cart.setVarsObj({ 'highlight': hgTracks.highlight, - 'enableHighlightingDialog': hgTracks.enableHighlightingDialog ? 1 : 0 }); + var cartSettings = { 'highlight': hgTracks.highlight, + 'enableHighlightingDialog': hgTracks.enableHighlightingDialog ? 1 : 0 }; + + if (hgTracks.windows && !hgTracks.virtualSingleChrom) { + var nonVirtChrom = ""; + var nonVirtStart = -1; + var nonVirtEnd = -1; + for (i=0,len=hgTracks.windows.length; i < len; ++i) { + var w = hgTracks.windows[i]; + // overlap with new position? + if (w.virtEnd > start && end > w.virtStart) { + var s = Math.max(start, w.virtStart); + var e = Math.min(end, w.virtEnd); + var cs = s - w.virtStart + w.winStart; + var ce = e - w.virtStart + w.winStart; + if (nonVirtChrom === "") { + nonVirtChrom = w.chromName; + nonVirtStart = cs; + nonVirtEnd = ce; + } else { + if (w.chromName === nonVirtChrom) { + nonVirtEnd = Math.max(ce, nonVirtEnd); + } else { + break; + } + } + } + } + if (nonVirtChrom !== "") + cartSettings.nonVirtHighlight = getDb() + '.' + nonVirtChrom + ':' + nonVirtStart + '-' + (nonVirtEnd+1) + '#AAFFFF'; + } else if (hgTracks.windows && hgTracks.virtualSingleChrom) { + cartSettings.nonVirtHighlight = hgTracks.highlight; + } + // TODO if not virt, do we need to erase cart nonVirtHighlight ? + cart.setVarsObj(cartSettings); imageV2.highlightRegion(); }, selectionEndDialog: function (newPosition) // Let user choose between zoom-in and highlighting. { var dragSelectDialog = $("#dragSelectDialog")[0]; if (!dragSelectDialog) { - $("body").append("
" + newPosition + + $("body").append("
" + "

" + "Don't show this dialog again and always zoom.
" + "(Re-enable highlight via the 'configure' menu at any time.)

"); dragSelectDialog = $("#dragSelectDialog")[0]; } + if (hgTracks.windows) { + var i,len; + var newerPosition = newPosition; + if (hgTracks.virtualSingleChrom && (newPosition.search("virt:")===0)) { + newerPosition = genomePos.disguisePosition(newPosition); + } + var str = newerPosition + "
\n"; + var str2 = "
\n"; + str2 += "
    \n"; + var pos = parsePosition(newPosition); + var start = pos.start - 1; + var end = pos.end; + var selectedRegions = 0; + for (i=0,len=hgTracks.windows.length; i < len; ++i) { + var w = hgTracks.windows[i]; + // overlap with new position? + if (w.virtEnd > start && end > w.virtStart) { + var s = Math.max(start, w.virtStart); + var e = Math.min(end, w.virtEnd); + var cs = s - w.virtStart + w.winStart; + var ce = e - w.virtStart + w.winStart; + str2 += "
  • " + w.chromName + ":" + (cs+1) + "-" + ce + "
  • \n"; + selectedRegions += 1; + } + } + str2 += "
\n"; + if (!(hgTracks.virtualSingleChrom && (selectedRegions === 1))) { + str += str2; + } + $("#dragSelectPosition").html(str); + } else { + $("#dragSelectPosition").html(newPosition); + } $(dragSelectDialog).dialog({ modal: true, title: "Drag-and-select", closeOnEscape: true, resizable: false, autoOpen: false, revertToOriginalPos: true, minWidth: 400, buttons: { "Zoom In": function() { // Zoom to selection $(this).dialog("option", "revertToOriginalPos", false); if ($("#disableDragHighlight").attr('checked')) hgTracks.enableHighlightingDialog = false; if (imageV2.inPlaceUpdate) { + if (hgTracks.virtualSingleChrom && (newPosition.search("virt:")===0)) { + newPosition = genomePos.disguisePosition(newPosition); // DISGUISE + } var params = "position=" + newPosition; if (!hgTracks.enableHighlightingDialog) params += "&enableHighlightingDialog=0"; imageV2.navigateInPlace(params, null, true); } else { $('body').css('cursor', 'wait'); if (!hgTracks.enableHighlightingDialog) cart.setVarsObj({'enableHighlightingDialog': 0 }); document.TrackHeaderForm.submit(); } $(this).dialog("close"); }, "Highlight": function() { // Highlight selection $(imageV2.imgTbl).imgAreaSelect({hide:true}); if ($("#disableDragHighlight").attr('checked')) hgTracks.enableHighlightingDialog = false; dragSelect.highlightThisRegion(newPosition); $(this).dialog("close"); }, "Cancel": function() { $(this).dialog("close"); } }, + open: function () { // Make zoom the focus/default action $(this).parents('.ui-dialog-buttonpane button:eq(0)').focus(); }, close: function() { // All exits to dialog should go through this $(imageV2.imgTbl).imgAreaSelect({hide:true}); if ($(this).dialog("option", "revertToOriginalPos")) genomePos.revertToOriginalPos(); if ($("#disableDragHighlight").attr('checked')) $(this).remove(); else $(this).hide(); $('body').css('cursor', ''); // Occasionally wait cursor got left behind } @@ -991,31 +1358,32 @@ if (mouseHasMoved === false) { // Update highlight by converting bp back to pix pxDown = convertFromBases(selRange.beg); pxUp = convertFromBases(selRange.end); hiliteShow(pxDown,pxUp); } //if ((selRange.end - selRange.beg) < 50000) // dontAsk = true; if (dontAsk || confirm("Jump to new position:\n\n"+chr.name+":"+commify(selRange.beg)+ "-"+commify(selRange.end)+" size:"+commify(selRange.width)) ) { genomePos.setByCoordinates(chr.name, selRange.beg, selRange.end); // Stop the presses :0) $('area.cytoBand').mousedown( function(e) { return false; }); if (imageV2.backSupport) { imageV2.navigateInPlace("position=" + - encodeURIComponent(genomePos.get().replace(/,/g,'')),null,true); + encodeURIComponent(genomePos.get().replace(/,/g,'')) + + "&findNearest=1",null,true); hiliteCancel(); } else document.TrackHeaderForm.submit(); return true; // Make sure the setTimeout below is not called. } } } hiliteCancel(); setTimeout(posting.allowMapClicks,50); } mouseIsDown = false; mouseHasMoved = false; } function isWithin(beg,here,end) @@ -1337,84 +1705,93 @@ return null; var rowId = $(row).attr('id').substring('tr_'.length); var rec = hgTracks.trackDb[rowId]; if (tdbIsSubtrack(rec) === false) return null; var rows = $('tr.trDraggable:has(p.' + rec.parentTrack+')'); return rows; }, zipButtons: function (table) { // Goes through the image and binds composite track buttons when adjacent var rows = $(table).find('tr'); var lastClass=""; var lastBtn = null; + var lastSide = null; var lastMatchesLast=false; var lastBlue=true; var altColors=false; var count=0; var countN=0; for (var ix=0; ix south) { atEdge = false; beyondImage = false; if (savedPosition) - genomePos.set(savedPosition,null); + genomePos.set(savedPosition); var oldPos = prevX.toString() + "px"; $(".panImg").css( {'left': oldPos }); $('.tdData').css( {'backgroundPosition': oldPos } ); if (highlightArea) imageV2.highlightRegion(); return true; } // Do we need to fetch anything? if (beyondImage) { if (imageV2.inPlaceUpdate) { var pos = parsePosition(genomePos.get()); imageV2.navigateInPlace("position=" + encodeURIComponent(pos.chrom + ":" + pos.start + "-" + pos.end), null, true); @@ -1743,33 +2120,32 @@ Math.round(portalScrolledX*hgTracks.imgBoxBasesPerPixel); else newPortalStart = closedPortalStart - // As offset goes down, bases seen goes up! Math.round(portalScrolledX*hgTracks.imgBoxBasesPerPixel); if (newPortalStart < hgTracks.chromStart && bounded) { // Stay within bounds newPortalStart = hgTracks.chromStart; recalculate = true; } var newPortalEnd = newPortalStart + portalWidthBases; if (newPortalEnd > hgTracks.chromEnd && bounded) { newPortalEnd = hgTracks.chromEnd; newPortalStart = newPortalEnd - portalWidthBases; recalculate = true; } if (newPortalStart > 0) { - var newPos = hgTracks.chromName + ":" + - commify(newPortalStart) + "-" + commify(newPortalEnd); - genomePos.set(newPos, 0); // 0 means no need to change the size + var newPos = hgTracks.chromName + ":" + newPortalStart + "-" + newPortalEnd; + genomePos.set(newPos); // no need to change the size } if (recalculate && hgTracks.imgBoxBasesPerPixel > 0) { // Need to recalculate X for bounding drag portalScrolledX = (closedPortalStart - newPortalStart) / hgTracks.imgBoxBasesPerPixel; newOffsetX = portalScrolledX - (hgTracks.imgBoxPortalOffsetX+hgTracks.imgBoxLeftLabel); } return newOffsetX; } function mapTopAndBottom(mapName,east,west) { // Find the top and bottom px given left and right boundaries var mapPortal = { top: -10, bottom: -10 }; var items = $("map[name='"+mapName+"']").children(); if ($(items).length>0) { $(items).each(function(t) { @@ -2016,34 +2392,35 @@ var id = rightClick.selectedMenuItem.id; var url = null; // TODO: Break this giant routine with shared vars into some sub-functions var href = null; var rec = null; var row = null; var rows = null; var selectUpdated = null; if (menuObject.shown) { // warn("Spinning: menu is still shown"); setTimeout(function() { rightClick.hitFinish(menuItemClicked, menuObject, cmd); }, 10); return; } if (cmd === 'selectWholeGene' || cmd === 'getDna' || cmd === 'highlightItem') { // bring whole gene into view or redirect to DNA screen. href = rightClick.selectedMenuItem.href; - var chromStart, chromEnd; - var a = /hgg_chrom=(\w+)&/.exec(href); + var chrom, chromStart, chromEnd; // Many links leave out the chrom (b/c it's in the server side cart as "c") - var chrom = hgTracks.chromName; + // var chrom = hgTracks.chromName; // This is no longer acceptable + // with multi-window capability drawing multiple positions on multiple chroms. + var a = /hgg_chrom=(\w+)&/.exec(href); if (a) { if (a && a[1]) chrom = a[1]; a = /hgg_start=(\d+)/.exec(href); if (a && a[1]) chromStart = parseInt(a[1]) + 1; a = /hgg_end=(\d+)/.exec(href); if (a && a[1]) chromEnd = parseInt(a[1]); } else { // a = /hgc.*\W+c=(\w+)/.exec(href); a = /hgc.*\W+c=(\w+)/.exec(href); if (a && a[1]) chrom = a[1]; a = /o=(\d+)/.exec(href); @@ -2053,31 +2430,52 @@ if (a && a[1]) chromEnd = parseInt(a[1]); } if (!chrom || chrom.length === 0 || !chromStart || !chromEnd) {// 1-based chromStart warn("couldn't parse out genomic coordinates"); } else { if (cmd === 'getDna') { // NOTE: this should be shared with URL generation for getDna blue bar menu url = "../cgi-bin/hgc?g=getDna&i=mixed&c=" + chrom; url += "&l=" + (chromStart - 1) + "&r=" + chromEnd; url += "&db=" + getDb() + "&hgsid=" + getHgsid(); if ( ! window.open(url) ) { rightClick.windowOpenFailedMsg(); } } else if (cmd === 'highlightItem') { - dragSelect.highlightThisRegion(parseInt(chromStart), parseInt(chromEnd)); + if (hgTracks.windows && !hgTracks.virtualSingleChrom) { + //warn("hitFinish highlightItem chrom="+chrom+" chromStart="+chromStart+" chromEnd="+chromEnd); // DEBUG REMOVE + // orig way only worked if the entire item was visible in the windows. + //var result = genomePos.chromToVirtChrom(chrom, parseInt(chromStart-1), parseInt(chromEnd)); + + var result = genomePos.convertChromPosToVirtCoords(chrom, parseInt(chromStart-1), parseInt(chromEnd)); + + //warn("result.chromStart="+result.chromStart+" result.chromEnd="+result.chromEnd); // DEBUG REMOVE + if (result.chromStart != -1) + { + var newPos2 = hgTracks.chromName+":"+(result.chromStart+1)+"-"+result.chromEnd; + dragSelect.highlightThisRegion(newPos2); + } + + } else { + var newChrom = hgTracks.chromName; + if (hgTracks.windows && hgTracks.virtualSingleChrom) { + newChrom = hgTracks.windows[0].chromName; + } + var newPos3 = newChrom+":"+(parseInt(chromStart))+"-"+parseInt(chromEnd); + dragSelect.highlightThisRegion(newPos3); + } } else { var newPosition = genomePos.setByCoordinates(chrom, chromStart, chromEnd); var reg = new RegExp("hgg_gene=([^&]+)"); var b = reg.exec(href); var name; // pull item name out of the url so we can set hgFind.matches (redmine 3062) if (b && b[1]) { name = b[1]; } else { reg = new RegExp("[&?]i=([^&]+)"); b = reg.exec(href); if (b && b[1]) { name = b[1]; } } @@ -2702,88 +3100,239 @@ if ((maxSize===0) || (winSize < maxSize)) { var url = "hgTracks?hgsid="+getHgsid()+"&hgt.redirectTool="+toolId; var onclick = "$('#extToolDialog').dialog('close');"; htmlLines.push("
  • "+shortLabel+": "+longLabel+"
  • "); } else { note = "
    Needs zoom to < "+maxSize/1000+" kbp."; htmlLines.push('
  • '+shortLabel+": "+longLabel+note); } } htmlLines.push(""); content = htmlLines.join(""); - var title = hgTracks.chromName + ":" + (hgTracks.winStart+1) + "-" + hgTracks.winEnd + " on another website"; + var title = hgTracks.chromName + ":" + (hgTracks.winStart+1) + "-" + hgTracks.winEnd; + if (hgTracks.nonVirtPosition) + title = hgTracks.nonVirtPosition; + title += " on another website"; $("body").append("

    " + content + "

    "); // copied from the hgTrackUi function below var popMaxHeight = ($(window).height() - 40); var popMaxWidth = ($(window).width() - 40); var popWidth = 600; if (popWidth > popMaxWidth) popWidth = popMaxWidth; // also copied from the hgTrackUi code below $('#extToolDialog').dialog({ resizable: true, // Let description scroll vertically height: popMaxHeight, width: popWidth, minHeight: 200, minWidth: 600, maxHeight: popMaxHeight, maxWidth: popMaxWidth, modal: true, closeOnEscape: true, autoOpen: false, buttons: { "Close": function() { $(this).dialog("close"); }}, }); $('#extToolDialog').dialog('open'); } -// a function for the keyboard shortcut: -// Jump to the "Get DNA" page + ///////////////////////////////////////////////////////// + //// popupHgt popup for hgTracks (aka modal dialog) //// +///////////////////////////////////////////////////////// +var popUpHgt = { + + whichHgTracksMethod: "", + title: "", + + cleanup: function () + { // Clean out the popup box on close + if ($('#hgTracksDialog').html().length > 0 ) { + // clear out html after close to prevent problems caused by duplicate html elements + $('#hgTracksDialog').html(""); + popUpHgt.whichHgTracksMethod = ""; + popUpHgt.title = ""; + } + }, + + _uiDialogRequest: function (whichHgTracksMethod) + { // popup cfg dialog + popUpHgt.whichHgTracksMethod = whichHgTracksMethod; + var myLink = "../cgi-bin/hgTracks?hgsid=" + getHgsid() + "&db=" + getDb(); + if (popUpHgt.whichHgTracksMethod === "multi-region config") { + myLink += "&hgTracksConfigMultiRegionPage=multi-region"; + popUpHgt.title = "Configure Multi-Region View"; + } + + $.ajax({ + type: "GET", + url: cart.addUpdatesToUrl(myLink), + dataType: "html", + trueSuccess: popUpHgt.uiDialog, + success: catchErrorOrDispatch, + error: errorHandler, + cache: false + }); + }, + + hgTracks: function (whichHgTracksMethod) + { // Launches the popup but shields the ajax with a waitOnFunction + waitOnFunction( popUpHgt._uiDialogRequest, whichHgTracksMethod); + }, + + uiDialogOk: function (popObj) + { // When hgTracks Cfg popup closes with ok, then update cart and refresh parts of page + + }, + + uiDialog: function (response, status) + { + // Take html from hgTracks and put it up as a modal dialog. + + // make sure all links (e.g. help links) open up in a new window + response = response.replace(/"+ cleanHtml +"
    "); + + // Strategy for popups with js: + // - jsFiles and CSS should not be included in html. Here they are shluped out. + // - The resulting files ought to be loadable dynamically (with getScript()), + // but this was not working nicely with the modal dialog + // Therefore include files must be included with hgTracks CGI ! + // - embedded js should not be in the popup box. + // - Somethings should be in a popup.ready() function, and this is emulated below, + // as soon as the cleanHtml is added + // Since there are many possible popup cfg dialogs, the ready should be all inclusive. + + // -- popup.ready() -- Here is the place to do things that might otherwise go + // into a $('#pop').ready() routine! + + // Searching for some semblance of size suitability + var popMaxHeight = ($(window).height() - 40); + var popMaxWidth = ($(window).width() - 40); + var popWidth = 740; + if (popWidth > popMaxWidth) + popWidth = popMaxWidth; + + $('#hgTracksDialog').dialog({ + ajaxOptions: { + // This doesn't work + cache: true + }, + resizable: true, // Let scroll vertically + height: 'auto', + width: popWidth, + minHeight: 200, + minWidth: 700, + maxHeight: popMaxHeight, + maxWidth: popMaxWidth, + modal: true, + closeOnEscape: true, + autoOpen: false, + buttons: { + /* NOT NOW + "OK": function() { + popUpHgt.uiDialogOk($('#pop')); + $(this).dialog("close"); + } + */ + }, + // popup.ready() doesn't seem to work in open. + + //create: function () { + //$(this).siblings().find(".ui-dialog-title").html('Test '); + //$(this).siblings().find(".ui-dialog-title").html(''); + //}, + + open: function () { + $('#hgTracksDialog').find('.filterBy,.filterComp').each( + function(i) { // ddcl.js is dropdown checklist lib support + if ($(this).hasClass('filterComp')) + ddcl.setup(this); + else + ddcl.setup(this, 'noneIsAll'); + } + ); + }, + + close: function() { + popUpHgt.cleanup(); + } + }); + + + $('#hgTracksDialog').dialog('option' , 'title' , popUpHgt.title); + $('#hgTracksDialog').dialog('open'); + + } +}; + +// A function for the keyboard shortcut: +// View DNA function gotoGetDnaPage() { - var url = "hgc?hgsid="+getHgsid()+"&g=getDna&i=mixed&c="+hgTracks.chromName+"&l="+hgTracks.winStart+"&r="+hgTracks.winEnd+"&db="+getDb(); + var position = hgTracks.chromName+":"+hgTracks.winStart+"-"+hgTracks.winEnd; + if (hgTracks.virtualSingleChrom && (pos.chrom.search("virt") === 0)) { + position = genomePos.get().replace(/,/g,''); + } else if (hgTracks.windows && hgTracks.nonVirtPosition) { + position = hgTracks.nonVirtPosition; + } + var pos = parsePosition(position); + if (pos) { + var url = "hgc?hgsid="+getHgsid()+"&g=getDna&i=mixed&c="+pos.chrom+"&l="+pos.start+"&r="+pos.end+"&db="+getDb(); window.location.href = url; } + return false; +} ////////////////////////////////// //// popup (aka modal dialog) //// ////////////////////////////////// var popUp = { trackName: "", trackDescriptionOnly: false, saveAllVars: null, cleanup: function () { // Clean out the popup box on close if ($('#hgTrackUiDialog').html().length > 0 ) { // clear out html after close to prevent problems caused by duplicate html elements $('#hgTrackUiDialog').html(""); popUp.trackName = ""; //set to defaults popUp.trackDescriptionOnly = false; popUp.saveAllVars = null; } }, - _uiDialigRequest: function (trackName,descriptionOnly) + _uiDialogRequest: function (trackName,descriptionOnly) { // popup cfg dialog popUp.trackName = trackName; var myLink = "../cgi-bin/hgTrackUi?g=" + trackName + "&hgsid=" + getHgsid() + "&db=" + getDb(); popUp.trackDescriptionOnly = descriptionOnly; if (popUp.trackDescriptionOnly) myLink += "&descriptionOnly=1"; var rec = hgTracks.trackDb[trackName]; if (!descriptionOnly && rec && rec.configureBy) { if (rec.configureBy === 'none') return; else if (rec.configureBy === 'clickThrough') { jQuery('body').css('cursor', 'wait'); window.location = cart.addUpdatesToUrl(myLink); @@ -2793,40 +3342,57 @@ myLink += "&ajax=1"; $.ajax({ type: "GET", url: cart.addUpdatesToUrl(myLink), dataType: "html", trueSuccess: popUp.uiDialog, success: catchErrorOrDispatch, error: errorHandler, cmd: rightClick.selectedMenuItem, cache: false }); }, hgTrackUi: function (trackName,descriptionOnly) { // Launches the popup but shields the ajax with a waitOnFunction - waitOnFunction( popUp._uiDialigRequest, trackName, descriptionOnly ); + waitOnFunction( popUp._uiDialogRequest, trackName, descriptionOnly ); }, uiDialogOk: function (popObj, trackName) { // When hgTrackUi Cfg popup closes with ok, then update cart and refresh parts of page var rec = hgTracks.trackDb[trackName]; var subtrack = tdbIsSubtrack(rec) ? trackName : undefined; // subtrack vis rules differ // For unknown reasons IE8 fails to find $('#pop'), occasionally var allVars = getAllVars($('#hgTrackUiDialog'), subtrack ); + // Since 2010, when Tim changed this to only report changed vars instead of all form vars, + // it no longer matches the behavior of hgTrackUi when called the non-popup way. + // A few places in the hgTracks C code have been patched to explicitly set the cart vars + // for some default checkboxes. So now this still means that QA must explicitly test + // both paths through the code: as a separate full hgTracksUi page, and as a popup config window. + // hgTrackUi always sends in its form all variables causing them to be explicitly set in the cart. + // The popup only sends things that have changed, causing those changes to appear explicitly + // and even then it skips over disabled form items. + // There is some C code that was written before the popup config, + // and it expects all the variables are set or none are. + // If just some are set, and not the default ones, it gets confused. + // I fixed just such a bug in the code that handles refSeq. + // See commit daf92c0f9eb331ea60740e6802aabd241d4be363. var changedVars = varHashChanges(allVars,popUp.saveAllVars); + // DEBUG REMOVE + //debugDumpFormCollection("saveAllVars", popUp.saveAllVars); + //debugDumpFormCollection("allVars", allVars); + //debugDumpFormCollection("changedVars", changedVars); var newVis = changedVars[trackName]; // subtracks do not have "hide", thus '[]' var hide = (newVis && (newVis === 'hide' || newVis === '[]')); if ( ! normed($('#imgTbl')) ) { // On findTracks or config page if (objNotEmpty(changedVars)) cart.setVarsObj(changedVars); } else { // On image page if (hide) { if (objNotEmpty(changedVars)) cart.setVarsObj(changedVars); $(document.getElementById('tr_' + trackName)).remove(); imageV2.afterImgChange(true); } else { // Keep local state in sync if user changed visibility @@ -2846,51 +3412,51 @@ }, uiDialog: function (response, status) { // Take html from hgTrackUi and put it up as a modal dialog. // make sure all links (e.g. help links) open up in a new window response = response.replace(/"+ cleanHtml +"
  • "); - // Strategy for poups with js: + // Strategy for popups with js: // - jsFiles and CSS should not be included in html. Here they are shluped out. // - The resulting files ought to be loadable dynamically (with getScript()), // but this was not working nicely with the modal dialog // Therefore include files must be included with hgTracks CGI ! // - embedded js should not be in the popup box. // - Somethings should be in a popup.ready() function, and this is emulated below, // as soon as the cleanHtml is added // Since there are many possible popup cfg dialogs, the ready should be all inclusive. if ( ! popUp.trackDescriptionOnly ) { // If subtrack then vis rules differ var subtrack = tdbIsSubtrack(hgTracks.trackDb[popUp.trackName]) ? popUp.trackName :""; // Saves the original vars (and vals) that may get changed by the popup cfg. popUp.saveAllVars = getAllVars( $('#hgTrackUiDialog'), subtrack ); // -- popup.ready() -- Here is the place to do things that might otherwise go // into a $('#pop').ready() routine! } - // Searching for some selblance of size suitability + // Searching for some semblance of size suitability var popMaxHeight = ($(window).height() - 40); var popMaxWidth = ($(window).width() - 40); var popWidth = 740; if (popWidth > popMaxWidth) popWidth = popMaxWidth; $('#hgTrackUiDialog').dialog({ ajaxOptions: { // This doesn't work cache: true }, resizable: true, // Let description scroll vertically height: (popUp.trackDescriptionOnly ? popMaxHeight : 'auto'), width: popWidth, minHeight: 200, @@ -3000,37 +3566,40 @@ $('.timing').remove(); for (var ix = strs.length; ix > 0; ix--) { $('body').prepend(strs[ix - 1]); } } reg = new RegExp("([\\S\\s]+?)"); a = reg.exec(response); if (a && a[1]) { $('.trackTiming').replaceWith(a[1]); } }, loadSuggestBox: function () { if ($('#positionInput').length) { - suggestBox.init(getDb(), $("#suggestTrack").length > 0, + if (!suggestBox.initialized) { // only call init once + suggestBox.init(getDb(), + $("#suggestTrack").length > 0, function (item) { - genomePos.set(item.id, commify(getSizeFromCoordinates(item.id))); + genomePos.set(item.id, getSizeFromCoordinates(item.id)); }, function (position) { - genomePos.set(position, commify(getSizeFromCoordinates(position))); + genomePos.set(position, getSizeFromCoordinates(position)); }); + } // Make sure suggestTrack is visible when user chooses via gene select (#3484). if ($("#suggestTrack").length) { $(document.TrackForm || document.TrackHeaderForm).submit(function(event) { if ($('#hgFindMatches').length) { vis.makeTrackVisible($("#suggestTrack").val()); } }); } } }, afterImgChange: function (dirty) { // Standard things to do when manipulations change image without ajax update. dragReorder.init(); dragSelect.load(false); @@ -3179,30 +3748,45 @@ var $oldMap = $("map[name='ideoMap']"); var $container = $oldMap.parent(); $oldMap.remove(); $container.append(ideoMapMatch[0]); } if (imageV2.backSupport) { // Reinit chrom dragging. if ($('area.cytoBand').length >= 1) { $('img#chrom').chromDrag(); } } } } }, + updateBackground: function (response) + { + // Added by galt to update window separators + // Parse out background image url + // background-image:url("../trash/hgt/blueLines1563-118-12_hgwdev_galt_9df9_e33b30.png") + + var a = /background-image:url\("(..\/trash\/hgt\/winSeparators[^"]+[.]png)"\)/.exec(response); + if (a && a[1]) { + //warn("updateBackground called! winSepartors"+a[1]); + $('td.tdData').css("background-image", "url("+a[1]+")"); + } + + }, + + requestImgUpdate: function (trackName,extraData,loadingId,newVisibility) { // extraData, loadingId and newVisibility are optional var data = "hgt.trackImgOnly=1&hgsid=" + getHgsid() + "&hgt.trackNameFilter=" + trackName; if (extraData && extraData !== "") data += "&" + extraData; if (!loadingId || loadingId === "") loadingId = showLoadingImage("tr_" + trackName); var getOrPost = "GET"; if ((data.length) > 2000) // extraData could contain a bunch of changes from the cfg dialog getOrPost = "POST"; $.ajax({ type: getOrPost, url: "../cgi-bin/hgTracks", data: cart.addUpdatesToUrl(data), @@ -3227,36 +3811,44 @@ var url = cart.addUpdatesToUrl(window.location.href); if (extraData) { if ( url.lastIndexOf("?") === -1) url += "?" + extraData; else url += '&' + extraData; } window.location.assign(url); return false; } document.TrackHeaderForm.submit(); }, updateImgAndMap: function (response, status) { // Handle ajax response with an updated trackMap image, map and optional ideogram. - // + // and maybe the redLines background too. // this.cmd can be used to figure out which menu item triggered this. // this.id === appropriate track if we are retrieving just a single track. + //warn("updateImgAndMap got here 0"); // DEBUG REMOVE GALT + // update local hgTracks.trackDb to reflect possible side-effects of ajax request. + + //alert(response); //DEBUG REMOVE GALT + var newJson = scrapeVariable(response, "hgTracks"); + + //alert(JSON.stringify(newJson)); // DEBUG REMOVE GALT + var oldJson = hgTracks; var valid = false; if (!newJson) { var stripped = {}; stripJsEmbedded(response, true, stripped); if ( ! stripped.warnMsg ) warn("hgTracks object is missing from the response"); } else { if (this.id) { if (newJson.trackDb[this.id]) { var visibility = vis.enumOrder[newJson.trackDb[this.id].visibility]; var limitedVis; if (newJson.trackDb[this.id].limitedVis) limitedVis = vis.enumOrder[newJson.trackDb[this.id].limitedVis]; if (this.newVisibility && limitedVis && this.newVisibility !== limitedVis) @@ -3267,62 +3859,66 @@ rec.limitedVis = newJson.trackDb[this.id].limitedVis; vis.update(this.id, visibility); valid = true; } else { warn("Invalid hgTracks.trackDb received from the server"); } } else { valid = true; } } if (valid) { if (imageV2.enabled && this.id && this.cmd && this.cmd !== 'wholeImage' - && this.cmd !== 'selectWholeGene') { + && this.cmd !== 'selectWholeGene' + && !newJson.virtChromChanged) { // Extract ... and update appropriate row in imgTbl; // this updates src in img_left_ID, img_center_ID and img_data_ID // and map in map_data_ID var id = this.id; if (imageV2.updateImgForId(response, id, false)) { imageV2.afterReload(id); + imageV2.updateBackground(response); // Added by galt to update window separators } else { warn("Couldn't parse out new image for id: " + id); // Very helpful when debugging and alert doesn't render the html: //alert("Couldn't parse out new image for id: " + id+"BR"+response); } } else { if (imageV2.enabled) { // Implement in-place updating of hgTracks image - genomePos.setByCoordinates(newJson.chromName, newJson. - winStart + 1, newJson.winEnd); + // GALT delaying this until after newJson updated in hgTracks so disguising works + //genomePos.setByCoordinates(newJson.chromName, newJson.winStart + 1, newJson.winEnd); $("input[name='c']").val(newJson.chromName); $("input[name='l']").val(newJson.winStart); $("input[name='r']").val(newJson.winEnd); - if (newJson.cgiVersion !== oldJson.cgiVersion) { + if (newJson.cgiVersion !== oldJson.cgiVersion || newJson.virtChromChanged) { // Must reload whole page because of a new version on the server; // this should happen very rarely. Note that we have already updated // position based on the user's action. imageV2.fullReload(); } else { // Will rebuild image adding new, removing old and resorting tracks imageV2.updateImgForAllIds(response,oldJson,newJson); imageV2.updateChromImg(response); + imageV2.updateBackground(response); // Added by galt to update window separators hgTracks = newJson; genomePos.original = undefined; + genomePos.setByCoordinates(hgTracks.chromName, hgTracks.winStart + 1, hgTracks.winEnd); // MOVED HERE GALT initVars(); imageV2.afterReload(); } } else { warn("ASSERT: Attempt to update track without advanced javascript features."); } } if (hgTracks.measureTiming) { imageV2.updateTiming(response); } } if (this.disabledEle) { this.disabledEle.removeAttr('disabled'); } if (this.loadingId) { @@ -3480,38 +4076,76 @@ data: cart.addUpdatesToUrl(params + "&hgt.trackImgOnly=1&hgt.ideogramToo=1&hgsid=" + getHgsid()), dataType: "html", trueSuccess: imageV2.updateImgAndMap, success: catchErrorOrDispatch, error: errorHandler, cmd: 'wholeImage', loadingId: showLoadingImage("imgTbl"), disabledEle: disabledEle, currentId: currentId, currentIdYOffset: currentIdYOffset, cache: false }); }, + disguiseHighlight: function(position) + // disguise highlight position + { + pos = parsePositionWithDb(position); + // DISGUISE + if (hgTracks.virtualSingleChrom && (pos.chrom.search("virt") === 0)) { + var positionStr = pos.chrom+":"+pos.start+"-"+pos.end; + var newPosition = genomePos.disguisePosition(positionStr); + var newPos = parsePosition(newPosition); + pos.chrom = newPos.chrom; + pos.start = newPos.start; + pos.end = newPos.end; + } + return pos.db+"."+pos.chrom+":"+pos.start+"-"+pos.end+pos.color; + }, + + undisguiseHighlight: function(pos) + // undisguise highlight pos + { + // UN-DISGUISE + //console.warn("undisguiseHighlight: got here 0"); // DEBUG REMOVE + if (hgTracks.virtualSingleChrom && (pos.chrom.search("virt") !== 0)) { + var position = pos.chrom+":"+pos.start+"-"+pos.end; + var newPosition = genomePos.undisguisePosition(position); + //console.warn("undisguiseHighlight: newPosition="+newPosition); // DEBUG REMOVE + var newPos = parsePosition(newPosition); + if (newPos) { + pos.chrom = newPos.chrom; + pos.start = newPos.start; + pos.end = newPos.end; + } + } + }, + highlightRegion: function() // highlight vertical region in imgTbl based on hgTracks.highlight (#709). { var pos; var hexColor = '#FFAAAA'; $('#highlightItem').remove(); if (hgTracks.highlight) { + //console.warn("highlightRegion: hgTracks.highlight="+hgTracks.highlight); // DEBUG REMOVE pos = parsePositionWithDb(hgTracks.highlight); + //console.warn("highlightRegion: pos.chrom="+pos.chrom); // DEBUG REMOVE + // UN-DISGUISE + imageV2.undisguiseHighlight(pos); if (pos) { pos.start--; // make start 0-based to match hgTracks.winStart if (pos.color) hexColor = pos.color; } } if (pos && pos.chrom === hgTracks.chromName && pos.db === getDb() && pos.start <= hgTracks.imgBoxPortalEnd && pos.end >= hgTracks.imgBoxPortalStart) { var portalWidthBases = hgTracks.imgBoxPortalEnd - hgTracks.imgBoxPortalStart; var portal = $('#imgTbl td.tdData')[0]; var leftPixels = $(portal).offset().left + 3; // 3 for borders and cgi item calcs ?? var pixelsPerBase = ($(portal).width() - 2) / portalWidthBases; var clippedStartBases = Math.max(pos.start, hgTracks.imgBoxPortalStart); var clippedEndBases = Math.min(pos.end, hgTracks.imgBoxPortalEnd); var widthPixels = (clippedEndBases - clippedStartBases) * pixelsPerBase; @@ -3530,165 +4164,168 @@ var area = jQuery("
    "); $(area).css({ backgroundColor: hexColor, // display: 'none' left: leftPixels + 'px', top: $('#imgTbl').offset().top + 1 + 'px', width: widthPixels + 'px', height: $('#imgTbl').css('height') }); $(area).data({leftPixels: leftPixels, widthPixels: widthPixels});// needed by dragScroll // Larry originally appended to imgTbl, but discovered that doesn't work on IE 8 and 9. $('body').append($(area)); // z-index is done in css class, so highlight is beneath transparent data images. // NOTE: ideally highlight would be below transparent blue-lines, but THAT is a // background-image so z-index can't get below it! PS/PDF looks better for blue-lines! } }, - backSupport: (window.History.enabled !== undefined), // support of r back button via: + backSupport: (window.History.enabled !== undefined), // support of our back button via: history: null, // jquery.history.js and HTML5 history API setupHistory: function () { // Support for back-button using jquery.history.js. // Sets up the history and initializes a state. // Since ajax updates leave the browser cached pages different from the server state, // simple back-button fails. Using a 'dirty flag' we had forced an update from server, // whenever the back button was hit, meaning there was no going back from server-state! - // NOW using the hitsory API, the back-button triggers a 'statechange' event which can + // NOW using the history API, the back-button triggers a 'statechange' event which can // contain data. We save the position in the data and ajax update the image when the // back-button is pressed. This works great for going back through ajax-updated position // changes, but is a bit messier when going back past a full-page retrieved state (as // described below). // NOTE: many things besides position could be ajax updated (e.g. track visibility). We are // using the back-button to keep track of position only. Since the image should be updated // every-time the back button is pressed, all track settings should persist (not go back). // What will occasionally fail is vis box state and group expansion state. This is because // the back-button goes to a browser cached page and then the image alone is updated. imageV2.history = window.History; // The 'statechange' function triggerd by the back-button. // Whenever the position changes, then use ajax-update to refetch the position imageV2.history.Adapter.bind(window,'statechange',function(){ + var prevDbPos = imageV2.history.getState().data.lastDbPos; var prevPos = imageV2.history.getState().data.position; - var curPos = encodeURIComponent(genomePos.get().replace(/,/g,'')); - if (prevPos && prevPos !== curPos) { - // NOTE: this function is NOT called when backing passed a full retrieval boundary + var curDbPos = hgTracks.lastDbPos; + if (prevDbPos && prevDbPos !== curDbPos) { + // NOTE: this function is NOT called when backing past a full retrieval boundary genomePos.set(decodeURIComponent(prevPos)); - imageV2.navigateInPlace("position=" + prevPos, null, false); + imageV2.navigateInPlace("" + prevDbPos, null, false); } }); - // TODO: move elsewhere? // With history support it is best that most position changes will ajax-update the image // This ensures that the 'go' and 'refresh' button will do so unless the chrom changes. $("input[value='go'],input[value='refresh']").click(function () { var newPos = genomePos.get().replace(/,/g,''); + var newDbPos = hgTracks.lastDbPos; if ( ! imageV2.manyTracks() ) { var newChrom = newPos.split(':')[0]; var oldChrom = genomePos.getOriginalPos().split(':')[0]; if (newChrom === oldChrom) { imageV2.markAsDirtyPage(); - imageV2.navigateInPlace("position="+encodeURIComponent(newPos), null, false); + imageV2.navigateInPlace("position=" + newPos, null, false); window.scrollTo(0,0); return false; } } // If not just image update AND there are vis updates waiting... if (cart.updatesWaiting()) { - var url = "../cgi-bin/hgTracks?" + cart.varsToUrlData({ 'db': getDb(), - 'position': newPos, 'hgsid': getHgsid() }); + var url = "../cgi-bin/hgTracks?position=" + newPos + "&" + cart.varsToUrlData({ 'db': getDb(), 'hgsid': getHgsid() }); window.location.assign(url); return false; } return true; }); // Have vis box changes update cart through ajax. This helps keep page/cart in sync. vis.initForAjax(); // We reach here from these possible paths: // A) Forward: Full page retrieval: hgTracks is first navigated to (or chrom change) // B) Back-button past a full retrieval (B in: ->A,->b,->c(full page),->d,<-c,<-B(again)) // B1) Dirty page: at least one non-position change (e.g. 1 track vis changed in b) // B2) Clean page: only position changes from A->b->| var curPos = encodeURIComponent(genomePos.get().replace(/,/g,'')); + var curDbPos = hgTracks.lastDbPos; var cachedPos = imageV2.history.getState().data.position; + var cachedDbPos = imageV2.history.getState().data.lastDbPos; // A) Forward: Full page retrieval: hgTracks is first navigated to (or chrom change) - if (!cachedPos) { // Not a back-button operation + if (!cachedDbPos) { // Not a back-button operation // set the current position into history outright (will replace). No img update needed imageV2.setInHistory(true); } else { // B) Back-button past a full retrieval genomePos.set(decodeURIComponent(cachedPos)); // B1) Dirty page: at least one non-position change if (imageV2.isDirtyPage()) { imageV2.markAsCleanPage(); // Only forcing a full page refresh if chrom changes var cachedChrom = decodeURIComponent(cachedPos).split(':')[0]; var curChrom = decodeURIComponent( curPos).split(':')[0]; if (cachedChrom === curChrom) { - imageV2.navigateInPlace("db="+getDb()+"&position=" + cachedPos, null, false); + imageV2.navigateInPlace("db="+getDb()+"&"+cachedDbPos, null, false); } else { imageV2.fullReload(); } } else { // B2) Clean page: only position changes from a->b - if (cachedPos !== curPos) { - imageV2.navigateInPlace("db="+getDb()+"&position=" + cachedPos, null, false); + if (cachedDbPos !== curDbPos) { + imageV2.navigateInPlace("db="+getDb()+"&"+cachedDbPos, null, false); } } // Special because FF is leaving vis drop-downs disabled vis.restoreFromBackButton(); } }, setInHistory: function (fullPageLoad) { // Keep a position history and allow the back-button to work (sort of) // replaceState on initial page load, pushState on each advance // When call triggered by back button, the lastPos===newPos, so no action. - var lastPos = imageV2.history.getState().data.position; + var lastDbPos = imageV2.history.getState().data.lastDbPos; var newPos = encodeURIComponent(genomePos.get().replace(/,/g,'')); // no commas + var newDbPos = hgTracks.lastDbPos; // A full page load could be triggered by back-button, but then there will be a lastPos // if this is the case then don't set the position in history again! - if (fullPageLoad && lastPos) + if (fullPageLoad && lastDbPos) return; - if (!lastPos || lastPos !== newPos) { + if (!lastDbPos || lastDbPos !== newDbPos) { // Swap the position into the title var title = $('TITLE')[0].text; var ttlWords = title.split(' '); if (ttlWords.length >= 2) { for (var i=1; i < ttlWords.length; i++) { if (ttlWords[i].indexOf(':') >= 0) { ttlWords[i] = genomePos.get(); } } title = ttlWords.join(' '); } else title = genomePos.get(); - var sid = getHgsid(); // Wish you were here! Come on, someone must catch this. + var sid = getHgsid(); if (fullPageLoad) { // Should only be on initial set-up: first navigation to page - imageV2.history.replaceState({position: newPos, hgsid: + sid },title, - "hgTracks?db=" + getDb() + "&position=" + newPos + "&hgsid="+sid); + imageV2.history.replaceState({lastDbPos: newDbPos, position: newPos, hgsid: + sid },title, + "hgTracks?db="+getDb()+"&"+newDbPos+"&hgsid="+sid); } else { // Should be when advancing (not-back-button) - imageV2.history.pushState({position: newPos, hgsid: + getHgsid()},title, - "hgTracks?db=" + getDb() + "&position=" + newPos + "&hgsid="+sid); + imageV2.history.pushState({lastDbPos: newDbPos, position: newPos, hgsid: + sid },title, + "hgTracks?db="+getDb()+"&"+newDbPos+"&hgsid="+sid); } } } }; ////////////////////// //// track search //// ////////////////////// var trackSearch = { searchKeydown: function (event) { if (event.which === 13) { // Required to fix problem on IE and Safari where value of hgt_tSearch is "-" // (i.e. not "Search").