03c9bfc4c9a1746228e2eb542ffd1f30851e83a4
hiram
  Thu Oct 29 12:59:48 2020 -0700
add mouseOver data display, commented out for this check in, uncomment to turn on refs #21980

diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 7d5136d..c26bcd1 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -3782,31 +3782,32 @@
                     }
                 }
 
                 // Need to update vis box (in case this is reached via back-button)
                 if (imageV2.backSupport && fullImageReload) {
                     // Update abbr so that rows can be resorted properly
                     var abbr = $(newTr).attr('abbr');
 
                     if (abbr) {
                         $(tr).attr('abbr', abbr);
                     }
 
                     if (newJsonRec)
                         vis.update(id, vis.enumOrder[newJsonRec.visibility]);
                 }
-//                updateMouseOver(id);
+//                // uncomment to enable the mouseOver system 2020-10 - Hiram
+//                mouseOver.updateMouseOver(id);
                 return true;
             }
         }
         return false;
     },
 
     updateImgForAllIds: function (response, oldJson, newJson)
     {   // update all rows in imgTbl based upon navigateInPlace response.
         var imgTbl = $('#imgTbl');
 
         // We update rows one at a time 
         // (b/c updating the whole imgTbl at one time doesn't work in IE).
         var id;
         for (id in newJson.trackDb) {
             var newJsonRec = newJson.trackDb[id];
@@ -4446,30 +4447,250 @@
 
             var sid = getHgsid();
             if (fullPageLoad) { 
                 // Should only be on initial set-up: first navigation to page
                 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({lastDbPos: newDbPos, position: newPos, hgsid: + sid },title,
                                  "hgTracks?db="+getDb()+"&"+newDbPos+"&hgsid="+sid);
             }
         }
     }
 };
 
