c32d6e08d19a0b8ad7ece12aab39d0ba0be7a9e1
chmalee
  Mon Dec 1 16:26:27 2025 -0800
hgTracks tooltips use the data-tooltip attribute on the area element if present, falling back to the title attribute. Makes title attributes for (hopefully all) tracks with special tooltips put the tooltip string into the data-tooltip attribute instead of the title attribute, refs #35756

diff --git src/hg/hgTracks/imageV2.c src/hg/hgTracks/imageV2.c
index bd8753c83ca..9d50ecd1506 100644
--- src/hg/hgTracks/imageV2.c
+++ src/hg/hgTracks/imageV2.c
@@ -366,117 +366,128 @@
 {
 struct mapItem *item;
 for (item=map->items;item!=NULL;item=item->next)
     {
     if ((abs(item->topLeftX     - topLeftX)     < 2)
     &&  (abs(item->topLeftY     - topLeftY)     < 2)
     &&  (abs(item->bottomRightX - bottomRightX) < 2)
     &&  (abs(item->bottomRightY - bottomRightY) < 2)) // coordinates within a pixel is okay
         return item;
     }
 return NULL;
 }
 
 struct mapItem *mapSetItemUpdate(struct mapSet *map,struct mapItem *item,char *link,char *title,
                                  int topLeftX,int topLeftY,int bottomRightX,int bottomRightY,
-                                 char *id)
+                                 char *id, char *tooltip)
 // Update a single mapItem
 {
 if (title != NULL)
+    {
     item->title = cloneString(title);
+    item->tooltip = cloneString(title);
+    }
+if (tooltip != NULL)
+    item->tooltip = cloneString(tooltip);
 if (link != NULL)
     {
     if (map->linkRoot != NULL && startsWith(map->linkRoot,link))
         item->linkVar = cloneString(link + strlen(map->linkRoot));
     else
         item->linkVar = cloneString(link);
     }
 item->topLeftX     = topLeftX;
 item->topLeftY     = topLeftY;
 item->bottomRightX = bottomRightX;
 item->bottomRightY = bottomRightY;
 freeMem(item->id);
 item->id           = cloneString(id);
 return item;
 }
 
 struct mapItem *mapSetItemAdd(struct mapSet *map,char *link,char *title,int topLeftX,int topLeftY,
-                              int bottomRightX,int bottomRightY, char *id)
+                              int bottomRightX,int bottomRightY, char *id, char *tooltip)
 // Add a single mapItem to a growing mapSet
 {
 struct mapItem *item;
 AllocVar(item);
 if (title != NULL)
+    {
     item->title = cloneString(title);
+    // default the tooltip to the title
+    item->tooltip = cloneString(title);
+    }
+if (tooltip != NULL)
+    item->tooltip = cloneString(tooltip);
 if (link != NULL)
     {
     if (map->linkRoot != NULL && startsWith(map->linkRoot,link))
         item->linkVar = cloneString(link + strlen(map->linkRoot));
     else
         item->linkVar = cloneString(link);
     }
 item->topLeftX     = topLeftX;
 item->topLeftY     = topLeftY;
 item->bottomRightX = bottomRightX;
 item->bottomRightY = bottomRightY;
 item->id           = cloneString(id);
 slAddHead(&(map->items),item);
 //warn("Added map(%s) item '%s' count:%d",map->name,title,slCount(map->items));
 return map->items;
 }
 
 struct mapItem *mapSetItemUpdateOrAdd(struct mapSet *map,char *link,char *title,
                                       int topLeftX,int topLeftY,int bottomRightX,int bottomRightY,
-                                      char *id)
+                                      char *id, char *tooltip)
 // Update or add a single mapItem
 {
 struct mapItem *item = mapSetItemFind(map,topLeftX,topLeftY,bottomRightX,bottomRightY);
 if (item != NULL)
-    return mapSetItemUpdate(map,item,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY, id);
+    return mapSetItemUpdate(map,item,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY, id, tooltip);
 else
-    return mapSetItemAdd(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY, id);
+    return mapSetItemAdd(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY, id, tooltip);
 }
 
 struct mapItem *doMapSetItemFindOrAdd(struct mapSet *map,char *link,char *title,
                                     int topLeftX,int topLeftY,int bottomRightX,int bottomRightY,
-                                    char *id)
+                                    char *id, char *tooltip)
 // Finds or adds the map item
 {
 struct mapItem *item = mapSetItemFind(map,topLeftX,topLeftY,bottomRightX,bottomRightY);
 if (item != NULL)
     return item;
 else
-    return mapSetItemAdd(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY,id);
+    return mapSetItemAdd(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY,id, tooltip);
 }
 
 struct mapItem *mapSetItemFindOrAdd(struct mapSet *map,char *link,char *title,
                                     int topLeftX,int topLeftY,int bottomRightX,int bottomRightY,
-                                    char *id)
+                                    char *id, char *tooltip)
 // Function to allow conf variable to turn off or on the searching of overlapping
 // previous boxes.
 {
 static struct mapItem *(*mapFunc)() = NULL;
 
 if (mapFunc == NULL)
     {
     if (cfgOption("restoreMapFind"))
         mapFunc = doMapSetItemFindOrAdd;
     else
         mapFunc = mapSetItemAdd;
     }
 
-return (*mapFunc)(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY,id);
+return (*mapFunc)(map,link,title,topLeftX,topLeftY,bottomRightX,bottomRightY,id, tooltip);
 }
 
 void mapItemFree(struct mapItem **pItem)
 // frees all memory assocated with a single mapItem
 {
 if (pItem != NULL && *pItem != NULL)
     {
     struct mapItem *item = *pItem;
     if (item->title != NULL)
         freeMem(item->title);
     if (item->linkVar != NULL)
         freeMem(item->linkVar);
     if (item->id != NULL)
         freeMem(item->id);
     freeMem(item);
@@ -1123,31 +1134,31 @@
 int bottomY = 0;
 imgTrackCoordinates(imgTrack,NULL,NULL,NULL,&bottomY);
 return bottomY;
 }
 
 struct mapSet *imgTrackGetMapByType(struct imgTrack *imgTrack,enum sliceType type)
 // Gets the map assocated with a specific slice belonging to the imgTrack
 {
 struct imgSlice *slice = imgTrackSliceGetByType(imgTrack,type);
 if (slice == NULL)
     return NULL;
 return sliceGetMap(slice,FALSE); // Map could belong to image or could be slice specific
 }
 
 int imgTrackAddMapItem(struct imgTrack *imgTrack,char *link,char *title,
-                       int topLeftX,int topLeftY,int bottomRightX,int bottomRightY, char *id)
+                       int topLeftX,int topLeftY,int bottomRightX,int bottomRightY, char *id, char *tooltip)
 // Will add a map item to an imgTrack's appropriate slice's map.  Since a map item may span
 // slices, the imgTrack is in the best position to determine where to put the map item
 // returns count of map items added, which could be 0, 1 or more than one if item spans slices
 // NOTE: Precedence is given to first map item when adding items with same coordinates!
 {
 if (imgTrack == NULL)
     return 0;
 struct imgSlice *slice;
 char *imgFile = NULL;               // name of file that hold the image
 char *neededId = NULL; // id is only added it it is NOT the trackId.
 if (imgTrack->tdb == NULL || differentStringNullOk(id, imgTrack->tdb->track))
     neededId = id;
 
 // Trap surprising location s for map items, but only on test machines.
 if (hIsPrivateHost())
@@ -1174,31 +1185,31 @@
         {
         if (imgFile == NULL)
             imgFile = slice->parentImg->file;
         }
     if (topLeftX     < (slice->offsetX + slice->width-1)
     && bottomRightX > (slice->offsetX + 1)
     && topLeftY     < (slice->offsetY + slice->height-1)
     && bottomRightY > (slice->offsetY + 1)) // Overlap of a pixel or 2 is tolerated
         {
         struct mapSet *map = sliceGetMap(slice,FALSE);
         if (map!=NULL)
             {  // NOTE: using find or add gives precedence to first of same coordinate map items
             mapSetItemFindOrAdd(map,link,title,max(topLeftX,slice->offsetX),
                                 max(topLeftY,slice->offsetY),
                                 min(bottomRightX,slice->offsetX + slice->width),
-                                min(bottomRightY,slice->offsetY + slice->height), neededId);
+                                min(bottomRightY,slice->offsetY + slice->height), neededId, tooltip);
             count++;
             }
         else
             {  // NOTE: This assumes that if there is no map, the entire slice should get the link!
             char * name = (imgTrack->name != NULL ? imgTrack->name
                                                   : imgTrack->tdb != NULL ? imgTrack->tdb->track
                                                                           : imgFile);
             warn("imgTrackAddMapItem(%s,%s) mapItem(lx:%d,rx:%d) is overlapping "
                  "slice:%s(lx:%d,rx:%d)",name,title,topLeftX,bottomRightX,
                  sliceTypeToString(slice->type),slice->offsetX,(slice->offsetX + slice->width - 1));
             sliceAddLink(slice,link,title);
             count++;
             }
         }
     }
