57aba7b8fb23d9bdbeceb665a001452e6d7a6b99
jcasper
  Thu Jan 8 00:37:48 2026 -0800
An attempt at better auto-scaling for Hi-C colors in arc mode, refs #36774

diff --git src/hg/hgTracks/hicTrack.c src/hg/hgTracks/hicTrack.c
index 789abd0cc38..dc5d82d7fd0 100644
--- src/hg/hgTracks/hicTrack.c
+++ src/hg/hgTracks/hicTrack.c
@@ -85,80 +85,82 @@
 /* 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);
 int newStringLen = strlen(tg->longLabel) + strlen(abbrevBinSize) + strlen(normalization) + 10;
+char *drawMode = hicUiFetchDrawMode(cart, tg->tdb);
 char *newLabel = needMem(newStringLen);
 safef(newLabel, newStringLen, "%s (%s, %s)", tg->longLabel, abbrevBinSize, normalization);
 tg->longLabel = newLabel;  // leaks old cloneString() memory chunk
 
 // Later, it would be nice to validate that this file is for the current assembly (see the hicMeta
 // structure).  It would be hard - the assembly name in the file's "genome" field can't be relied on.
 // Maybe by comparing chromosome names and sizes?
 
 // Note: This is giving it a 0-based, full-closed window. Straw seems to use 1-based coordinates
 // by default, but accepts 0 as the start of a window without complaint.
 // Pad the start because we want to display partial interactions if the end of an interacting block is
 // in view but not the start.  Straw won't report interactions if the start of the block isn't in the
 // supplied position range.
 int strawStart = winStart - binSize + 1;
 if (strawStart < 0) strawStart = 0;
 
 struct interact *hicItems = NULL;
 tg->networkErrMsg = hicLoadData(hicFileInfo, binSize, normalization, chromName, strawStart, winEnd-1, chromName,
         strawStart, winEnd-1, &hicItems);
 
 // Using the interact structure because it has convenient fields, but this is not interact data and
 // shouldn't be passed to those functions.
 int numRecords = slCount(hicItems), filtNumRecords = 0;
 tg->maxRange = 0.0; // the max height of an interaction in this window
 double *countsCopy = NULL;
 if (numRecords > 0)
     AllocArray(countsCopy, numRecords);
 
 struct interact *thisHic = hicItems;
-char *drawMode = hicUiFetchDrawMode(cart, tg->tdb);
 struct interact* filteredOut = NULL;
 struct interact** prevNextPtr = &hicItems; // for removing items from the linked list
 
 double maxRange = hicUiMaxInteractionRange(cart, tg->tdb);
 double minRange = hicUiMinInteractionRange(cart, tg->tdb);
 
+int filteredOutCount = 0; // For reporting how many elements were excluded (not including self loops)
 while (thisHic != NULL)
     {
     // Add filtering based on max interaction distance
     if (sameString(thisHic->sourceChrom, thisHic->targetChrom))
         {
         unsigned leftEdge = thisHic->sourceStart < thisHic->targetStart ? thisHic->sourceStart : thisHic->targetStart;
         unsigned rightEdge = thisHic->sourceEnd > thisHic->targetEnd ? thisHic->sourceEnd : thisHic->targetEnd;
         if ((maxRange && maxRange < (double)(rightEdge - leftEdge) ) ||
             (minRange && minRange > (double)(rightEdge - leftEdge) ))
             {
             // 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
+            filteredOutCount++;
             continue;
             }
         }
 
     if (sameString(drawMode, HIC_DRAW_MODE_ARC))
         {
         // we omit self-interactions in arc mode (they'd just be weird vertical lines)
         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;
@@ -173,37 +175,65 @@
     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)
-    tg->graphUpperLimit = 2.0*doubleMedian(filtNumRecords, countsCopy);
+    {
+    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;
+
+int hiddenCount = filteredOutCount;
+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;
+    }
+
 }
 
 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