2f6c858cd7d9b2fc0b48f8618bef5e74712c6639
jcasper
  Tue Feb 3 23:16:15 2026 -0800
Hi-C track height in arc mode should match the height of the highest arc, refs #36774

diff --git src/hg/hgTracks/hicTrack.c src/hg/hgTracks/hicTrack.c
index dc5d82d7fd0..2f0a76895a7 100644
--- src/hg/hgTracks/hicTrack.c
+++ src/hg/hgTracks/hicTrack.c
@@ -69,30 +69,42 @@
 struct hicMeta *metaResult = NULL;
 if (filename == NULL)
     {
     warn("Missing bigDataUrl setting for track %s", tg->track);
     return NULL;
     }
 char *errMsg = hicLoadHeader(filename, &metaResult, database);
 if (errMsg != NULL)
     {
     tg->networkErrMsg = errMsg;
     return NULL;
     }
 return metaResult;
 }
 
+int cmpHicItems(const void *elem1, const void *elem2)
+/* Comparison function for sorting Hi-C interactions by score */
+{
+struct interact *item1 = *((struct interact**)elem1);
+struct interact *item2 = *((struct interact**)elem2);
+if (item1->value > item2->value)
+    return 1;
+if (item1->value < item2->value)
+    return -1;
+return 0;
+}
+
 static void loadAndFilterItems(struct track *tg)
 /* Load all Hi-C items in the current region and identify the window height
  * and median value for this region. */
 {
 if (tg->customPt == NULL)
     tg->customPt = grabHeader(tg);
 if (tg->customPt == NULL)
     return;
 struct hicMeta *hicFileInfo = (struct hicMeta*)tg->customPt;
 
 int binSize = hicUiFetchResolutionAsInt(cart, tg->tdb, hicFileInfo, winEnd-winStart);
 char *normalization = hicUiFetchNormalization(cart, tg->tdb, hicFileInfo);
 
 char abbrevBinSize[1024];
 sprintWithMetricBaseUnit(abbrevBinSize, sizeof(abbrevBinSize), binSize);
@@ -159,50 +171,50 @@
         if (sameString(thisHic->sourceChrom, thisHic->targetChrom) &&
                 (thisHic->sourceStart == thisHic->targetStart))
             {
             // a bit of pointer play to avoid repeated calls to slRemoveEl
             *prevNextPtr = thisHic->next; // set prev element's next to the following element
             slAddHead(&filteredOut, thisHic);
             thisHic = *prevNextPtr; // restore thisHic to point to the next element
             continue;
             }
         }
     countsCopy[filtNumRecords++] = thisHic->value;
 
     // Calculate the track draw height required to see this item
     int leftx = max(thisHic->chromStart, winStart);
     int rightx = min(thisHic->chromEnd, winEnd);
-    double thisHeight = scaleForWindow(insideWidth, winStart, winEnd)*(rightx - leftx)/2.0; // triangle or arc
+    // Height in triangle or square mode (we handle arcs separately a bit later)
+    double thisHeight = scaleForWindow(insideWidth, winStart, winEnd)*(rightx - leftx)/2.0; // triangle
     if (sameString(drawMode,HIC_DRAW_MODE_SQUARE))
         thisHeight = scaleForWindow(insideWidth, winStart, winEnd)*(winEnd-winStart); // square - always draw the full square
 
     if (thisHeight > tg->maxRange)
         tg->maxRange = thisHeight;
     prevNextPtr = &thisHic->next;
     thisHic = thisHic->next;
     }
 
 if (filteredOut != NULL)
     interactFreeList(&filteredOut);
 
 // Heuristic for auto-scaling the color gradient based on the scores in view - draw the max color value
 // at or above 2*median score.
 if (filtNumRecords > 0)
     {
     double median = doubleMedian(filtNumRecords, countsCopy);
     tg->graphUpperLimit = 2.0*median;
-    //if (sameString(drawMode, HIC_DRAW_MODE_ARC) && filtNumRecords > 1000)
     if (filtNumRecords > 1000)
         {
         int ix = (int)(filtNumRecords/(0.000008*filtNumRecords + 6.462459));
         if (ix < 1)
             ix = 1;
         tg->graphUpperLimit = countsCopy[filtNumRecords-ix];
         }
     }
 else
     tg->graphUpperLimit = 0.0;
 
 if (countsCopy != NULL)
     freeMem(countsCopy);
 tg->items = hicItems;
 
