06d7be056190c14b85e71bc12523f18ea6815b5e markd Mon Dec 7 00:50:29 2020 -0800 BLAT mmap index support merge with master diff --git src/hg/js/mouseOver.js src/hg/js/mouseOver.js index 40ca240..1c8c46e 100644 --- src/hg/js/mouseOver.js +++ src/hg/js/mouseOver.js @@ -1,129 +1,327 @@ // One single top-level object variable to hold all other global variables var mapData = {}; -// mapData.rects[] - an array of the rectangles, where each rect is -// {x, y, w, h, v} == x,y width,height, value +// mapData.spans{} - key name is track name, value is an array of +// objects: {x1, x2, value} // mapData.visible - keep track of window visible or not, value: true|false -// mapData.tracks[] - list of tracks with mapBoxes - +// shouldn't need to do this here, the window knows if it +// is visible or not, just ask it for status +// mapData.tracks{} - tracks that came in with mapBoxes, key is track name +// value is the number of boxes (for debugging) // ========================================================================= -// intersect the point with the rectangle, were rect corners are x1,y1 x2,y2 +// intersect the point with the rectangle, where rect corners are x1,y1 x2,y2 // ========================================================================= -function intersectPointRectangle(x,y, rect) { - var answer = true; - if (x <= rect.x1 || x > rect.x2) { answer = false; } - else if (y <= rect.y1 || y > rect.y2) { answer = false; } +// function intersectPointRectangle(x,y, rect) { +// var answer = true; +// if (x <= rect.x1 || x > rect.x2) { answer = false; } +// else if (y <= rect.y1 || y > rect.y2) { answer = false; } +// return answer; +// } + +function dbgShowTracks() { +var dbgMsg = "
    "; +var dbgCounted = 0; +if (typeof(hgTracks) !== "undefined") { + if (typeof (hgTracks.trackDb) !== "undefined") { + for (var trackName in hgTracks.trackDb) { + var isWiggle = false; + var tdName = "td_data_" + trackName; + var tdId = document.getElementById(tdName); + var tdRect = tdId.getBoundingClientRect(); + var tdLeft = Math.floor(tdRect.left); + var tdTop = Math.floor(tdRect.top); + var imgName = "img_data_" + trackName; + var imgElement = document.getElementById(imgName); + var jsonUrl = "no img element"; + if (imgElement) { + var src = imgElement.src; + jsonUrl = imgElement.src.replace("hgt/hgt_", "hgt/" + trackName + "_"); + jsonUrl = jsonUrl.replace(".png", ".json"); +jsonUrl = jsonUrl.replace("https://hgwdev-hiram.gi.ucsc.edu", ""); + } + var rec = hgTracks.trackDb[trackName]; + if (rec.type.includes("wig")) { isWiggle = true; } + if (rec.type.includes("bigWig")) { isWiggle = true; } + if (! isWiggle) { jsonUrl = "not wiggle"; } + var viz = vis.enumOrder[hgTracks.trackDb[trackName].visibility]; + dbgMsg += "
  1. " + trackName + ", " + rec.type + ", " + viz + ", " + tdLeft + "," + tdTop + ", " + jsonUrl + "
  2. "; + dbgCounted += 1; + } + } else { + dbgMsg += "
  3. no hgTracks.trackDb
  4. "; + } +} else { + dbgMsg += "
  5. no hgTracks
  6. "; +} +dbgMsg += "