@@ -1828,30 +1839,33 @@
             hPrintf(" HREF=%s",item->linkVar);
         else if (startsWith("/cgi-bin/hgGene", item->linkVar)) // redmine #4151
             hPrintf(" HREF='..%s'",item->linkVar);             // FIXME: Chin should get rid
         else                                                   // of this special case!
             hPrintf(" HREF='%s'",item->linkVar);
         hPrintf(" class='area'");
         }
     else
         warn("map item has no url!");
 
     if (item->title != NULL && strlen(item->title) > 0)
         {
         char *encodedString = attributeEncode(item->title);
         if (cfgOptionBooleanDefault("showMouseovers", FALSE))
             {
+            if (isNotEmpty(item->tooltip))
+                hPrintf(" title='%s' data-tooltip='%s'", encodedString, attributeEncode(item->tooltip));
+            else
                 hPrintf(" TITLE='%s'", encodedString);
             }
         else
             {
             // for TITLEs, which we use for mouseOvers,  since they can't have HTML in
             // them, we substitute a unicode new line for <br> after we've encoded it.
             // This is stop-gap until we start doing mouseOvers entirely in Javascript
             hPrintf(" TITLE='%s'", replaceChars(encodedString,"&#x3C;br&#x3E;", "&#8232;"));
             }
         }
     if (item->id != NULL)
         hPrintf(" id='%s'", item->id);
     hPrintf(">" );
     }
 hPrintf("</MAP>\n");