5dc1d6e658ab009f27314e192340275a6bb70237
max
  Tue Jun 2 16:20:48 2026 -0700
Add colorFields trackDb setting for bigBed/bigGenePred color scheme switching

Adds a new trackDb statement `colorFields` that renders a "Color by:" dropdown
in the track controls page, letting users switch among multiple pre-computed
color schemes stored as extra bigBed fields containing R,G,B strings.

The `default="label"` key renames the standard itemRgb option in the dropdown.
Other entries name extra bigBed fields whose R,G,B values override itemRgb when
selected. When a non-default scheme is active, a "(Coloring by: label)" suffix
appears in the track long label.

Changes:
- hui.c/hui.h: new colorFieldsCfgUi() rendered inside bedScoreCfgUi() for bigBed
- bigBedTrack.c: colorFieldIdx lookup + per-item filterColor override + longLabel suffix
- tagTypes.tab: register colorFields for bigBed and bigGenePred
- trackDbLibrary.shtml, trackDbDoc.html, trackDbHub.v3.html, changes.html: documentation

refs #26253

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

diff --git src/hg/hgTracks/bigBedTrack.c src/hg/hgTracks/bigBedTrack.c
index 95edcc0f7b5..d48d1f4519b 100644
--- src/hg/hgTracks/bigBedTrack.c
+++ src/hg/hgTracks/bigBedTrack.c
@@ -710,30 +710,55 @@
 boolean bigBedOnePath = cfgOptionBooleanDefault("bigBedOnePath", TRUE);
 if (bigBedOnePath  && (fieldCount == 0))
     track->bedSize = fieldCount = bbi->definedFieldCount;
 
 struct bigBedInterval *bb, *bbList; 
 char *quickLiftFile = cloneString(trackDbSetting(track->tdb, "quickLiftUrl"));
 struct hash *chainHash = NULL;
 if (quickLiftFile)
     bbList = quickLiftGetIntervals(quickLiftFile, bbi, chromName, winStart, winEnd, &chainHash);
 else
     bbList = bigBedSelectRangeExt(track, chrom, start, end, lm, maxItems);
 
 char *squishField = cartOrTdbString(cart, track->tdb, "squishyPackField", NULL);
 int squishFieldIdx = bbExtraFieldIndex(bbi, squishField);
 
+/* colorFields: optional alternative color scheme stored in a named extra field. */
+int colorFieldIdx = 0;
+char *colorFieldsSetting = trackDbSettingClosestToHome(tdb, "colorFields");
+if (useItemRgb && colorFieldsSetting)
+    {
+    char *colorFieldName = cartOptionalStringClosestToHome(cart, tdb, FALSE, "colorField");
+    if (!isEmpty(colorFieldName))
+        {
+        colorFieldIdx = bbExtraFieldIndex(bbi, colorFieldName);
+        /* Append "(Coloring by: <label>)" to the track's longLabel.
+         * Look up the human-readable label from the colorFields key=value list. */
+        char *label = colorFieldName;
+        struct slPair *pairs = slPairListFromString(colorFieldsSetting, TRUE);
+        if (pairs)
+            {
+            struct slPair *p = slPairFind(pairs, colorFieldName);
+            if (p && isNotEmpty((char *)p->val))
+                label = (char *)p->val;
+            }
+        char suffix[256];
+        safef(suffix, sizeof suffix, " (Coloring by: %s)", label);
+        track->longLabel = catTwoStrings(track->longLabel, suffix);
+        }
+    }
+
 int seqTypeField =  0;
 if (sameString(track->tdb->type, "bigPsl"))
     {
     seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
     }
 
 int mouseOverIdx = bbExtraFieldIndex(bbi, mouseOverField);
 
 track->bbiFile = NULL;
 
 struct bigBedFilter *filters = bigBedBuildFilters(cart, bbi, track->tdb) ;
 struct bigBedFilter *highlights = bigBedBuildHighlights(cart, bbi, track->tdb) ;
 if (compositeChildHideEmptySubtracks(cart, track->tdb, NULL, NULL))
    labelTrackAsHideEmpty(track);
 
@@ -832,30 +857,33 @@
             else
                 {
                 bed = bedLoadN(bedRow, fieldCount == 7 ? 6 : fieldCount);
                 bedCopy = cloneBed(bed);
                 lf = bedMungToLinkedFeatures(&bed, tdb, fieldCount,
                     scoreMin, scoreMax, useItemRgb);
                 }
             }
 
         if (lf && highlights)
             addHighlightToLinkedFeature(lf, highlights, bbi, bedRow, track->tdb);
 
         if (lf && squishFieldIdx)
             lf->squishyPackVal = atof(restField(bb, squishFieldIdx));
 
+        if (lf && colorFieldIdx)
+            lf->filterColor = itemRgbColumn(restField(bb, colorFieldIdx));
+
         if (track->visibility != tvDense && lf && doWindowSizeFilter
             && (quickLiftFile ? lf->start : bb->start) < winStart
             && (quickLiftFile ? lf->end : bb->end) > winEnd)
             {
             mergeCount++;
             struct linkedFeatures *tmp;
             if (quickLiftFile)
                 {
                 struct bed *mergeBed = cloneBed(bedCopy);
                 tmp = bedMungToLinkedFeatures(&mergeBed, tdb, fieldCount,
                     scoreMin, scoreMax, useItemRgb);
                 }
             else
                 {
                 struct bed *bed = bedLoadN(bedRow, fieldCount);