"; +// if (hgTracks && hgTracks.trackDb) { +// dbgMsg = ". hgTracks.trackDb exists with " + objKeyCount(hgTracks.trackDb) + "objKeyCount"; +// } +$('#debugMsg').html(dbgMsg); +} + +// called from: updateImgForId when it has updated a track in place +// need to refresh the event handlers and json data +function updateMouseOver(trackName) { +// alert("updateMouseOver '" + trackName + ")"); +dbgShowTracks(); +// var msg = ". . . updateMouseOver trackName: '" + trackName + "'"; +// $('#debugMsg').html(msg); + if (! mapData.tracks) { + var msg = ". . . updateMouseOver mapData.tracks has disappeared: '" + trackName + "'"; + $('#debugMsg').html(msg); +return; +} + if (mapData.tracks[trackName]) { + // var msg = ". . . updateMouseOver trackName: '" + trackName + "'"; + // $('#debugMsg').html(msg); + var tdName = "td_data_" + trackName; + var tdElement = document.getElementById(tdName); + var id = tdElement.id; + tdElement.addEventListener('mousemove', mouseInTrackImage); + tdElement.addEventListener('mouseout', mouseLeftTrackImage); + var imgName = "img_data_" + trackName; + var imgElement = document.getElementById(imgName); + var src = imgElement.src; + var jsonUrl = imgElement.src.replace("hgt/hgt_", "hgt/" + trackName + "_"); + jsonUrl = jsonUrl.replace(".png", ".json"); + fetchMapData(jsonUrl); +// alert("updateMouseOver id: '" + jsonUrl + "' valid track here"); + } else { +var msg = ". . . updateMouseOver NOT trackName: '" + trackName + "'"; +$('#debugMsg').html(msg); + return; // not a track we are working on here + } +} + +// given an X coordinate: x, find the index idx +// in the rects[idx] array where rects[idx].x1 <= x < rects[idx].x2 +// returning -1 when not found +// if we knew the array was sorted on x1 we could get out early +// when x < x1 +function findRange(x, rects) { + var answer = -1; + for ( var idx in rects ) { + if ((rects[idx].x1 <= x) && (x < rects[idx].x2)) { + answer = idx; + break; + } + } return answer; } +function popUpDisappear() { + if (mapData.visible) { // should *NOT* have to keep track !*! + var mouseOver = document.querySelector(".wigMouseOver"); + mouseOver.classList.toggle("showMouseOver"); + mapData.visible = false; + } +} + +function popUpVisible() { + if (! mapData.visible) { // should *NOT* have to keep track !*! + var contain = document.getElementById('mouseOverContainer'); + var mouseOver = document.querySelector(".wigMouseOver"); + mouseOver.classList.toggle("showMouseOver"); + mapData.visible = true; + } +} + +function mouseLeftTrackImage(evt) { + popUpDisappear(); +} + +// the evt.target.id is the img_data_ element of the track graphic +function mouseInTrackImage(evt) { +// $('#msgDebug').html(". . . debug message"); + // the center label also events here, can't use that + // plus there is a one pixel line under the center label that has no + // id name at all + if (! evt.target.id.includes("img_data_")) { return; } + var trackName = evt.target.id.replace("img_data_", ""); + if (trackName.length < 1) { return; } + // find location of this slice in the image, this is the track + // image in the graphic, including left margin and center label + // This location follows the window scrolling + var tdName = "td_data_" + trackName; + var tdId = document.getElementById(tdName); + var tdRect = tdId.getBoundingClientRect(); + var tdLeft = Math.floor(tdRect.left); + var tdTop = Math.floor(tdRect.top); + // find the location of the image itself, this could be the single complete + // graphic image of all the tracks, or possibly the single image of the + // track itself. This location also follows the window scrolling and can + // even go negative when the web browser scrolls a window that is larger + // than the width of the web browser. + var imageId = document.getElementById(evt.target.id); + var imageRect = imageId.getBoundingClientRect(); + var imageLeft = Math.floor(imageRect.left); + var imageTop = Math.floor(imageRect.top); + var srcUrl = evt.target.src; + var evX = evt.x; // location of mouse on the web browser screen + var evY = evt.y; +var msg = ". . . mouse x,y: " + evX + "," + evY"; +$('#xyMouse').html(msg); + var offLeft = Math.max(0, Math.floor(evt.x - tdLeft)); + var windowUp = false; // see if window is supposed to become visible + var foundIdx = -1; + var valP = "noX"; + var lengthSpansArray = -1; + if (mapData.spans[trackName]) { + foundIdx = findRange(offLeft, mapData.spans[trackName]); + lengthSpansArray = mapData.spans[trackName].length; + } + if (foundIdx > -1) { + valP = mapData.spans[trackName][foundIdx].v; + // value to display + var msg = " " + mapData.spans[trackName][foundIdx].v + " "; + $('#mouseOverText').html(msg); + var msgWidth = Math.ceil($('#mouseOverText').width()); + var msgHeight = Math.ceil($('#mouseOverText').height()); + var posLeft = evt.x - msgWidth + "px"; + var posTop = tdTop + "px"; + $('#mouseOverContainer').css('left',posLeft); + $('#mouseOverContainer').css('top',posTop); + windowUp = true; // yes, window is to become visible + } +// var offTop = Math.max(0, Math.floor(evt.y - $(this).offset().top)); +// var msg = "