@@ -210,30 +222,59 @@
 if (sameString(drawMode, HIC_DRAW_MODE_ARC) && hicUiArcLimitEnabled(cart, tg->tdb))
     {
     int itemLimit = hicUiGetArcLimit(cart, tg->tdb);
     if (filtNumRecords > itemLimit)
         hiddenCount += (filtNumRecords-itemLimit);
     }
 if (hiddenCount > 0)
     {
     char filterString[1024] = "";
     safef(filterString, sizeof(filterString), " (%d items filtered out)", hiddenCount);
     newLabel = catTwoStrings(tg->longLabel, filterString);
     freeMem(tg->longLabel);
     tg->longLabel = newLabel;
     }
 
+if (sameString(drawMode, HIC_DRAW_MODE_ARC))
+    {
+    // Handle track height calculations in arc mode (have to sort the arcs first)
+    slSort(&tg->items, cmpHicItems); // So that the darkest arcs are drawn on top (i.e. last) and not lost
+    struct interact *this = (struct interact*) tg->items;
+    if (hicUiArcLimitEnabled(cart, tg->tdb))
+        {
+        // Skip past the arcs that won't be drawn
+        int limit = hicUiGetArcLimit(cart, tg->tdb);
+        int itemCount = filtNumRecords;
+        while (itemCount > limit && this != NULL)
+            {
+            this = this->next;
+            itemCount--;
+            }
+        }
+    tg->maxRange = 0;
+    while (this != NULL)
+        {
+        int leftx = max(this->chromStart, winStart);
+        int rightx = min(this->chromEnd, winEnd);
+        // The max height of this Bezier will be half the height of the middle control point.  We're putting
+        // the control point height at 0.75 of the width between the endpoints because it looks decent.
+        double thisHeight = 0.75 * scaleForWindow(insideWidth, winStart, winEnd)*(rightx - leftx)/2.0;
+        if (thisHeight > tg->maxRange)
+            tg->maxRange = thisHeight;
+        this = this->next;
+        }
+    }
 }
 
 void hicLoadItems(struct track *tg)
 /* Load Hi-C items in (mostly) interact format */
 {
 char *filename = trackDbSettingOrDefault(tg->tdb, "bigDataUrl", NULL);
 if (filename == NULL)
     return;
 if (tg->customPt == NULL)
     {
     tg->customPt = grabHeader(tg);
     struct track *hicInNextWindow = tg->nextWindow;
     while (hicInNextWindow != NULL)
         {
         // pre-cache the hic header info; no reason to re-fetch
@@ -424,65 +465,52 @@
         y = maxHeight-(int)y;
         if (invert)
             y = yScale * ((winEnd-winStart)-leftEnd);
         hvGfxBox(hvg, (int)x+xOff, (int)y+yOff, (int)(xScale*(rightEnd-rightStart))+1, (int)(yScale*(leftEnd-leftStart))+1, colorIx);
         }
     }
     // Draw top-left to bottom-right diagonal axis line in black
     int colorIx = hvGfxFindColorIx(hvg, 0, 0, 0);
     if (invert)  // Draw bottom-left to top-right instead
         hvGfxLine(hvg, xOff, maxHeight+yOff, ((winEnd-winStart)*xScale)+xOff, yOff, colorIx);
     else
         hvGfxLine(hvg, xOff, yOff, ((winEnd-winStart)*xScale)+xOff, maxHeight+yOff, colorIx);
 }
 
 
