95a26c9ef09eae3c8d6290eb92712a4c7f31d9c6 hiram Tue Nov 24 12:20:03 2020 -0800 prepare for future with different track types and different item data structures refs #21980 diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index 8649bca..e02fe81 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -4452,77 +4452,78 @@ "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: {}, + items: {}, // items[trackName][] - data for each item in this track + visible: false, // keeping track of popUp window visibility + tracks: {}, // tracks[trackName] - number of data items for this track + trackType: {}, // key is track name, value is track type from hgTracks jsonUrl: {}, // list of json files from hidden DIV elements - maximumWidth: {}, + maximumWidth: {}, // maximumWidth[trackName] - largest string to display 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, + delayInProgress: false, // true while waiting for delay timer + mostRecentMouseEvt: null, // to use when mouse delay is finished + browserTextSize: 12, // default if not found otherwise - // spans{} - key name is track name, value is an array of + // items{} - key name is track name, value is an array of data items + // where the format of each item can be different for different + // data tracks. For example, the wiggle track 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) + // value is the number of items (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 hgt_....json file name jsonFileName: function(imgDataId) { 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]) { + } else if (hgTracks.trackDb && hgTracks.trackDb[trackName]) { trackType = hgTracks.trackDb[trackName].type; - } - } + } else if (mouseOver.trackType[trackName]) { + trackType = mouseOver.trackType[trackName]; } var tdData = "td_data_" + trackName; var tdDataId = document.getElementById(tdData); var imgData = "img_data_" + 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.fetchJsonData(url); // may be a refresh, don't know } else { if (trackType) { var validType = false; if (trackType.indexOf("wig") === 0) { validType = true; } @@ -4532,30 +4533,34 @@ if (validType) { $( tdDataId ).mousemove(mouseOver.mouseMoveDelay); $( tdDataId ).mouseout(mouseOver.popUpDisappear); 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 + // Note, different track types could have different intersection + // procedures. For example, the HiC track will need to intersect + // the mouse position within the diamond/square defined by the + // items in the display. findRange: function (x, rects) { var answer = -1; // assmume not found var idx = 0; if (hgTracks.revCmplDisp) { var rectsLen = rects.length - 1; for ( idx in rects ) { if ((rects[rectsLen-idx].x1 <= x) && (x < rects[rectsLen-idx].x2)) { answer = rectsLen-idx; break; } } } else { for ( idx in rects ) { if ((rects[idx].x1 <= x) && (x < rects[idx].x2)) { @@ -4607,51 +4612,51 @@ // var evtY = Math.floor(evt.pageY); // var offX = Math.floor(evt.offsetX); // no need for evtY or offX // 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, 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 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 + // the graphOffset is the index (x coordinate) into the 'items' 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]) { - foundIdx = mouseOver.findRange(graphOffset, mouseOver.spans[trackName]); + if (mouseOver.items[trackName]) { + foundIdx = mouseOver.findRange(graphOffset, mouseOver.items[trackName]); } // can show 'no data' when not found var mouseOverValue = "no data"; if (foundIdx > -1) { // value to display - if (mouseOver.spans[trackName][foundIdx].c > 1) { - mouseOverValue = " μ " + mouseOver.spans[trackName][foundIdx].v + " "; + if (mouseOver.items[trackName][foundIdx].c > 1) { + mouseOverValue = " μ " + mouseOver.items[trackName][foundIdx].v + " "; } else { - mouseOverValue = " " + mouseOver.spans[trackName][foundIdx].v + " "; + mouseOverValue = " " + mouseOver.items[trackName][foundIdx].v + " "; } } $('#mouseOverText').html(mouseOverValue); var msgWidth = mouseOver.maximumWidth[trackName]; $('#mouseOverText').width(msgWidth); var msgHeight = Math.ceil($('#mouseOverText').height()); var lineHeight = Math.max(0, tdHeight - msgHeight); var lineTop = Math.max(0, tdTop + msgHeight); var msgLeft = Math.max(0, clientX - (msgWidth/2) - 3); // with magic 3 var lineLeft = Math.max(0, clientX - 3); // with magic 3 $('#mouseOverText').css('fontSize',mouseOver.browserTextSize); $('#mouseOverText').css('left',msgLeft + "px"); $('#mouseOverText').css('top',tdTop + "px"); $('#mouseOverVerticalLine').css('left',lineLeft + "px"); $('#mouseOverVerticalLine').css('top',lineTop + "px"); @@ -4705,62 +4710,68 @@ // ======================================================================= // 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. + // 2020-11-24 more generalized incoming data structure, don't care + // what the structure is for each item, this will vary + // depending upon the type of track. trackType now remembered + // in mouseOver.trackType[trackName] // ======================================================================= 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.trackType[trackName]) {mouseOver.trackType[trackName] = undefined;} + if (mouseOver.items[trackName]) {mouseOver.items[trackName] = undefined;} if (mouseOver.tracks[trackName]) {mouseOver.tracks[trackName] = 0;} - mouseOver.spans[trackName] = []; // start array + mouseOver.items[trackName] = []; // start array + mouseOver.trackType[trackName] = arr[trackName].t; // add a 'mousemove' and 'mouseout' event listener to each track // display object var tdData = "td_data_" + trackName; var tdDataId = document.getElementById(tdData); // 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 + // save incoming x1,x2,v data into the mouseOver.items[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; + for (var datum in arr[trackName].d) { // .d is the data array + if (arr[trackName].d[datum].c > 1) { hasMean = true; } + var lenV = arr[trackName].d[datum].v.toString().length; if (lenV > lengthLongestNumberString) { lengthLongestNumberString = lenV; - longestNumber = arr[trackName][span].v; + longestNumber = arr[trackName].d[datum].v; } - mouseOver.spans[trackName].push(arr[trackName][span]); + mouseOver.items[trackName].push(arr[trackName].d[datum]); ++itemCount; } 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());