. . . mouse in target.id: " + evt.target.id + "(" + trackName + ")[" + foundIdx + "]='" + valP + "' spansLength: " + lengthSpansArray + " at " + offLeft + "," + offTop + ", evX,Y: " + evX + "," + evY + "

"; +// $('#eventRects').html(msg); + if (windowUp) { // the window should become visible + popUpVisible(); + } else { // the window should disappear + popUpDisappear(); + } // window visible/not visible +} + // ========================================================================= // receiveData() callback for successful JSON request, receives incoming JSON -// data and gets it into global variables for use here +// data and gets it into global variables for use here. +// The incoming 'arr' is a a set of objects where the object key name is +// the track name, used here as an array reference: arr[trackName] +// (currently only one object per json file, one file for each track, +// this may change to have multiple tracks in one json file.) +// The value associated with each track name +// is an array of span definitions, where each element in the array is a +// mapBox definition object: +// {x1:n, x2:n, value:s} +// where n is an integer in the range: 0..width, +// and s is the value string to display +// Will need to get them sorted on x1 for efficient searching as +// they accumulate in the local data structure here. // ========================================================================= function receiveData(arr) { - if (typeof mapData.rects === 'undefined') { - mapData.rects = []; // get this array started first time - mapData.tracks = []; + if (typeof mapData.spans === 'undefined') { + mapData.spans = {}; // get this object started first time + mapData.tracks = {}; } mapData.visible = false; - for (var mapId in arr) { - mapData.tracks.push(mapId); - // save all the incoming mapBoxes into the mapData.rects[] array - arr[mapId].forEach(function(box) { - mapData.rects.push(box)}); + for (var trackName in arr) { +// alert("receiving data for " + trackName); + mapData.spans[trackName] = []; // start array + // add a 'mousemove' and 'mouseout' event listener to each track + // display object + var objectName = "td_data_" + trackName; + var objectId = document.getElementById(objectName); + if (! objectId) { return; } // not sure why objects are not found + objectId.addEventListener('mousemove', mouseInTrackImage); + objectId.addEventListener('mouseout', mouseLeftTrackImage); + // would be nice to know when the window is scrolling in the browser so + // the text box could disappear. These do not appear to work. + // Beware, onscroll event is continuous while scrolling. +// objectId.addEventListener('onscroll', mouseLeftTrackImage); +// window.addEventListener('onscroll', mouseLeftTrackImage); + var itemCount = 0; // just for monitoring purposes + // save incoming x1,x2,v data into the mapData.spans[trackName][] array + arr[trackName].forEach(function(box) { + mapData.spans[trackName].push(box); ++itemCount}); + mapData.tracks[trackName] = itemCount; // merely for debugging watch } +// dbgShowTracks(); +// var msg = ""; +// $('#debugMsg').html(msg); } // ========================================================================= // fetchMapData() sends JSON request, callback to receiveData() upon return // ========================================================================= function fetchMapData(url) { +// alert("fetchMapData(" + url + ")"); var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (4 === this.readyState && 200 === this.status) { var mapData = JSON.parse(this.responseText); receiveData(mapData); } }; xmlhttp.open("GET", url, true); xmlhttp.send(); // sends request and exits this function // the onreadystatechange callback above will trigger // when the data has safely arrived } // function fetchMapData() // Mouse x,y positions arrive as fractions when the // WEB page is zoomed into to make the pixels larger. Hence the Math.floor // to keep them as integers. -function mouseMoving(x, y) { - if (typeof mapData.rects !== 'undefined') { // if there are rectangles - for (var i = 0; i < mapData.tracks.length; i++) { - var trackName = mapData.tracks[i]; - var imgData = "img_data_" + trackName; - var imgMap = document.getElementById(imgData); - var imageRect = imgMap.getBoundingClientRect(); - var imageTop = imageRect.top; - // x1,y1 are coordinates of mouse relative to the top,left corner of - // the browser tracks image - var x1 = x - Math.floor(imageRect.left); - var y1 = y - Math.floor(imageRect.top); - var windowUp = false; // see if window is supposed to become visible +// function mouseMoving(x, y) { +// var xyMouse = "