-int cmpHicItems(const void *elem1, const void *elem2)
-/* Comparison function for sorting Hi-C interactions by score */
-{
-struct interact *item1 = *((struct interact**)elem1);
-struct interact *item2 = *((struct interact**)elem2);
-if (item1->value > item2->value)
-    return 1;
-if (item1->value < item2->value)
-    return -1;
-return 0;
-}
-
 static void drawHicArc(struct track *tg, int seqStart, int seqEnd,
         struct hvGfx *hvg, int xOff, int yOff, int width, 
         MgFont *font, Color color, enum trackVisibility vis)
 /* Draw Hi-C interactions in arc mode */
 {
 double xScale = scaleForWindow(width, seqStart, seqEnd);
 double yScale = xScale;
 int maxHeight = tg->height;
 struct interact *hicItem = NULL;
 struct hicMeta *hicFileInfo = (struct hicMeta*)tg->customPt;
 int binSize = hicUiFetchResolutionAsInt(cart, tg->tdb, hicFileInfo, winEnd-winStart);
 boolean invert = hicUiFetchInverted(cart, tg->tdb);
 if (binSize == 0)
     return;
 
 yScale *= hicSqueezeFactor(vis);
 
 double maxScore = getHicMaxScore(tg);
 Color *colorIxs = colorSetForHic(hvg, tg, HIC_SCORE_BINS+1);
 if (colorIxs == NULL)
     return; // something went wrong with colors
 
-slSort(&tg->items, cmpHicItems); // So that the darkest arcs are drawn on top and not lost
 hicItem = (struct interact *)tg->items;
 if (hicUiArcLimitEnabled(cart,tg->tdb) && (hicUiGetArcLimit(cart, tg->tdb) > 0))
     {
     // limit to only the X highest scoring interactions
     int itemCount = slCount(tg->items);
     int limit = hicUiGetArcLimit(cart, tg->tdb);
     while (itemCount > limit && hicItem != NULL)
         {
         hicItem = hicItem->next;
         itemCount--;
         }
     }
 
 for (; hicItem; hicItem = hicItem->next)
     {
@@ -493,32 +521,31 @@
 
     if ((leftMidpoint < 0) || (leftMidpoint > winEnd-winStart))
         continue;  // skip this item - we'd be drawing to a point off the screen
     if ((rightMidpoint < 0) || (rightMidpoint > winEnd-winStart))
         continue;  // skip this item - we'd be drawing to a point off the screen
 
     int colorIx;
     if (hicItem->value > maxScore)
         colorIx = colorIxs[HIC_SCORE_BINS];
     else
         colorIx = colorIxs[(int)(HIC_SCORE_BINS * hicItem->value/maxScore)];
 
     double leftx = xScale * leftMidpoint;
     double rightx = xScale * rightMidpoint;
     double midx = xScale * (rightMidpoint+leftMidpoint)/2.0;
-    double midy = yScale * (rightMidpoint-leftMidpoint)/2.0;
-    midy *= 1.5; // Heuristic scaling for better use of vertical space
+    double midy = yScale * (rightMidpoint-leftMidpoint) * 0.75; // Heuristic scaling for better-looking arcs
     if (!invert)
         midy = maxHeight-(int)midy;
     int lefty = maxHeight, righty = maxHeight; // the height of the endpoints
     if (invert)
         lefty = righty = 0;
     hvGfxCurve(hvg, (int)leftx+xOff, lefty+yOff, (int)midx+xOff, midy+yOff,
             (int)rightx+xOff, righty+yOff, colorIx, FALSE);
     }
 }
 
 void hicDrawItems(struct track *tg, int seqStart, int seqEnd,
         struct hvGfx *hvg, int xOff, int yOff, int width, 
         MgFont *font, Color color, enum trackVisibility vis)
 /* Draw a set of Hi-C interactions with the current user settings. */
 {