ff4af73ba2a52b18ce5423aae3104fae1d1a3473 hiram Fri Nov 20 17:25:43 2020 -0800 converted to single file for json data instead of one file for each track refs #21980 diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index 8c8f548..8649bca 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -4455,95 +4455,96 @@ 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: {}, + jsonUrl: {}, // list of json files from hidden DIV elements maximumWidth: {}, popUpDelay: 200, // 0.2 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, browserTextSize: 12, // spans{} - key name is track name, value is an array of // objects: {x1, x2, v, c} // where [x1..x2) is the array index where the value 'v' // is found, and 'c' is the data value count in this value // i.e. when c > 1 the value is a 'mean' of 'c' data values // 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) // maximumWidth{} - key is track name, value is length of longest // number string as measured when rendered - // given hgt_....png file name, change to trackName_....json file name - jsonFileName: function(imgElement, trackName) + // given hgt_....png file name, change to hgt_....json file name + jsonFileName: function(imgDataId) { - var jsonFile=imgElement.src.replace("hgt/hgt_", "hgt/" + trackName + "_"); - jsonFile = jsonFile.replace(".png", ".json"); + var jsonFile=imgDataId.src.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, trackDb) { var trackType = null; var hasChildren = null; if (trackDb) { trackType = trackDb.type; hasChildren = trackDb.hasChildren; } else { if (hgTracks.trackDb) { if (hgTracks.trackDb[trackName]) { trackType = hgTracks.trackDb[trackName].type; } } } var tdData = "td_data_" + trackName; var tdDataId = document.getElementById(tdData); var imgData = "img_data_" + trackName; - var imgElement = document.getElementById(imgData); - if (imgElement && tdDataId) { - if (mouseOver.tracks[trackName]) { + var imgDataId = document.getElementById(imgData); + if (imgDataId && tdDataId) { + var url = mouseOver.jsonFileName(imgDataId); + if (mouseOver.tracks[trackName]) { // > 0 -> seen before in receiveData $( tdDataId ).mousemove(mouseOver.mouseMoveDelay); $( tdDataId ).mouseout(mouseOver.popUpDisappear); - mouseOver.fetchMapData(mouseOver.jsonFileName(imgElement, trackName), trackName); + mouseOver.fetchJsonData(url); // may be a refresh, don't know } else { if (trackType) { var validType = false; if (trackType.indexOf("wig") === 0) { validType = true; } if (trackType.indexOf("bigWig") === 0) { validType = true; } if (trackType.indexOf("wigMaf") === 0) { validType = false; } if (hasChildren) { validType = false; } if (validType) { $( tdDataId ).mousemove(mouseOver.mouseMoveDelay); $( tdDataId ).mouseout(mouseOver.popUpDisappear); - mouseOver.fetchMapData(mouseOver.jsonFileName(imgElement, trackName), trackName); + mouseOver.fetchJsonData(url); } } } } }, // 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 var idx = 0; @@ -4601,32 +4602,32 @@ if (trackName.length < 1) { return; } // verify valid trackName // location of mouse relative to the whole page // even when the top of page has scolled off var evtX = Math.floor(evt.pageX); // var evtY = Math.floor(evt.pageY); // var offX = Math.floor(evt.offsetX); // no need for evtY or offX // 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 tdId = document.getElementById(evt.currentTarget.id); var tdRect = tdId.getBoundingClientRect(); var tdLeft = Math.floor(tdRect.left); var tdTop = Math.floor(tdRect.top); - var tdHeight = Math.floor(tdRect.height); var tdWidth = Math.floor(tdRect.width); + var tdHeight = Math.floor(tdRect.height); var rightSide = tdLeft + tdWidth; // clientX is the X coordinate of the mouse hot spot var clientX = Math.floor(evt.clientX); // the graphOffset is the index (x coordinate) into the 'spans' definitions // of the data value boxes for the graph. The magic number three // is used elsewhere in this code, note the comment on the constant // LEFTADD. var graphOffset = Math.max(0, clientX - tdLeft - 3); if (hgTracks.revCmplDisp) { graphOffset = Math.max(0, rightSide - clientX); } var windowUp = false; // see if window is supposed to become visible var foundIdx = -1; if (mouseOver.spans[trackName]) { @@ -4709,114 +4710,123 @@ // (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) { + // clear these variables if they existed before + if (mouseOver.spans[trackName]) {mouseOver.spans[trackName] = undefined;} + if (mouseOver.tracks[trackName]) {mouseOver.tracks[trackName] = 0;} 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 // from jQuery doc: // As the .mousemove() method is just a shorthand // for .on( "mousemove", handler ), detaching is possible // using .off( "mousemove" ). $( tdDataId ).mousemove(mouseOver.mouseMoveDelay); $( tdDataId ).mouseout(mouseOver.popUpDisappear); var itemCount = 0; // just for monitoring purposes // save incoming x1,x2,v data into the mouseOver.spans[trackName][] array var lengthLongestNumberString = 0; var longestNumber = 0; var hasMean = false; for (var span in arr[trackName]) { if (arr[trackName][span].c > 1) { hasMean = true; } var lenV = arr[trackName][span].v.toString().length; if (lenV > lengthLongestNumberString) { lengthLongestNumberString = lenV; longestNumber = arr[trackName][span].v; } mouseOver.spans[trackName].push(arr[trackName][span]); ++itemCount; } - mouseOver.tracks[trackName] = itemCount; // merely for debugging watch + mouseOver.tracks[trackName] = itemCount; // != 0 -> indicates valid track var mouseOverValue = ""; if (hasMean) { mouseOverValue = " μ " + longestNumber + " "; } else { mouseOverValue = " " + longestNumber + " "; } $('#mouseOverText').html(mouseOverValue); // see how big as rendered $('#mouseOverText').css('fontSize',mouseOver.browserTextSize); var maximumWidth = Math.ceil($('#mouseOverText').width()); $('#mouseOverText').html("no data"); // might be bigger if (Math.ceil($('#mouseOverText').width() > maximumWidth)) { maximumWidth = Math.ceil($('#mouseOverText').width()); } mouseOver.maximumWidth[trackName] = maximumWidth; } }, // receiveData: function (arr) - failedRequest: function(trackName) - { // failed request to get json data, remove it from the track list - if (mouseOver.tracks[trackName]) { - delete mouseOver.tracks[trackName]; + failedRequest: function(url) + { // failed request to get json data, remove it from the URL list + if (mouseOver.jsonUrl[url]) { + delete mouseOver.jsonUrl[url]; } }, // ========================================================================= - // fetchMapData() sends JSON request, callback to receiveData() upon return + // fetchJsonData() sends JSON request, callback to receiveData() upon return // ========================================================================= - fetchMapData: function (url, trackName) + fetchJsonData: function (url) { + // avoid fetching the same URL multiple times. Multiple track data + // can be in a single json file + if (mouseOver.jsonUrl[url]) { + mouseOver.jsonUrl[url] += 1; + return; + } + mouseOver.jsonUrl[url] = 1; // remember already done this one var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (4 === this.readyState && 200 === this.status) { var mapData = JSON.parse(this.responseText); mouseOver.receiveData(mapData); } else { if (4 === this.readyState && 404 === this.status) { - mouseOver.failedRequest(trackName); + mouseOver.failedRequest(url); } } }; 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 }, getData: function () { // check for the hidden div elements for mouseOverData + // single file version can find many trackNames, but will all + // be the same URL var trackList = document.getElementsByClassName("mouseOverData"); for (var i = 0; i < trackList.length; i++) { var trackName = trackList[i].getAttribute('name'); - var jsonData = trackList[i].getAttribute('jsonData'); - if (jsonData) { - mouseOver.fetchMapData(jsonData, trackName); - } + var jsonFileUrl = trackList[i].getAttribute('jsonUrl'); + if (jsonFileUrl) { mouseOver.fetchJsonData(jsonFileUrl); } } }, // any scrolling turns the popUp message off scroll: function() { if (mouseOver.visible) { mouseOver.popUpDisappear(); } }, addListener: function () { mouseOver.visible = false; if (window.browserTextSize) { mouseOver.browserTextSize = window.browserTextSize; } window.addEventListener('scroll', mouseOver.scroll, false);