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/lib/hui.c src/hg/lib/hui.c index 61a1597fe41..7d80316bbb5 100644 --- src/hg/lib/hui.c +++ src/hg/lib/hui.c @@ -7423,30 +7423,107 @@ boolean option = cartUsualBoolean(cart, varName, isDefault); cgiMakeCheckBox(varName, option); // find comment for the column listed struct asColumn *col = as->columnList; unsigned num = ptToInt(thisLabel->val); for(; col && num--; col = col->next) ; assert(col); printf(" %s ", col->comment); } } } +static char *colorFieldLabel(struct slPair *p) +/* Derive a display label for one colorFields entry. + * Uses the explicit ="label" if provided, otherwise strips a leading "colorBy" + * prefix and replaces underscores with spaces. */ +{ +if (isNotEmpty((char *)p->val)) + return (char *)p->val; +char *lbl = p->name; +if (startsWith("colorBy", lbl)) + lbl += strlen("colorBy"); +char *derived = cloneString(lbl); +subChar(derived, '_', ' '); +return derived; +} + +void colorFieldsCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *prefix) +/* If trackDb declares colorFields, render a "Color by:" dropdown to choose among color schemes. + * + * colorFields is a space-separated list of fieldName[="Human Label"] pairs: + * colorFields default="Evidence type" colorByHlaClass="HLA class" colorByKozak="Kozak strength" + * + * The special name "default" (with optional label) represents the standard itemRgb field + * (col 9). When no default= entry is present, "Default" is added implicitly as the first + * option. Any other name must be an extra bigBed field containing a pre-computed R,G,B string. + * + * Cart variable: <prefix>.colorField — empty string means use field 9. */ +{ +if (tdbIsComposite(tdb)) + return; +char *setting = trackDbSettingClosestToHome(tdb, "colorFields"); +if (setting == NULL) + return; + +struct slPair *pairs = slPairListFromString(setting, TRUE); +if (pairs == NULL) + return; + +/* Cart variable; empty string = use field 9. */ +char cartVar[1024]; +safef(cartVar, sizeof cartVar, "%s.colorField", prefix); +char *current = cartUsualString(cart, cartVar, ""); + +/* Two-pass build of label/value arrays. + * Pass 1: check whether the user supplied an explicit default= entry. + * Pass 2: populate the arrays, putting the "use field 9" option first. */ +char *defaultLabel = "Default"; +struct slPair *p; +int nAlternatives = 0; +for (p = pairs; p != NULL; p = p->next) + { + if (sameString(p->name, "default")) + defaultLabel = colorFieldLabel(p); + else + nAlternatives++; + } + +int nOptions = 1 + nAlternatives; /* slot 0 = field-9 default + one per alternative */ +char **labels = needMem(nOptions * sizeof(char *)); +char **values = needMem(nOptions * sizeof(char *)); +labels[0] = defaultLabel; +values[0] = ""; + +int i = 1; +for (p = pairs; p != NULL; p = p->next) + { + if (sameString(p->name, "default")) + continue; + values[i] = p->name; + labels[i] = colorFieldLabel(p); + i++; + } + +printf("<B>Color by:</B> "); +cgiMakeDropListFull(cartVar, labels, values, nOptions, current, NULL, NULL); +printf("<BR>\n"); +} + void mergeSpanCfgUi(struct cart *cart, struct trackDb *tdb, char *prefix) /* If this track offers a merge spanned items option, put up the cfg for it, which * is just a checkbox with a small explanation. Comparing tdb->track to prefix * ensures we don't offer this control at the composite level, as this is a * subtrack only config */ { if (trackDbSettingOn(tdb, MERGESPAN_TDB_SETTING) && sameString(tdb->track, prefix)) { boolean curOpt = trackDbSettingOn(tdb, "mergeSpannedItems"); char mergeSetting[256]; safef(mergeSetting, sizeof(mergeSetting), "%s.%s", tdb->track, MERGESPAN_CART_SETTING); if (cartVarExists(cart, mergeSetting)) curOpt = cartBoolean(cart, mergeSetting); printf("<b>Merge items that span the current region</b>:"); cgiMakeCheckBox(mergeSetting, curOpt); @@ -7918,47 +7995,49 @@ cgiMakeRadioButton(varName, "MANE_Select", sameString(setString, "MANE_Select")); printf(" %s ", "MANE only"); cgiMakeRadioButton(varName, "basic", sameString(setString, "basic")); printf(" %s ", "BASIC only"); cgiMakeRadioButton(varName, "all", sameString(setString, "all")); printf(" %s ", "All"); } void bedScoreCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *name, char *title, boolean boxed) /* Put up bed-specific score controls */ { char *scoreMax = trackDbSettingClosestToHome(tdb, SCORE_FILTER _MAX); int maxScore = (scoreMax ? sqlUnsigned(scoreMax):1000); scoreCfgUi(db, cart,tdb,name,title,maxScore,boxed); -if(startsWith("bigBed", tdb->type)) +if(tdbIsBigBed(tdb)) { labelCfgUi(db, cart, tdb, name); + colorFieldsCfgUi(db, cart, tdb, name); mergeSpanCfgUi(cart, tdb, name); wigOption(cart, name, title, tdb); } } void genePredCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *name, char *title, boolean boxed) /* Put up genePred-specific controls */ { char varName[64]; boolean parentLevel = isNameAtParentLevel(tdb,name); char *geneLabel = cartUsualStringClosestToHome(cart, tdb,parentLevel, "label", "gene"); boxed = cfgBeginBoxAndTitle(tdb, boxed, title); labelCfgUi(db, cart, tdb, name); +colorFieldsCfgUi(db, cart, tdb, name); boolean isGencode3 = trackDbSettingOn(tdb, "isGencode3"); if (sameString(name, "acembly")) { char *acemblyClass = cartUsualStringClosestToHome(cart,tdb,parentLevel,"type", acemblyEnumToString(0)); printf("<p><b>Gene Class: </b>"); acemblyDropDown("acembly.type", acemblyClass); printf(" "); } else if (isGencode3) { newGencodeShowOptions(cart, tdb); } else if (startsWith("wgEncodeGencode", name))