4390310b0fa270367bc7b18e5728bfb1a2a9670d
hiram
  Mon Nov 2 16:05:32 2020 -0800
mouse over popup now has a one second delay before appearing refs #21980

diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 92e8630..b552cf4 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -4455,58 +4455,62 @@
                 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: {},
-//    popUpDelay: 100000,       // can not get this to work ?
+    popUpDelay: 1000,   // one second delay before popUp appears
+    popUpTimer: null,// handle from setTimeout to use in clearTimout(popUpTimer)
+    delayDone: true,   // mouse has not left element, still receiving move evts
+    delayInProgress: false,        // true if working with delay timer done
+    mostRecentMouseEvt: null,
 
     // 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 tdData = "td_data_" + trackName;
       var tdElement  = document.getElementById(tdData);
       var id = tdElement.id;
-      tdElement.addEventListener('mousemove', mouseOver.mouseInTrackImage);
+      tdElement.addEventListener('mousemove', mouseOver.mouseMoveDelay);
       tdElement.addEventListener('mouseout', mouseOver.popUpDisappear);
       var imgData = "img_data_" + trackName;
       var imgElement  = document.getElementById(imgData);
       mouseOver.fetchMapData(mouseOver.jsonFileName(imgElement, trackName), 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
@@ -4516,43 +4520,47 @@
            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;
 //        $('#mouseOverContainer').css('display','none'); // does not work
         $('#mouseOverLine').css('display','none');
       }
-//      mouseOver.popUpDelay = 100000;
+      if (mouseOver.popUpTimer) {
+         clearTimeout(mouseOver.popUpTimer);
+         mouseOver.popUpTimer = null;
+      }
+      mouseOver.delayDone = true;
+      mouseOver.delayInProgress = 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;
 //        $('#mouseOverContainer').css('display','block');  // does not work
         $('#mouseOverLine').css('display','block');
       }
-//      mouseOver.popUpDelay = 10;
     },
 
     //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
@@ -4595,72 +4603,94 @@
       $('#mouseOverLine').css('left',evt.x + "px");
       $('#mouseOverLine').css('top',posTop);
       // Setting the height of this line to the full image height eliminates
       //  the mouse event area
       $('#mouseOverLine').css('height',tdHeight + "px");
 //      $('#mouseOverLine').height(imageHeight + "px");
       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)
 
-/*      this doesn't work, claims there is an error in security policy
+    // timeout calls here upon completion
+    delayCompleted: function()
+    {
+       mouseOver.delayDone = true;
+       // mouse could just be sitting there with no events, if there
+       // have been events during the timer, the evt has been recorded
+       // so the popUp appears where the mouse is while it moved during the
+       // time delay since mostRecentMouseEvt is up to date to now
+       // If mouse has moved out of element during timeout, the
+       // delayInProgress will be false and nothing happens.
+       if (mouseOver.delayInProgress) {
+          mouseOver.mouseInTrackImage(mouseOver.mostRecentMouseEvt);
+       }
+    },
+
+    // all mouse move events come here even during timeout
     mouseMoveDelay: function (evt)
     {
-      if (mouseOver.popUpDelay == 100000) {     // first time here
-        mouseOver.popUpDelay -= 1;              // no longer first time
-        setTimeout(mouseOver.mouseInTrackImage(evt), mouseOver.popUpDelay);
-      } else if (mouseOver.popUpDelay > 10) {
-        return; // wait for first one to complete before issuing more
+      mouseOver.mostRecentMouseEvt = evt;   // record evt for delayCompleted
+      if (mouseOver.delayInProgress) {
+        if (mouseOver.delayDone) {
+          mouseOver.mouseInTrackImage(evt);	// OK to trigger event now
+          return;
         } else {
-        mouseOver.mouseInTrackImage(evt);  // after first one is done, pass them along
+          return; // wait for delay to be done
+        }
       }
+      mouseOver.delayDone = false;
+      mouseOver.delayInProgress = true;
+      if (mouseOver.popUpTimer) {
+         clearTimeout(mouseOver.popUpTimer);
+         mouseOver.popUpTimer = null;
+      }
+      mouseOver.popUpTimer = setTimeout(mouseOver.delayCompleted, mouseOver.popUpDelay);
     },
-*/
 
     // =======================================================================
     // 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 tdData = "td_data_" + trackName;
       var tdDataId  = document.getElementById(tdData);
       if (! tdDataId) { return; } // not sure why objects are not always found
       // there should be a more simple jQuery function to bind these events
-      tdDataId.addEventListener('mousemove', mouseOver.mouseInTrackImage);
+      tdDataId.addEventListener('mousemove', mouseOver.mouseMoveDelay);
       tdDataId.addEventListener('mouseout', mouseOver.popUpDisappear);
       var itemCount = 0;	// just for monitoring purposes
       // save incoming x1,x2,v data into the mouseOver.spans[trackName][] array
       for (var span in arr[trackName]) {
         mouseOver.spans[trackName].push(arr[trackName][span]);
        ++itemCount;
       }
       mouseOver.tracks[trackName] = itemCount;	// merely for debugging watch
       }
     },  //      receiveData: function (arr)
 
     failedRequest: function(trackName)
     {
       if (mouseOver.tracks[trackName]) {
 //      alert("failed request trackName: '"+ trackName + "'");