. . . mouse at x,y: " + Math.floor(x) + "," + Math.floor(y); +// $('#xyMouse').html(xyMouse); +// } // function mouseMoving(x, y) - for (var rect of mapData.rects) { // work through rects list - if (intersectPointRectangle(x1,y1, rect)) { // found mouse in rect - var msg = " " + rect.v + " "; // value to display - $('#mouseOverText').html(msg); - var msgWidth = Math.ceil($('#mouseOverText').width()); - var msgHeight = Math.ceil($('#mouseOverText').height()); - var posLeft = (x - msgWidth) + "px"; - var posTop = (imageTop + rect.y1) + "px"; - $('#mouseOverContainer').css('left',posLeft); - $('#mouseOverContainer').css('top',posTop); - windowUp = true; // yes, window is to become visible - break; // can stop looking after first match +function getMouseOverData() { + if (typeof(hgTracks) !== "undefined") { + if (typeof (hgTracks.trackDb) !== "undefined") { + for (var trackName in hgTracks.trackDb) { + var isWiggle = false; + var rec = hgTracks.trackDb[trackName]; + if (rec.type.includes("wig")) { isWiggle = true; } + if (rec.type.includes("bigWig")) { isWiggle = true; } + if (! isWiggle) { continue; } + var imgName = "img_data_" + trackName; + var imgElement = document.getElementById(imgName); + if (imgElement) { + var src = imgElement.src; + var jsonUrl = src.replace("hgt/hgt_", "hgt/" + trackName + "_"); + jsonUrl = jsonUrl.replace(".png", ".json"); + jsonUrl = jsonUrl.replace("https://hgwdev-hiram.gi.ucsc.edu", ""); + fetchMapData(jsonUrl); } } - if (windowUp) { // the window should become visible - if (! mapData.visible) { // should *NOT* have to keep track !*! - var contain = document.getElementById('mouseOverContainer'); - var text = document.getElementById('mouseOverText'); - text.style.backgroundColor = "#ff69b4"; - var mouseOver = document.querySelector(".wigMouseOver"); - mouseOver.classList.toggle("showMouseOver"); - mapData.visible = true; } - } else { // the window should disappear - if (mapData.visible) { - var mouseOver = document.querySelector(".wigMouseOver"); - mouseOver.classList.toggle("showMouseOver"); - mapData.visible = false; } - } // window visible/not visible - } // for each track - } // if there are rectangles defined -} // function mouseMoving(x, y) - -function getMouseOverData() { +dbgShowTracks(); // there could be a number of these mouseOver class elements // there is one for each track that has this mouseOver data - var x = document.getElementsByClassName("mouseOver"); - for (var i = 0; i < x.length; i++) { - var trashUrl = x[i].getAttribute('trashFile'); - fetchMapData(trashUrl); - } +// var x = document.getElementsByClassName("mouseOver"); +// for (var i = 0; i < x.length; i++) { +// var trashUrl = x[i].getAttribute('trashFile'); +// fetchMapData(trashUrl); +// } } -function getMousePos(evt) { - return { x: evt.clientX, y: evt.clientY }; -} +// function getMousePos(evt) { +// return { x: evt.clientX, y: evt.clientY }; +// } -function addMouseMonitor() { - window.addEventListener('load', function(evt) { - getMouseOverData(); - }, false); - window.addEventListener('mousemove', function(evt) { - var mousePos = getMousePos(evt); - mouseMoving(Math.floor(mousePos.x), Math.floor(mousePos.y)); - }, false); -} +// function addMouseMonitor() { +// window.addEventListener('load', function(evt) { +// getMouseOverData(); +// }, false); +// window.addEventListener('mousemove', function(evt) { +// var mousePos = getMousePos(evt); +// mouseMoving(Math.floor(mousePos.x), Math.floor(mousePos.y)); +// }, false); +// } -$('document').ready(addMouseMonitor()); +// $('document').ready(addMouseMonitor());