7bed537d83b701bb4284080c2b634f1069417cae braney Thu Jun 18 12:07:03 2026 -0700 fix squishyPack track stealing a neighboring track's center label when image orders collide A squishyPack track is split into a full-height part and a squished part that are stitched back together by smashSquish(), which assumes the squished clone is drawn immediately after its source. flatTracksCmp() broke an image-order tie involving a squink track purely by visibility, even against an unrelated track sharing the same order, so a colliding track (e.g. MANE vs GENCODE V49) could sort between the source and its squink. smashSquish() then merged the squished slice onto the wrong row, dragging the neighbor's center label with it. Keep a squink adjacent to its own source in flatTracksCmp(), and guard smashSquish() so it only merges a linked neighbor cloned from that track, refs #37785 diff --git src/hg/hgTracks/imageV2.c src/hg/hgTracks/imageV2.c index 5f95146aeae..82ad780d229 100644 --- src/hg/hgTracks/imageV2.c +++ src/hg/hgTracks/imageV2.c @@ -55,32 +55,39 @@ flatTrack->order = topOrder + index + 1; else flatTrack->order = ++lastOrder; } slAddHead(flatTracks,flatTrack); } int flatTracksCmp(const void *va, const void *vb) // Compare to sort on flatTrack->order { const struct flatTracks *a = *((struct flatTracks **)va); const struct flatTracks *b = *((struct flatTracks **)vb); if (a->order == b->order) { - if ((a->track->originalTrack != NULL) || (b->track->originalTrack != NULL)) - return a->track->visibility - b->track->visibility; + // A squishyPack "squink" track (originalTrack set) shares its source track's order and + // must sort immediately after that source so the two can be smashed back together (see + // smashSquish). Use visibility to order the genuine source/squink pair, but a squink + // compared against an unrelated track that happens to share the same order must fall + // through to priority - since the squink carries its source's priority, that keeps it + // grouped with its source instead of being separated by the other track. + struct track *at = a->track, *bt = b->track; + if (sameOk(at->originalTrack, bt->track) || sameOk(bt->originalTrack, at->track)) + return at->visibility - bt->visibility; return tgCmpPriority(&(a->track),&(b->track)); } return (a->order - b->order); } void flatTracksSort(struct flatTracks **flatTracks) // This routine sorts the imgTracks then forces tight ordering, so new tracks wil go to the end { // flatTracks list has 2 sets of "order": those already dragReordered (below IMG_ORDERTOP) // and those not yet reordered (above). Within those not yet dragReordered are 2 sets: // Those that begin numbering at IMG_ORDERTOP and those that begin at IMG_ORDEREND. // This routine must determine if there are any already dragOrdered, and if so, position the // newcomers in place. Newly appearing customTracks will appear at top, while newly appearing // standard tracks appear at the end of the image. int haveBeenOrderd = 0, imgOrdHighest=0; // Keep track of reordered count and position @@ -2071,31 +2078,36 @@ hPrintf("</A>"); if (slice->parentImg) hPrintf("</div>"); } struct imgTrack *smashSquish(struct imgTrack *imgTrackList) /* Javascript doesn't know about our trick to do squishyPack so we need to pass it a single div instead of two. * We assume that the linked track immediately follows the original track in the sorted list. */ { struct imgTrack *nextImg, *imgTrack; for (imgTrack = imgTrackList; imgTrack!=NULL;imgTrack = nextImg) { nextImg = imgTrack->next; - boolean joinNext = ((nextImg != NULL) && nextImg->linked); + // Only smash a squink into the track it was cloned from. nextImg->linked is set for any + // squishyPack squink, but if an unrelated track sorted between a squink and its source we + // must not merge the squink's slice into the wrong neighbor (its pixels are elsewhere). + boolean joinNext = ((nextImg != NULL) && nextImg->linked + && nextImg->tdb != NULL && imgTrack->tdb != NULL + && sameOk(nextImg->tdb->originalTrack, imgTrack->tdb->track)); if (joinNext) // Smash these together { struct imgSlice *packedSlices = imgTrack->slices; struct imgSlice *squishSlices = imgTrack->next->slices; if ((packedSlices == NULL) || (squishSlices == NULL) || (slCount(packedSlices) != slCount(squishSlices))) continue; for(; packedSlices && squishSlices; packedSlices = packedSlices->next, squishSlices = squishSlices->next) { if (packedSlices->type != stCenter) { if ((packedSlices->map != NULL) && (squishSlices->map != NULL)) packedSlices->map->items = slCat(packedSlices->map->items, squishSlices->map->items);