src/hg/js/hgTracks.js 1.40
1.40 2009/09/15 00:21:53 tdreszer
Make the chrom image draggable. Note click gives new position, same window size. Ctrl- gives cytoBand.
Index: src/hg/js/hgTracks.js
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/js/hgTracks.js,v
retrieving revision 1.39
retrieving revision 1.40
diff -b -B -U 1000000 -r1.39 -r1.40
--- src/hg/js/hgTracks.js 13 Sep 2009 21:05:49 -0000 1.39
+++ src/hg/js/hgTracks.js 15 Sep 2009 00:21:53 -0000 1.40
@@ -1,1062 +1,1339 @@
// Javascript for use in hgTracks CGI
// $Header$
var debug = false;
var originalPosition;
var originalSize;
var originalCursor;
var clickClipHeight;
var startDragZoom = null;
var newWinWidth;
var imageV2 = false;
var imgBoxPortal = false;
var blockUseMap = false;
var mapHtml;
var mapItems;
var trackImg; // jQuery element for the track image
var trackImgTbl; // jQuery element used for image table under imageV2
var imgAreaSelect; // jQuery element used for imgAreaSelect
var originalImgTitle;
var autoHideSetting = true; // Current state of imgAreaSelect autoHide setting
var selectedMapItem; // index of currently choosen map item (via context menu).
var browser; // browser ("msie", "safari" etc.)
function commify (str) {
if(typeof(str) == "number")
str = str + "";
var n = str.length;
if (n <= 3) {
return str;
} else {
var pre = str.substring(0, n-3);
var post = str.substring(n-3);
var pre = commify(pre);
return pre + "," + post;
}
}
function initVars(img)
{
// There are various entry points, so we call initVars in several places to make sure this variables get updated.
if(!originalPosition) {
// remember initial position and size so we can restore it if user cancels
originalPosition = $('#positionHidden').val();
originalSize = $('#size').text();
originalCursor = jQuery('body').css('cursor');
}
}
function selectStart(img, selection)
{
initVars();
var now = new Date();
startDragZoom = now.getTime();
blockUseMap = true;
// vvvvvvv Should be obsolete since maps items are ignored when startDragZoom is set
// if(imageV2 == false) {
// jQuery.each(jQuery.browser, function(i, val) {
// if(i=="msie" && val) {
// // Very hacky way to solve following probem specif to IE:
// // If the user ends selection with the mouse in a map box item, the map item
// // is choosen instead of the selection; to fix this, we remove map box items
// // during the mouse selection process.
// mapHtml = $('#map').html();
// $('#map').empty();
// }
// });
// }
// ^^^^^^^^ Should be obsolete since maps items are ignored when startDragZoom is set
}
function setPositionByCoordinates(chrom, start, end)
{
var newPosition = chrom + ":" + commify(start) + "-" + commify(end);
setPosition(newPosition, commify(end - start + 1));
return newPosition;
}
function setPosition(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.
if(position) {
// XXXX There are multiple tags with name == "position":^(
var tags = document.getElementsByName("position");
for (var i = 0; i < tags.length; i++) {
var ele = tags[i];
ele.value = position;
}
}
if(size) {
$('#size').text(size);
}
}
function checkPosition(img, selection)
{
// return true if user's selection is still w/n the img (including some slop).
var imgWidth = jQuery(img).width();
var imgHeight = jQuery(img).height();
var imgOfs = jQuery(img).offset();
var slop = 10;
return (selection.event.pageX >= (imgOfs.left - slop)) && (selection.event.pageX < (imgOfs.left + imgWidth + slop))
&& (selection.event.pageY >= (imgOfs.top - slop)) && (selection.event.pageY < (imgOfs.top + imgHeight + slop));
}
function updatePosition(img, selection, singleClick)
{
// singleClick is true when the mouse hasn't moved (or has only moved a small amount).
var insideX = parseInt(document.getElementById("hgt.insideX").value);
var revCmplDisp = parseInt(document.getElementById("hgt.revCmplDisp").value) == 0 ? false : true;
var chromName = document.getElementById("hgt.chromName").value;
var winStart = parseInt(document.getElementById("hgt.winStart").value);
var winEnd = parseInt(document.getElementById("hgt.winEnd").value);
if(typeof imgBoxPortalStart != "undefined" && imgBoxPortalStart) {
winStart = imgBoxPortalStart;
winEnd = imgBoxPortalEnd;
}
var imgWidth = jQuery(img).width() - insideX;
var width = winEnd - winStart;
var newPos = null;
var newSize = null;
var mult = width / imgWidth; // mult is bp/pixel multiplier
var startDelta; // startDelta is how many bp's to the right/left
if(revCmplDisp) {
var x1 = Math.min(imgWidth, selection.x1);
startDelta = Math.floor(mult * (imgWidth - x1));
} else {
var x1 = Math.max(insideX, selection.x1);
startDelta = Math.floor(mult * (x1 - insideX));
}
if(singleClick) {
var newStart = (winStart + 1) + (startDelta - Math.floor(newWinWidth / 2));
if(newStart < 1) {
newStart = 1;
newEnd = newWinWidth;
} else {
// hgTracks gracefully handles overflow past the end of the chrom, so don't worry about that.
newEnd = (winStart + 1) + (startDelta + Math.floor(newWinWidth / 2));
}
newPos = chromName + ":" + commify(newStart) + "-" + commify(newEnd);
newSize = newEnd - newStart + 1;
} else {
var endDelta;
if(revCmplDisp) {
endDelta = startDelta;
var x2 = Math.min(imgWidth, selection.x2);
startDelta = Math.floor(mult * (imgWidth - x2));
} else {
var x2 = Math.max(insideX, selection.x2);
endDelta = Math.floor(mult * (x2 - insideX));
}
var newStart = winStart + 1 + startDelta;
var newEnd = winStart + 1 + endDelta;
if(newEnd > winEnd) {
newEnd = winEnd;
}
newPos = chromName + ":" + commify(newStart) + "-" + commify(newEnd);
newSize = newEnd - newStart + 1;
}
if(newPos != null) {
setPosition(newPos, commify(newSize));
return true;
}
}
function selectChange(img, selection)
{
initVars();
updatePosition(img, selection, false);
if(checkPosition(img, selection)) {
jQuery('body').css('cursor', originalCursor);
} else {
jQuery('body').css('cursor', 'not-allowed');
}
return true;
}
function selectEnd(img, selection)
{
var now = new Date();
var doIt = false;
if(originalCursor != null)
jQuery('body').css('cursor', originalCursor);
// ignore releases outside of the image rectangle (allowing a 10 pixel slop)
if(autoHideSetting && checkPosition(img, selection)) {
// ignore single clicks that aren't in the top of the image (this happens b/c the clickClipHeight test in selectStart
// doesn't occur when the user single clicks).
doIt = startDragZoom != null || selection.y1 <= clickClipHeight;
}
if(doIt) {
// startDragZoom is null if mouse has never been moved
if(updatePosition(img, selection, (selection.x2 == selection.x1) || startDragZoom == null || (now.getTime() - startDragZoom) < 100)) {
jQuery('body').css('cursor', 'wait');
document.TrackHeaderForm.submit();
}
} else {
setPosition(originalPosition, originalSize);
originalPosition = originalSize = null;
// if(mapHtml) {
// $('#map').append(mapHtml);
// }
}
// mapHtml = null;
startDragZoom = null;
setTimeout('blockUseMap=false;',50); // Necessary incase the selectEnd was over a map item. select takes precedence.
return true;
}
$(window).load(function () {
jQuery.each(jQuery.browser, function(i, val) {
if(val) {
browser = i;
}
});
// jQuery load function with stuff to support drag selection in track img
loadImgAreaSelect(true);
// Don't load contextMenu if jquery.contextmenu.js hasn't been loaded
if(trackImg && jQuery.fn.contextMenu) {
if(imageV2) {
$("map[name!=ideoMap]").each( function(t) { parseMap($(this,false));});
} else {
// XXXX still under debate whether we have to remove the map
parseMap($('#map'),true);
mapHtml = $('#map').html();
$('#map').empty();
}
originalImgTitle = trackImg.attr("title");
if(imageV2) {
loadContextMenu(trackImgTbl);
$(".trDraggable,.nodrop").each( function(t) { loadContextMenu($(this)); });
} else {
loadContextMenu(trackImg);
trackImg.mousemove(
function (e) {
mapEvent(e);
}
);
trackImg.mousedown(
function (e) {
mapMouseDown(e);
}
);
}
}
});
function loadImgAreaSelect(firstTime)
{
var rulerEle = document.getElementById("hgt.rulerClickHeight");
var dragSelectionEle = document.getElementById("hgt.dragSelection");
// disable if ruler is not visible.
if((dragSelectionEle != null) && (dragSelectionEle.value == '1') && (rulerEle != null)) {
var imgHeight = 0;
trackImg = $('#img_data_ruler');
if(trackImg == undefined || trackImg.length == 0) { // Revert to old imageV1
trackImg = $('#trackMap');
imgHeight = jQuery(trackImg).height();
} else {
imageV2 = true;
trackImgTbl = $('#imgTbl');
imgHeight = trackImg.height();
// XXXX Tim, I think we should get height from trackImgTbl, b/c it automatically adjusts as we add/delete items.
imgHeight = trackImgTbl.height();
}
clickClipHeight = parseInt(rulerEle.value);
newWinWidth = parseInt(document.getElementById("hgt.newWinWidth").value);
imgAreaSelect = jQuery((trackImgTbl || trackImg).imgAreaSelect({ selectionColor: 'blue', outerColor: '',
minHeight: imgHeight, maxHeight: imgHeight,
onSelectStart: selectStart, onSelectChange: selectChange, onSelectEnd: selectEnd,
autoHide: autoHideSetting, movable: false,
clickClipHeight: clickClipHeight}));
}
}
function toggleTrackGroupVisibility(button, prefix)
{
// toggle visibility of a track group; prefix is the prefix of all the id's of tr's in the
// relevant group. This code also modifies the corresponding hidden fields and the gif of the +/- img tag.
var retval = true;
var hidden = $("input[name='hgtgroup_"+prefix+"_close']");
var newVal=1; // we're going - => +
if($(button) != undefined && $(hidden) != undefined && $(hidden).length > 0) {
var oldSrc = $(button).attr("src");
if(arguments.length > 2)
newVal = arguments[2] ? 0 : 1;
else
newVal = oldSrc.indexOf("/remove") > 0 ? 1 : 0;
var newSrc;
if(newVal == 1) {
newSrc = oldSrc.replace("/remove", "/add");
$("tr[id^='"+prefix+"-']").hide();
} else {
newSrc = oldSrc.replace("/add", "/remove");
$("tr[id^='"+prefix+"-']").show();
}
$(button).attr("src",newSrc);
$(hidden).val(newVal);
// setCartVar("hgtgroup_" + prefix + "_close", newVal);
retval = false;
}
return retval;
}
function setAllTrackGroupVisibility(newState)
{
// Set visibility of all track groups to newState (true means expanded).
// This code also modifies the corresponding hidden fields and the gif's of the +/- img tag.
$("img[id$='_button']").each( function (i) {
if(this.src.indexOf("/remove") > 0 || this.src.indexOf("/add") > 0)
toggleTrackGroupVisibility(this,this.id.substring(0,this.id.length - 7),newState); // clip '_button' suffix
});
return false;
}
function imgTblSetOrder(table)
{
// Sets the 'order' value for the image table after a drag reorder
$("input[name$='_imgOrd']").each(function (i) {
var tr = $(this).parents('tr');
if($(this).val() != $(tr).attr('rowIndex')) {
//alert('Reordered '+$(this).val() + " to "+$(tr).attr('rowIndex'));
$(this).val($(tr).attr('rowIndex'));
}
});
}
/////////////////////////////////////////////////////
+jQuery.fn.chromIdeo = function(){
+this.each(function(){
+ // Plan:
+ // mouseDown: determine where in map: convert to img location: pxDown
+ // mouseMove: flag drag
+ // mouseUp: if no drag, then create href centered on bpDown loc with current span
+ // if drag, then create href from bpDown to bpUp
+ // if ctrlKey then expand selection to containing cytoBand(s)
+ var pxTop = -1; // Top of cytobands
+ var pxBottom = -1; // Bottom of cytobands
+ var pxLeft = -1; // Beginning location of first cytoBand
+ var pxRight = -1; // End location of last cytoband
+ var pxImg = { top: -1, height: -1, left: -1, width: -1 }; // Image dimensions
+ var chrDef = { name: "", beg: -1, end: -1 }; // Dimenaions of chrom in bases
+ var pxDown = 0; // pix X location of mouseDown
+ var chrImg = $(this);
+ var mouseIsDown = false;
+ var mouseHasMoved = false;
+ var hilite = jQuery('<div></div>');
+
+ initialize();
+
+ function initialize(){
+
+ findDimensions();
+
+ if(pxTop == -1)
+ alert("chromIdeo(): failed to register "+this.id);
+ else {
+ hiliteSetup();
+
+ $('.cytoBand').mousedown( function(e) {
+ updateImgOffsets();
+ pxDown = e.clientX - pxImg.left;
+ var pxY = e.clientY - pxImg.top;
+ if(mouseIsDown == false
+ && pxDown >= pxLeft
+ && pxDown <= pxRight
+ && pxY >= pxTop
+ && pxY <= pxBottom) {
+ mouseIsDown = true;
+ mouseHasMoved = false;
+
+ $(document).bind('mousemove',chromMove);
+ $(document).bind( 'mouseup', chromUp);
+ hiliteShow(pxDown,pxDown);
+ return false;
+ }
+ //else alert("out fo range pxY:"+pxY+" = (e.clientY:"+e.clientY+" - pxImg.top:"+pxImg.top+") body.scrollTop():"+$("body").scrollTop()+" range:"+pxTop+"-"+pxBottom);
+ });
+ }
+ }
+
+ function chromMove(e) {
+ if ( mouseIsDown ) {
+ var pxX = e.clientX - pxImg.left;
+ //if(pxX >= -10 && pxX <= pxImg.width) {
+ var relativeX = (pxX - pxDown);
+ if(mouseHasMoved || (mouseHasMoved == false && Math.abs(relativeX) > 2)) {
+ mouseHasMoved = true;
+ if(pxX >= pxLeft && pxX <= pxRight)
+ hiliteShow(pxDown,pxX);
+ else if(pxX < pxLeft)
+ hiliteShow(pxDown,pxLeft);
+ else
+ hiliteShow(pxDown,pxRight);
+ }
+ //}
+ }
+ }
+ function chromUp(e) { // Must be a separate function instead of pan.mouseup event.
+ chromMove(e); // Just in case
+ if(mouseIsDown) {
+ updateImgOffsets();
+ var pxUp = e.clientX - pxImg.left;
+ var pxY = e.clientY - pxImg.top;
+ //alert("chromIdeo("+chrDef.name+") selected range (pix):"+pxDown+"-"+pxUp+" chrom range (pix):"+pxLeft+"-"+pxRight+" chrom range (bp):"+chrDef.name+":"+chrDef.beg+"-"+chrDef.end);
+ if(e.ctrlKey) {
+ var band = findCytoBand(pxDown,pxUp);
+ if(band.left > -1 && band.right > -1) {
+ pxDown = band.left;
+ pxUp = band.right;
+ mouseHasMoved = true;
+ hiliteShow(pxDown,pxUp);
+ }
+ }
+ if(pxY >= 0 && pxY <= pxImg.height) { // within vertical range or else cancel
+ var selRange = { beg: -1, end: -1, width: -1 };
+ var dontAsk = true;
+
+ if(mouseHasMoved) {
+ // bounded by chrom dimensions: but must remain within image!
+ if( pxUp < pxLeft && pxUp >= -20 )
+ pxUp = pxLeft;
+ if( pxUp > pxRight && pxUp <= pxRight + 30)
+ pxUp = pxRight;
+
+ if( pxLeft <= pxUp && pxUp <= pxRight ) {// Within horizontal (chrom) range
+
+ var offset = (pxDown - pxLeft)/(pxRight - pxLeft);
+ selRange.beg = Math.round(offset * (chrDef.end - chrDef.beg));
+ offset = (pxUp - pxLeft)/(pxRight - pxLeft);
+ selRange.end = Math.round(offset * (chrDef.end - chrDef.beg));
+
+ if(Math.abs(selRange.end - selRange.beg) < 20)
+ mouseHasMoved = false; // Drag so small: treat as simple click
+ else
+ dontAsk = false;
+ }
+ //else alert("chromIdeo("+chrDef.name+") NOT WITHIN HORIZONTAL RANGE\n selected range (pix):"+pxDown+"-"+pxUp+" chrom range (pix):"+pxLeft+"-"+pxRight);
+ }
+ if(mouseHasMoved == false) { // Not else because small drag turns this off
+
+ hiliteShow(pxUp,pxUp);
+ var curBeg = parseInt($("#hgt\\.winStart").val()); // Note the escaped '.'
+ var curEnd = parseInt($("#hgt\\.winEnd").val());
+ var curWidth = curEnd - curBeg;
+ var offset = (pxUp - pxLeft)/(pxRight - pxLeft);
+ selRange.beg = Math.round(offset * (chrDef.end - chrDef.beg)) - Math.round(curWidth/2); // Notice that beg is based upon up position
+ selRange.end = selRange.beg + curWidth;
+ }
+ if(selRange.end > -1) {
+ // prompt, then submit for new position
+ selRange = rangeNormaizeToChrom(selRange,chrDef);
+ if(mouseHasMoved == false) { // Update highlight by converting bp back to pix
+ var offset = selRange.beg/chrDef.end;
+ pxDown = Math.round(offset * (pxRight - pxLeft)) + pxLeft;
+ offset = selRange.end/chrDef.end;
+ pxUp = Math.round(offset * (pxRight - pxLeft)) + pxLeft;
+ hiliteShow(pxDown,pxUp);
+ }
+ if(dontAsk || confirm("Jump to new position:\n\n"+chrDef.name+":"+commify(selRange.beg)+"-"+commify(selRange.end)+" size:"+commify(selRange.width))) {
+ setPositionByCoordinates(chrDef.name, selRange.beg, selRange.end)
+ document.TrackHeaderForm.submit();
+ }
+ }
+ }
+ //else alert("chromIdeo("+chrDef.name+") NOT WITHIN VERTICAL RANGE\n selected range (pix):"+pxDown+"-"+pxUp+" chrom range (pix):"+pxLeft+"-"+pxRight+"\n cytoTop-Bottom:"+pxTop +"-"+pxBottom);
+ hiliteCancel();
+ $(document).unbind('mousemove',chromMove);
+ $(document).unbind('mouseup',chromUp);
+ setTimeout('blockUseMap=false;',50);
+ }
+ mouseIsDown = false;
+ mouseHasMoved = false;
+ }
+
+ function findDimensions()
+ {
+ $('.cytoBand').each(function(t) {
+ var loc = this.coords.split(",");
+ if(loc.length == 4) {
+ if( pxTop == -1) {
+ pxLeft = parseInt(loc[0]);
+ pxRight = parseInt(loc[2]);
+ pxTop = parseInt(loc[1]);
+ pxBottom = parseInt(loc[3]);
+ } else {
+ if( pxLeft > parseInt(loc[0]))
+ pxLeft = parseInt(loc[0]);
+ if( pxRight < parseInt(loc[2]))
+ pxRight = parseInt(loc[2]);
+ }
+
+ var range = this.title.substr(this.title.lastIndexOf(':')+1)
+ var pos = range.split('-');
+ if(pos.length == 2) {
+ if( chrDef.name.length == 0) {
+ chrDef.beg = parseInt(pos[0]);
+ chrDef.end = parseInt(pos[1]);
+ chrDef.name = this.title.substring(this.title.lastIndexOf(' ')+1,this.title.lastIndexOf(':'))
+ } else {
+ if( chrDef.beg > parseInt(pos[0]))
+ chrDef.beg = parseInt(pos[0]);
+ if( chrDef.end < parseInt(pos[1]))
+ chrDef.end = parseInt(pos[1]);
+ }
+ }
+ $(this).css( 'cursor', 'text');
+ $(this).attr("href","");
+ }
+ });
+ }
+
+ function findCytoBand(pxDown,pxUp) {
+ var cyto = { left: -1, right: -1 };
+ $('.cytoBand').each(function(t) {
+ var loc = this.coords.split(",");
+ if(loc.length == 4) {
+ if(cyto.left == -1 || cyto.left > parseInt(loc[0])) {
+ if((parseInt(loc[0]) <= pxDown && pxDown < parseInt(loc[2]))
+ || (parseInt(loc[0]) <= pxUp && pxUp < parseInt(loc[2])))
+ cyto.left = parseInt(loc[0]);
+ }
+ if(cyto.right == -1 || cyto.right < parseInt(loc[2])) {
+ if((parseInt(loc[0]) <= pxDown && pxDown < parseInt(loc[2]))
+ || (parseInt(loc[0]) <= pxUp && pxUp < parseInt(loc[2])))
+ cyto.right = parseInt(loc[2]);
+ }
+ }
+ });
+ return cyto;
+ }
+ function rangeNormaizeToChrom(selection,chrom)
+ {
+ if(selection.end < selection.beg) {
+ var tmp = selection.end;
+ selection.end = selection.beg;
+ selection.beg = tmp;
+ }
+ selection.width = (selection.end - selection.beg);
+ selection.beg += 1;
+ if( selection.beg < chrom.beg) {
+ selection.beg = chrom.beg;
+ selection.end = chrom.beg + selection.width;
+ }
+ if( selection.end > chrom.end) {
+ selection.end = chrom.end;
+ selection.beg = chrom.end - selection.width;
+ if( selection.beg < chrom.beg) { // spans whole chrom
+ selection.width = (selection.end - chrom.beg);
+ selection.beg = chrom.beg + 1;
+ }
+ }
+ return selection;
+ }
+
+ function hiliteShow(down,cur)
+ {
+ var topY = pxImg.top;
+ var high = pxImg.height;
+ var begX = -1;
+ var wide = -1;
+ if(cur < down) {
+ begX = cur + pxImg.left;
+ wide = (down - cur);
+ } else {
+ begX = down + pxImg.left;
+ wide = (cur - down);
+ }
+ $(hilite).css({ left: begX + 'px', width: wide + 'px', top: topY + 'px', height: high + 'px', display:'' });
+ $(hilite).show();
+ }
+ function hiliteCancel(left,width,top,height)
+ {
+ $(hilite).hide();
+ $(hilite).css({ left: '0px', width: '0px', top: '0px', height: '0px' });
+ }
+
+ function hiliteSetup()
+ {
+ $(hilite).css({ backgroundColor: 'green', opacity: 0.4, borderStyle: 'solid', borderWidth: '1px', bordercolor: '#0000FF' });
+ $p = $(chrImg);
+
+ $(hilite).css({ display: 'none', position: 'absolute', overflow: 'hidden', zIndex: 1 });
+ jQuery($(chrImg).parents('body')).append($(hilite));
+ return hilite;
+ }
+
+ function updateImgOffsets()
+ {
+ var offs = $(chrImg).offset();
+ pxImg.top = Math.round(offs.top - $("body").scrollTop() );
+ pxImg.left = Math.round(offs.left - $("body").scrollLeft() );
+ pxImg.height = parseInt($(chrImg).css("height"));
+ pxImg.width = parseInt($(chrImg).css("height"));
+ return pxImg;
+ }
+});
+}
+/////////////////////////////////////////////////////
jQuery.fn.panImages = function(imgOffset,imgBoxLeftOffset){
this.each(function(){
var pic;
var pan;
if ( $(this).is("img") ) {
pan = $(this).parent("div");
pic = $(this);
}
else if ( $(this).is("div.scroller") ) {
pan = $(this);
pic = $(this).children("img#panImg"); // Get the real pic
}
if(pan == undefined || pic == undefined) {
throw "Not a div with a child image! 'panImages' can only be used with divs contain images.";
}
var leftLimit = imgBoxLeftOffset*-1; // This hides the leftLabel if the image contains it
var prevX = imgOffset*-1;
var newX = 0;
var mouseDownX = 0;
var mouseIsDown = false;
var portalWidth = $(pan).width();
//var ie=( $.browser.msie == true );
initialize();
function initialize(){
pan.css( 'cursor', 'w-resize');//'move');
panAdjustHeight(prevX);
pan.mousedown(function(e){
if(mouseIsDown == false) {
mouseIsDown = true;
mouseDownX = e.clientX;
//if(ie) { // Doesn't work (yet?)
// pic.ondrag='panner';
// pic.ondragend='panMouseUp';
//} else {
$(document).bind('mousemove',panner);
$(document).bind( 'mouseup', panMouseUp); // Will exec only once
//}
return false;
}
});
}
function panner(e) {
//if(!e) e = window.event;
if ( mouseIsDown ) {
var relativeX = (e.clientX - mouseDownX);
// Would be good to:
// 1) Slow scroll near edge
// 2) Allow scrolling past edge
// 3) ajax to new image when scrolled off edge
if(relativeX != 0) {
blockUseMap = true;
// Remeber that offsetX (prevX) is negative
if ( (prevX + relativeX) >= leftLimit ) { // scrolled all the way to the left
newX = leftLimit;
// get new image?
} else if ( (prevX + relativeX) < (imgBoxPortalWidth - imgBoxWidth + leftLimit) ) { // scrolled all the way to the right
newX = (imgBoxPortalWidth - imgBoxWidth + leftLimit);
// get new image?
} else
newX = prevX + relativeX;
$(".panImg").css( {'left': newX.toString() + "px" });
// Now is the time to get left-right, then march through data images to trim horizontal
panUpdatePosition(newX);
}
}
}
function panMouseUp(e) { // Must be a separate function instead of pan.mouseup event.
//if(!e) e = window.event;
if(mouseIsDown) {
if(prevX != newX)
panAdjustHeight(newX);
//if(prevX != newX) {
prevX = newX;
//if(ie) {
// pic.ondrag=''; pic.ondragend='';
//} else {
$(document).unbind('mousemove',panner);
$(document).unbind('mouseup',panMouseUp);
//}
mouseIsDown = false;
setTimeout('blockUseMap=false;',50); // Necessary incase the selectEnd was over a map item. select takes precedence.
//}
}
}
function panUpdatePosition(newOffsetX)
{
// Updates the 'position/search" display with change due to panning
var portalWidthBases = imgBoxPortalEnd - imgBoxPortalStart;
var portalScrolledX = (imgBoxPortalOffsetX+imgBoxLeftLabel) + newOffsetX;
var newPortalStart = imgBoxPortalStart - Math.round(portalScrolledX*imgBoxBasesPerPixel); // As offset goes down, bases seen goes up!
if( newPortalStart < imgBoxChromStart) // Stay within bounds
newPortalStart = imgBoxChromStart;
var newPortalEnd = newPortalStart + portalWidthBases;
if( newPortalEnd > imgBoxChromEnd) {
newPortalEnd = imgBoxChromEnd;
newPortalStart = newPortalEnd - portalWidthBases;
}
if(newPortalStart > 0) {
// XXXX ? imgBoxPortalStart = newPortalStart;
// XXXX ? imgBoxPortalEnd = newPortalEnd;
var newPos = document.getElementById("hgt.chromName").value + ":" + commify(newPortalStart) + "-" + commify(newPortalEnd);
setPosition(newPos, (newPortalEnd - newPortalStart + 1));
}
return true;
}
function mapTopAndBottom(mapName,left,right)
{
// Find the top and bottom px given left and right boundaries
var span = { top: -10, bottom: -10 };
var items = $("map[name='"+mapName+"']").children();
if($(items).length>0) {
$(items).each(function(t) {
var loc = this.coords.split(",");
var aleft = parseInt(loc[0]);
var aright = parseInt(loc[2]);
if(aleft < right && aright >= left) {
var atop = parseInt(loc[1]);
var abottom = parseInt(loc[3]);
if( span.top < 0 ) {
span.top = atop;
span.bottom = abottom;
} else if(span.top > atop) {
span.top = atop;
} else if(span.bottom < abottom) {
span.bottom = abottom;
}
}
});
}
return span;
}
function panAdjustHeight(newOffsetX) {
// Adjust the height of the track data images so that bed items scrolled off screen
// do not waste vertical real estate
var left = newOffsetX * -1;
var right = left + portalWidth;
$(".panImg").each(function(t) {
var mapid = "map_" + this.id.substring(4);
var hDiv = $(this).parent();
var top = parseInt($(this).css("top")) * -1;
var bottom = top + $(hDiv).height();
var span = mapTopAndBottom(mapid,left,right);
if(span.top > 0) {
var topdif = Math.abs(span.top - top);
var botdif = Math.abs(span.bottom - bottom);
if(topdif > 2 || botdif > 2) {
$(hDiv).height( span.bottom - span.top );
top = span.top * -1;
$(this).css( {'top': top.toString() + "px" });
// Need to adjust side label height as well!
var imgId = this.id.split("_");
var titlePx = 0;
var center = $("#img_center_"+imgId[2]);
if(center.length > 0)
titlePx = $(center).parent().height();
var side = $("#img_side_"+imgId[2]);
if( side.length > 0) {
$(side).parent().height( span.bottom - span.top + titlePx);
top += titlePx;
$(side).css( {'top': top.toString() + "px" });
}
}
}
});
}
});
};
/////////////////////////////////////////////////////
function blockTheMap(e)
{
blockUseMap=true;
}
$(document).ready(function()
{
// Convert map AREA gets to post the form, ensuring that cart variables are kept up to date
if($("FORM").length > 0) {
$('a,area').not("[href*='#']").not("[target]").click(function(i) {
if(blockUseMap==true) {
return false;
}
var thisForm=$(this).parents('form');
if(thisForm == undefined || $(thisForm).length == 0)
thisForm=$("FORM");
if($(thisForm).length > 1)
thisForm=$(thisForm)[0];
if(thisForm != undefined && $(thisForm).length == 1) {
//alert("posting form:"+$(thisForm).attr('name'));
return postTheForm($(thisForm).attr('name'),this.href);
}
return true;
});
}
if($('#imgTbl').length == 1) {
imageV2 = true;
// Make imgTbl allow draw reorder of imgTrack rows
if($(".tableWithDragAndDrop").length > 0) {
$(".tableWithDragAndDrop").tableDnD({
onDragClass: "trDrag",
dragHandle: "dragHandle",
onDragStart: function(table, row) {
$(document).bind('mousemove',blockTheMap);
},
onDrop: function(table, row) {
if(imgTblSetOrder) { imgTblSetOrder(table); }
$(document).unbind('mousemove',blockTheMap);
setTimeout('blockUseMap=false;',50); // Necessary incase the selectEnd was over a map item. select takes precedence.
}
});
}
if(imgBoxPortal) {
//alert("imgBox("+imgBoxChromStart+"-"+imgBoxChromEnd+","+imgBoxWidth+") bases/pix:"+imgBoxBasesPerPixel+"\nportal("+imgBoxPortalStart+"-"+imgBoxPortalEnd+","+imgBoxPortalWidth+") offset:"+imgBoxPortalOffsetX);
// Turn on drag scrolling.
$("div.scroller").panImages(imgBoxPortalOffsetX,imgBoxLeftLabel);
}
// Temporary warning while new imageV2 code is being worked through
if($('#map').children("AREA").length > 0) {
alert('Using imageV2, but old map is not empty!');
}
}
+ if($('img#chrom').length == 1) {
+ if($('.cytoBand').length > 1) {
+ $('img#chrom').chromIdeo();
+ }
+ }
+
});
function rulerModeToggle (ele)
{
autoHideSetting = !ele.checked;
var obj = imgAreaSelect.data('imgAreaSelect');
obj.setOptions({autoHide : autoHideSetting});
}
function findMapItem(e)
{
// Find mapItem for given event
var x,y;
if(imageV2) {
// It IS appropriate to use coordinates relative to the img WHEN we have a hit in the right-hand side, but NOT
// when we have a hit in the left hand elements (which do not have relative coordinates).
// XXXX still trying to figure this out.
var pos = $(e.target).position();
if(e.target.tagName == "IMG") {
// alert("img: x: " + x + ", y:" + y);
// alert("pageX: " + e.pageX + "; offsetLeft: " + pos.left);
x = e.pageX - pos.left;
y = e.pageY - pos.top;
// alert("x: " + x + "; y: " + y);
} else {
x = e.pageX - trackImg.attr("offsetLeft");
y = e.pageY - trackImg.attr("offsetTop");
}
// console.log(trackImg.attr("offsetLeft"), trackImg.attr("offsetTop"));
// console.log("findMapItem:", x, y);
// console.dir(mapItems);
} else {
x = e.pageX - e.target.offsetLeft;
y = e.pageY - e.target.offsetTop;
}
var retval = -1;
for(var i=0;i<mapItems.length;i++)
{
if(mapItems[i].obj && e.target === mapItems[i].obj) {
// This never occurs under IE
// console.log("Found match by objects comparison");
retval = i;
break;
} else if (!imageV2 || browser == "msie") {
//
// We start falling through to here under safari under imageV2 once something has been modified
if(mapItems[i].r.contains(x, y)) {
retval = i;
break;
}
}
}
// showWarning(x + " " + y + " " + retval + " " + $(e.target).attr('src'));
// console.log("findMapItem:", e.clientX, e.clientY, x, y, pos.left, pos.top, retval, mapItems.length, e.target.tagName);
// console.log(e.clientX, pos);
return retval;
}
function mapEvent(e)
{
var i = findMapItem(e);
if(i >= 0)
{
e.target.title = mapItems[i].title;
} else {
// XXXX this doesn't work.
// $('#myMenu').html("<ul id='myMenu' class='contextMenu'><li class='edit'><a href='#img'>Get Image</a></li></ul>");
e.target.title = originalImgTitle;
}
}
function mapMouseDown(e)
{
// XXXX Is rightclick logic necessary?
var rightclick = e.which ? (e.which == 3) : (e.button == 2);
if(rightclick)
{
return false;
} else {
var i = findMapItem(e);
if(i >= 0)
{
// XXXX Why does href get changed to "about://" on IE?
window.location = mapItems[i].href;
}
return true;
}
}
function contextMenuHit(menuItemClicked, menuObject, cmd)
{
setTimeout(function() { contextMenuHitFinish(menuItemClicked, menuObject, cmd); }, 1);
}
function contextMenuHitFinish(menuItemClicked, menuObject, cmd)
{
// dispatcher for context menu hits
if(menuObject.shown) {
// showWarning("Spinning: menu is still shown");
setTimeout(function() { contextMenuHitFinish(menuItemClicked, menuObject, cmd); }, 10);
return;
}
if(cmd == 'selectWholeGene') {
// bring whole gene into view
var href = mapItems[selectedMapItem].href;
var chromStart, chromEnd;
var a = /hgg_chrom=(\w+)&/.exec(href);
// Many links leave out the chrom (b/c it's in the server side cart as "c")
var chrom = document.getElementById("hgt.chromName").value;
if(a) {
if(a && a[1])
chrom = a[1];
a = /hgg_start=(\d+)/.exec(href);
if(a && a[1])
// XXXX does chromStart have to be incremented by 1?
chromStart = a[1];
a = /hgg_end=(\d+)/.exec(href);
if(a && a[1])
chromEnd = 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);
if(a && a[1])
chromStart = parseInt(a[1]) + 1;
a = /t=(\d+)/.exec(href);
if(a && a[1])
chromEnd = parseInt(a[1]);
}
if(chrom == null || chromStart == null || chromEnd == null) {
showWarning("couldn't parse out genomic coordinates");
} else {
var newPosition = setPositionByCoordinates(chrom, chromStart, chromEnd);
if(browser == "safari" || imageV2) {
// See comments below on safari.
// We need to parse out more stuff to support imageV2 via ajax, but it's probably possible.
jQuery('body').css('cursor', 'wait');
document.TrackHeaderForm.submit();
} else {
jQuery('body').css('cursor', '');
$.ajax({
type: "GET",
url: "../cgi-bin/hgTracks",
data: "hgt.trackImgOnly=1&hgt.ideogramToo=1&position=" + newPosition + "&hgsid=" + getHgsid(),
dataType: "html",
trueSuccess: handleUpdateTrackMap,
success: catchErrorOrDispatch,
cmd: cmd,
cache: false
});
}
}
} else if (cmd == 'hgTrackUi') {
// data: ?
jQuery('body').css('cursor', 'wait');
$.ajax({
type: "POST",
url: "../cgi-bin/hgTrackUi?ajax=1&g=" + mapItems[selectedMapItem].id + "&hgsid=" + getHgsid(),
dataType: "html",
trueSuccess: handleTrackUi,
success: catchErrorOrDispatch,
cache: true
});
} else if (cmd == 'dragZoomMode') {
autoHideSetting = true;
var obj = imgAreaSelect.data('imgAreaSelect');
obj.setOptions({autoHide : true, movable: false});
} else if (cmd == 'hilightMode') {
autoHideSetting = false;
var obj = imgAreaSelect.data('imgAreaSelect');
obj.setOptions({autoHide : false, movable: true});
} else if (cmd == 'viewImg') {
window.open(trackImg.attr('src'));
} else if (cmd == 'openLink') {
// XXXX This is blocked by Safari's popup blocker (without any warning message).
window.open(mapItems[selectedMapItem].href);
} else {
var id = mapItems[selectedMapItem].id;
var rec = trackDbJson[id];
if(rec && rec.parentTrack) {
// currently we fall back to the parentTrack
id = rec.parentTrack;
}
$("select[name=" + id + "]").each(function(t) {
$(this).val(cmd);
});
if(imageV2 && cmd == 'hide')
{
// Tell remote cart what happened (to keep them in sync with us).
setCartVar(id, cmd);
$('#tr_' + id).remove();
loadImgAreaSelect(false);
} else if (browser == "safari") {
// Safari has the following bug: if we update the local map dynamically, the changes don't get registered (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.
jQuery('body').css('cursor', 'wait');
document.TrackForm.submit();
} else {
var data = "hgt.trackImgOnly=1&" + id + "=" + cmd + "&hgsid=" + getHgsid();
if(imageV2) {
data += "&hgt.trackNameFilter=" + id;
}
jQuery('body').css('cursor', 'wait');
$.ajax({
type: "GET",
url: "../cgi-bin/hgTracks",
data: data,
dataType: "html",
trueSuccess: handleUpdateTrackMap,
success: catchErrorOrDispatch,
cmd: cmd,
cache: false
});
}
}
}
function loadContextMenu(img)
{
var menu = img.contextMenu(
function() {
var menu = [];
var selectedImg = " <img src='../images/Green_check.png' height='10' width='10' />";
var done = false;
if(selectedMapItem >= 0)
{
var href = mapItems[selectedMapItem].href;
var isGene = href.match("hgGene");
var isHgc = href.match("hgc");
var rec = trackDbJson[id];
var id = mapItems[selectedMapItem].id;
var rec = trackDbJson[id];
if(rec && rec.parentTrack) {
// currently we fall back to the parentTrack
id = rec.parentTrack;
}
// XXXX what if select is not available (b/c trackControlsOnMain is off)?
// Move functionality to a hidden variable?
var select = $("select[name=" + id + "]");
var cur = select.val();
if(cur) {
select.children().each(function(index, o) {
var title = $(this).val();
var str = title;
if(title == cur) {
str += selectedImg;
}
var o = new Object();
o[str] = {onclick: function (menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, title); return true;}};
menu.push(o);
});
menu.push($.contextMenu.separator);
var o = new Object();
if(isGene || isHgc) {
var title = mapItems[selectedMapItem].title || "feature";
o["Zoom to " + title] = {onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, "selectWholeGene"); return true; }};
o["Open Link in New Window"] = {onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, "openLink"); return true; }};
} else {
o[mapItems[selectedMapItem].title] = {onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, "hgTrackUi"); return true; }};
}
menu.push(o);
done = true;
} else {
// XXXX currently dead code
if(rec) {
// XXXX check current state from a hidden variable.
var visibilityStrsOrder = new Array("hide", "dense", "full", "pack", "squish");
var visibilityStrs = new Array("hide", "dense", "squish", "pack", "full");
for (i in visibilityStrs) {
// XXXX use maxVisibility and change hgTracks so it can hide subtracks
var o = new Object();
var str = visibilityStrs[i];
if(rec.canPack || (str != "pack" && str != "squish")) {
if(str == visibilityStrsOrder[rec.visibility]) {
str += selectedImg;
}
o[str] = {onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, visibilityStrs[i]); return true; }};
menu.push(o);
}
}
done = true;
}
}
}
if(!done) {
var str = "drag-and-zoom mode";
var o = new Object();
if(autoHideSetting) {
str += selectedImg;
// menu[str].className = 'context-menu-checked-item';
}
o[str] = { onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, "dragZoomMode"); return true; }};
menu.push(o);
o = new Object();
str = "hilight mode";
if(!autoHideSetting) {
str += selectedImg;
}
o[str] = { onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, "hilightMode"); return true; }};
menu.push(o);
menu.push({"view image": {onclick: function(menuItemClicked, menuObject) { contextMenuHit(menuItemClicked, menuObject, "viewImg"); return true; }}});
}
return menu;
},
{
beforeShow: function(e) {
// console.log(mapItems[selectedMapItem]);
selectedMapItem = findMapItem(e);
// XXXX? blockUseMap = true;
},
hideCallback: function() {
// this doesn't work
alert("hideCallback");
}
});
return;
}
function parseMap(ele, reset)
{
// Parse the jQuery <map> object into returned mapItems array (ele needn't be the element attached to current document).
if(reset || !mapItems) {
mapItems = new Array();
}
if(ele) {
var i = mapItems.length;
ele.children().each(function() {
mapItems[i++] = {
r : new Rectangle(this.coords),
href : this.href,
title : this.title,
id : this.id,
obj : this
};
});
}
return mapItems;
}
function showWarning(str)
{
$("#warningText").text(str);
$("#warning").show();
}
function catchErrorOrDispatch(obj,status)
{
if(obj.err)
{
showWarning(obj.err);
jQuery('body').css('cursor', '');
}
else
this.trueSuccess(obj,status);
}
function handleTrackUi(response, status)
{
// Take html from hgTrackUi and put it up as a modal dialog.
$('#hgTrackUiDialog').html(response);
$('#hgTrackUiDialog').dialog({
ajaxOptions: {
// This doesn't work
cache: true
},
resizable: true,
bgiframe: true,
height: 450,
width: 600,
modal: true,
closeOnEscape: true,
autoOpen: false,
title: "Track Settings",
close: function(){
// clear out html after close to prevent problems caused by duplicate html elements
$('#hgTrackUiDialog').html("");
}
});
jQuery('body').css('cursor', '');
$('#hgTrackUiDialog').dialog('open');
}
function handleUpdateTrackMap(response, status)
{
// Handle ajax response with an updated trackMap image (gif or png) and map.
//
// this.cmd can be used to figure out which menu item triggered this.
//
// var a= /(<IMG SRC\s*=\s*([^"]+)"[^>]+id='trackMap'\s*>/.exec(response);
// <IMG SRC = "../trash/hgtIdeo/hgtIdeo_hgwdev_larrym_61d1_8b4a80.gif" BORDER=1 WIDTH=1039 HEIGHT=21 USEMAP=#ideoMap id='chrom'>
// Parse out new ideoGram url (if available)
var a = /<IMG([^>]+SRC[^>]+id='chrom'[^>]*)>/.exec(response);
if(a && a[1]) {
b = /SRC\s*=\s*"([^")]+)"/.exec(a[1]);
if(b[1]) {
$('#chrom').attr('src', b[1]);
}
}
if(imageV2 && this.cmd && this.cmd != 'selectWholeGene') {
// Extract <TR id='tr_ID' class='trDraggable'>...</TR> 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 = mapItems[selectedMapItem].id;
var rec = trackDbJson[id];
if(rec && rec.parentTrack) {
// currently we fall back to the parentTrack
id = rec.parentTrack;
}
var str = "<TR id='tr_" + id + "' class='trDraggable'>([\\s\\S]+?)</TR>";
var reg = new RegExp(str);
a = reg.exec(response);
if(a && a[1]) {
// $('#tr_' + id).html();
// $('#tr_' + id).empty();
$('#tr_' + id).html(a[1]);
// XXXX move following to a shared method
parseMap(null, true);
$("map[name!=ideoMap]").each( function(t) { parseMap($(this, false));});
loadImgAreaSelect(false);
// Do NOT reload context menu (otherwise we get the "context menu sticks" problem).
// loadContextMenu($('#tr_' + id));
} else {
showWarning("Couldn't parse out new image");
}
} else {
if(imageV2) {
a= /<TABLE id=\'imgTbl\'[^>]*>([\S\s]+?)<\/TABLE>/.exec(response);
if(a[1]) {
// This doesn't work (much weirdness ensues).
$('#imgTbl').html(a[1]);
} else {
showWarning("Couldn't parse out new image");
}
} else {
a= /<IMG([^>]+SRC[^>]+id='trackMap[^>]*)>/.exec(response);
// Deal with a is null
if(a[1]) {
var b = /WIDTH\s*=\s*['"]?(\d+)['"]?/.exec(a[1]);
var width = b[1];
b = /HEIGHT\s*=\s*['"]?(\d+)['"]?/.exec(a[1]);
var height = b[1];
b = /SRC\s*=\s*"([^")]+)"/.exec(a[1]);
var src = b[1];
$('#trackMap').attr('src', src);
var obj = imgAreaSelect.data('imgAreaSelect');
if(width) {
trackImg.attr('width', width);
}
if(height) {
trackImg.attr('height', height);
// obj.setOptions({minHeight : height, maxHeight: height});
// obj.getOptions().minHeight = height;
// obj.getOptions().maxHeight = height;
// XXX doesn't work obj.options.maxHeight = height;
// This doesn't work: obj.windowResize();
// This works, but causes weird error IF we also change minHeight and maxHeight.
// jQuery(window).triggerHandler("resize");
// After much debugging, I found the best way to have imgAreaSelect continue to work
// was to reload it:
loadImgAreaSelect(false);
// XXX this doesn't work (i.e. does't make the re-sized row draggable).
jQuery.tableDnD.updateTables();
}
} else {
showWarning("Couldn't parse out new image");
}
}
// now pull out and parse the map.
a = /<MAP id='map' Name=map>([\s\S]+)<\/MAP>/.exec(response);
if(a[1]) {
var $map = $('<map>' + a[1] + '</map>');
parseMap($map, true);
} else {
showWarning("Couldn't parse out map");
}
}
jQuery('body').css('cursor', '');
}