+  ///////////////////////////////////////
+ //// mouseOver data display 2020-10 ///
+///////////////////////////////////////
+var mouseOver = {
+
+    spans: {},
+    visible: false,
+    tracks: {},
+
+    // spans{} - key name is track name, value is an array of
+    //                   objects: {x1, x2, value}
+    // visible - keep track of window visible or not, value: true|false
+    //           shouldn't need to do this here, the window knows if it
+    //           is visible or not, just ask it for status
+    // tracks{}  - tracks that were set up initially, key is track name
+    //             value is the number of boxes (for debugging)
+
+    // given hgt_....png file name, change to trackName_....json file name
+    jsonFileName: function(imgElement, trackName)
+    {
+      var jsonFile=imgElement.src.replace("hgt/hgt_", "hgt/" + trackName + "_");
+      jsonFile = jsonFile.replace(".png", ".json");
+      return jsonFile;
+    },
+
+    // called from: updateImgForId when it has updated a track in place
+    // need to refresh the event handlers and json data
+    updateMouseOver: function (trackName)
+    {
+      if (mouseOver.tracks[trackName]) {
+      // there should be a more simple jQuery function to bind these events
+      var tdName = "td_data_" + trackName;
+      var tdElement  = document.getElementById(tdName);
+      var id = tdElement.id;
+      tdElement.addEventListener('mousemove', mouseOver.mouseInTrackImage);
+      tdElement.addEventListener('mouseout', mouseOver.popUpDisappear);
+      var imgName = "img_data_" + trackName;
+      var imgElement  = document.getElementById(imgName);
+      mouseOver.fetchMapData(mouseOver.jsonFileName(imgElement, trackName));
+      }
+    },
+
+    // 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
+    findRange: function (x, rects)
+    {
+      var answer = -1;  // assmume not found
+      for ( var idx in rects ) {
+         if ((rects[idx].x1 <= x) && (x < rects[idx].x2)) {
+           answer = idx;
+           break;
+         }
+      }
+      return answer;
+    },
+
+    popUpDisappear: function () {
+      if (mouseOver.visible) {        // should *NOT* have to keep track !*!
+//       $('#mouseOverText').hide();       // does not function ?
+        var msgWindow = document.querySelector(".wigMouseOver");
+        msgWindow.classList.toggle("showMouseOver");
+        mouseOver.visible = false;
+      }
+    },
+
+    popUpVisible: function () {
+      if (! mouseOver.visible) {        // should *NOT* have to keep track !*!
+//      $('#mouseOverText').show();     // does not function ?
+        var msgWindow = document.querySelector(".wigMouseOver");
+        msgWindow.classList.toggle("showMouseOver");
+        mouseOver.visible = true;
+      }
+    },
+
+    //the evt.target.id is the img_data_<trackName> element of the track graphic
+    mouseInTrackImage: function (evt)
+    {
+    // 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, so verify we are getting the event from the correct
+    //   element.
+    if (! evt.target.id.includes("img_data_")) { return; }
+    var trackName = evt.target.id.replace("img_data_", "");
+    if (trackName.length < 1) { return; }	// verify valid trackName
+    // find location of this <td> slice in the image, this is the track
+    //   image in the graphic, including left margin and center label
+    //   This location follows the window scrolling, could go negative
+    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 offLeft = Math.max(0, Math.floor(evt.x - tdLeft));
+var msg = ". . . mouse x,y: " + evX + "," + evY + ", offLeft: " + offLeft;
+$('#xyMouse').html(msg);
+    var windowUp = false;     // see if window is supposed to become visible
+    var foundIdx = -1;
+    if (mouseOver.spans[trackName]) {
+       foundIdx = mouseOver.findRange(offLeft, mouseOver.spans[trackName]);
+    }
+    // might want to indicate 'no data' when not found
+    if (foundIdx > -1) {
+      // value to display
+      var msg = "&nbsp;" + mouseOver.spans[trackName][foundIdx].v + "&nbsp;";
+      $('#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
+    }
+    if (windowUp) {     // the window should become visible
+      mouseOver.popUpVisible();
+    } else {    // the window should disappear
+      mouseOver.popUpDisappear();
+    } //      window visible/not visible
+    },  //      mouseInTrackImage function (evt)
+
+    // =======================================================================
+    // receiveData() callback for successful JSON request, receives incoming
+    //             JSON 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.
+    // =======================================================================
+    receiveData: function (arr)
+    {
+      mouseOver.visible = false;
+      for (var trackName in arr) {
+      mouseOver.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
+      // there should be a more simple jQuery function to bind these events
+      objectId.addEventListener('mousemove', mouseOver.mouseInTrackImage);
+      objectId.addEventListener('mouseout', mouseOver.popUpDisappear);
+      // 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', popUpDisappear);
+//    window.addEventListener('onscroll', popUpDisappear);
+      var itemCount = 0;	// just for monitoring purposes
+      // save incoming x1,x2,v data into the mouseOver.spans[trackName][] array
+      arr[trackName].forEach(function(box) {
+        mouseOver.spans[trackName].push(box); ++itemCount});
+      mouseOver.tracks[trackName] = itemCount;	// merely for debugging watch
+      }
+    },  //      receiveData: function (arr)
+
+    // =========================================================================
+    // fetchMapData() sends JSON request, callback to receiveData() upon return
+    // =========================================================================
+    fetchMapData: function (url)
+    {
+       var xmlhttp = new XMLHttpRequest();
+       xmlhttp.onreadystatechange = function() {
+       if (4 === this.readyState && 200 === this.status) {
+          var mapData = JSON.parse(this.responseText);
+          mouseOver.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
+    },
+
+    getMouseOverData: function ()
+    {	// verify hgTracks and hgTracks.trackDb exist before running wild
+      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) {
+             mouseOver.fetchMapData(mouseOver.jsonFileName(imgElement, trackName));
+           }
+         }
+       }
+     }
+    },
+
+    addListener: function () {
+        window.addEventListener('load', mouseOver.getMouseOverData, false);
+    }
+};	//	var mouseOver
+
   //////////////////////
  //// 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").
             // NOTE: must match TRACK_SEARCH_PAGER in hg/inc/searchTracks.h
             $("input[name=hgt_tsPage]").val(0);  
             $('#trackSearch').submit();
             // This doesn't work with IE or Safari.
             // $('#searchSubmit').click();
@@ -4499,30 +4720,33 @@
             $('.submitOnEnter').keydown(trackSearch.searchKeydown);
             findTracks.normalize();
             findTracks.updateMdbHelp(0);
         }
     }
 };
 
 
   ///////////////
  //// READY ////
 ///////////////
 $(document).ready(function()
 {
     imageV2.moveTiming();
 
+    // uncomment to enable mouseOver 2020-10 (also one line in updateImgForId)
+//    mouseOver.addListener();
+
     // on Safari the back button doesn't call the ready function.  Reload the page if
     // the back button was pressed.
     $(window).bind("pageshow", function(event) {
         if (event.originalEvent.persisted) {
                 window.location.reload() ;
         }
     });
 
     // The page may be reached via browser history (back button)
     // If so, then this code should detect if the image has been changed via js/ajax
     // and will reload the image if necessary.
     // NOTE: this is needed for IE but other browsers can detect the dirty page much earlier
     if (!imageV2.backSupport) {
         if (imageV2.isDirtyPage()) {
             // mark as non dirty to avoid infinite loop in chrome.