733ba7b804e84d7b60ccad1f137398ecd52db983
chmalee
  Tue Apr 23 18:15:09 2024 -0700
Add a general highlight trackDb variable(s), working like trackDb filters, except put a color behind the item, refs #24507

diff --git src/hg/hgTracks/bigBedTrack.c src/hg/hgTracks/bigBedTrack.c
index 5ea2890..2d16790 100644
--- src/hg/hgTracks/bigBedTrack.c
+++ src/hg/hgTracks/bigBedTrack.c
@@ -31,32 +31,32 @@
 #include "quickLift.h"
 #include "hgConfig.h"
 
 static unsigned getFieldNum(struct bbiFile *bbi, char *field)
 // get field number for field name in bigBed.  errAbort if field not found.
 {
 int fieldNum =  bbFieldIndex(bbi, field);
 if (fieldNum < 0)
     fieldNum = defaultFieldLocation(field);
 if (fieldNum < 0)
     errAbort("error building filter with field %s.  Field not found.", field);
 
 return fieldNum;
 }
 
-struct bigBedFilter *bigBedMakeNumberFilter(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *filter, char *defaultLimits,  char *field)
-/* Make a filter on this column if the trackDb or cart wants us to. */
+struct bigBedFilter *bigBedMakeNumberFilter(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *filter, char *defaultLimits,  char *field, boolean isHighlight)
+/* Make a filter/highlight on this column if the trackDb or cart wants us to. */
 {
 struct bigBedFilter *ret = NULL;
 char *setting = trackDbSettingClosestToHome(tdb, filter);
 int fieldNum =  getFieldNum(bbi, field);
 if (setting)
     {
     boolean invalid = FALSE;
     double minValueTdb = 0,maxValueTdb = NO_VALUE;
     double minLimit=NO_VALUE,maxLimit=NO_VALUE,min = minValueTdb,max = maxValueTdb;
     colonPairToDoubles(setting,&minValueTdb,&maxValueTdb);
     colonPairToDoubles(defaultLimits,&minLimit,&maxLimit);
     getScoreFloatRangeFromCart(cart,tdb,FALSE,filter,&minLimit,&maxLimit,&min,&max);
     if ((int)minLimit != NO_VALUE || (int)maxLimit != NO_VALUE)
         {
         // assume tdb default values within range!
@@ -101,89 +101,121 @@
             {
             ret->comparisonType = COMPARE_MORE;
             ret->value1 = min;
             }
         else if ((int)min == NO_VALUE || ((int)minLimit != NO_VALUE && minLimit == min))
             {
             ret->comparisonType = COMPARE_LESS;
             ret->value1 = max;
             }
         else
             {
             ret->comparisonType = COMPARE_BETWEEN;
             ret->value1 = min;
             ret->value2 = max;
             }
+        if (isHighlight)
+            ret->isHighlight = TRUE;
         }
     }
 return ret;
 }
 
-struct bigBedFilter *bigBedMakeFilterText(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *filterName, char *field)
+struct bigBedFilter *bigBedMakeFilterText(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *filterName, char *field, boolean isHighlight)
 /* Add a bigBed filter using a trackDb filterText statement. */
 {
 struct bigBedFilter *filter;
 char *setting = trackDbSettingClosestToHome(tdb, filterName);
 char *value = cartUsualStringClosestToHome(cart, tdb, FALSE, filterName, setting);
 
 if (isEmpty(value)) 
     return NULL;
 
 char *typeValue = getFilterType(cart, tdb, field, FILTERTEXT_WILDCARD);
 
 AllocVar(filter);
 filter->fieldNum =  getFieldNum(bbi, field);
 
 if (sameString(typeValue, FILTERTEXT_REGEXP) )
     {
     filter->comparisonType = COMPARE_REGEXP;
     regcomp(&filter->regEx, value, REG_NOSUB);
     }
 else
     {
     filter->comparisonType = COMPARE_WILDCARD;
     filter->wildCardString = cloneString(value);
     }
 
+filter->isHighlight = isHighlight;
+
 return filter;
 }
 
-struct bigBedFilter *bigBedMakeFilterBy(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *field, struct slName *choices)
+char *getHighlightType(struct cart *cart, struct trackDb *tdb, char *field, char *def)
+{
+char settingString[4096];
+safef(settingString, sizeof settingString, "%s.%s", HIGHLIGHT_TYPE_NAME_LOW, field);
+char *setting = cartOrTdbString(cart, tdb, settingString, NULL);
+if (setting == NULL)
+    {
+    safef(settingString, sizeof settingString, "%s.%s", field, HIGHLIGHT_TYPE_NAME_CAP);
+    setting = cartOrTdbString(cart, tdb, settingString, NULL);
+    }
+if (setting == NULL)
+    {
+    safef(settingString, sizeof settingString, "%s%s", field, HIGHLIGHT_TYPE_NAME_CAP);
+    setting = cartOrTdbString(cart, tdb, settingString, def);
+    }
+return setting;
+}
+
+struct bigBedFilter *bigBedMakeFilterBy(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *field, struct slName *choices, boolean isHighlight)
 /* Add a bigBed filter using a trackDb filterBy statement. */
 {
 struct bigBedFilter *filter;
-char *setting = getFilterType(cart, tdb, field,  FILTERBY_DEFAULT);
+char *setting = NULL;
+if (isHighlight)
+    setting = getHighlightType(cart, tdb, field, HIGHLIGHTBY_DEFAULT);
+else
+setting = getFilterType(cart, tdb, field,  FILTERBY_DEFAULT);
 
 AllocVar(filter);
 filter->fieldNum =  getFieldNum(bbi, field);
 filter->comparisonType = COMPARE_HASH;
 if (setting) 
     {
     if (sameString(setting, FILTERBY_SINGLE_LIST) 
             || sameString(setting, FILTERBY_MULTIPLE_LIST_OR)
-            || sameString(setting, FILTERBY_MULTIPLE_LIST_ONLY_OR))
+            || sameString(setting, FILTERBY_MULTIPLE_LIST_ONLY_OR)
+            || sameString(setting, HIGHLIGHTBY_SINGLE_LIST)
+            || sameString(setting, HIGHLIGHTBY_MULTIPLE_LIST_OR)
+            || sameString(setting, HIGHLIGHTBY_MULTIPLE_LIST_ONLY_OR))
                 filter->comparisonType = COMPARE_HASH_LIST_OR;
     else if (sameString(setting, FILTERBY_MULTIPLE_LIST_AND) 
-            || sameString(setting, FILTERBY_MULTIPLE_LIST_ONLY_AND))
+            || sameString(setting, FILTERBY_MULTIPLE_LIST_ONLY_AND)
+            || sameString(setting, HIGHLIGHTBY_MULTIPLE_LIST_AND)
+            || sameString(setting, HIGHLIGHTBY_MULTIPLE_LIST_ONLY_AND))
                 filter->comparisonType = COMPARE_HASH_LIST_AND;
     }
 filter->valueHash = newHash(5);
 filter->numValuesInHash = slCount(choices);
 
 for(; choices; choices = choices->next)
     hashStore(filter->valueHash, choices->name);
 
+filter->isHighlight = isHighlight;
 return filter;
 }
 
 static void addGencodeFilters(struct cart *cart, struct trackDb *tdb, struct bigBedFilter **pFilters)
 /* Add GENCODE custom bigBed filters. */
 {
 struct bigBedFilter *filter;
 char varName[64];
 struct hash *hash;
 
 /* canonical */
 safef(varName, sizeof(varName), "%s.show.spliceVariants", tdb->track);
 boolean option = cartUsualBoolean(cart, varName, TRUE);
 if (!option)
     {
@@ -245,73 +277,103 @@
 struct trackDbFilter *tdbFilters = tdbGetTrackNumFilters(tdb);
 
 if ((tdbFilters == NULL) && !trackDbSettingOn(tdb, "noScoreFilter") && (bbi->definedFieldCount >= 5))
     {
     AllocVar(filter);
     slAddHead(&filters, filter);
     filter->fieldNum = 4;
     filter->comparisonType = COMPARE_MORE;
     char buffer[2048];
     safef(buffer, sizeof buffer, "%s.scoreFilter", tdb->track);
     filter->value1 = cartUsualDouble(cart, buffer, 0.0);
     }
 
 for(; tdbFilters; tdbFilters = tdbFilters->next)
     {
-    if ((filter = bigBedMakeNumberFilter(cart, bbi, tdb, tdbFilters->name, NULL, tdbFilters->fieldName)) != NULL)
+    if ((filter = bigBedMakeNumberFilter(cart, bbi, tdb, tdbFilters->name, NULL, tdbFilters->fieldName, FALSE)) != NULL)
         slAddHead(&filters, filter);
     }
 
 tdbFilters = tdbGetTrackTextFilters(tdb);
 
 for(; tdbFilters; tdbFilters = tdbFilters->next)
     {
-    if ((filter = bigBedMakeFilterText(cart, bbi, tdb, tdbFilters->name,  tdbFilters->fieldName)) != NULL)
+    if ((filter = bigBedMakeFilterText(cart, bbi, tdb, tdbFilters->name,  tdbFilters->fieldName, FALSE)) != NULL)
         slAddHead(&filters, filter);
     }
 
 filterBy_t *filterBySet = filterBySetGet(tdb, cart,NULL);
 filterBy_t *filterBy = filterBySet;
 for (;filterBy != NULL; filterBy = filterBy->next)
     {
     if (filterBy->slChoices && differentString(filterBy->slChoices->name, "All")) 
         {
-        if ((filter = bigBedMakeFilterBy(cart, bbi, tdb, filterBy->column, filterBy->slChoices)) != NULL)
+        if ((filter = bigBedMakeFilterBy(cart, bbi, tdb, filterBy->column, filterBy->slChoices, FALSE)) != NULL)
             slAddHead(&filters, filter);
         }
     }
 
 /* custom gencode filters */
 boolean isGencode3 = trackDbSettingOn(tdb, "isGencode3");
 
 if (isGencode3)
     addGencodeFilters(cart, tdb, &filters);
 
 return filters;
 }
 
+struct bigBedFilter *bigBedBuildHighlights(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb)
+/* Build all the numeric and highlights for a bigBed */
+{
+struct bigBedFilter *highlights = NULL, *highlight;
+struct trackDbFilter *tdbHighlights = tdbGetTrackNumHighlights(tdb);
 
-boolean bigBedFilterInterval(struct bbiFile *bbi, char **bedRow, struct bigBedFilter *filters)
-/* Go through a row and filter based on filters.  Return TRUE if all filters are passed. */
+for(; tdbHighlights; tdbHighlights = tdbHighlights->next)
+    {
+    if ((highlight = bigBedMakeNumberFilter(cart, bbi, tdb, tdbHighlights->name, NULL, tdbHighlights->fieldName, TRUE)) != NULL)
+        slAddHead(&highlights, highlight);
+    }
+
+tdbHighlights = tdbGetTrackTextHighlights(tdb);
+
+for(; tdbHighlights; tdbHighlights = tdbHighlights->next)
+    {
+    if ((highlight = bigBedMakeFilterText(cart, bbi, tdb, tdbHighlights->name,  tdbHighlights->fieldName, TRUE)) != NULL)
+        slAddHead(&highlights, highlight);
+    }
+
+filterBy_t *filterBySet = highlightBySetGet(tdb, cart,NULL);
+filterBy_t *filterBy = filterBySet;
+for (;filterBy != NULL; filterBy = filterBy->next)
+    {
+    if (filterBy->slChoices && differentString(filterBy->slChoices->name, "All"))
+        {
+        if ((highlight = bigBedMakeFilterBy(cart, bbi, tdb, filterBy->column, filterBy->slChoices, TRUE)) != NULL)
+            slAddHead(&highlights, highlight);
+        }
+    }
+
+return highlights;
+}
+
+boolean bigBedFilterOne(struct bigBedFilter *filter, char **bedRow, struct bbiFile *bbi)
+/* Return TRUE if a bedRow passes one filter or is in hgFindMatches */
 {
 if ((bbi->definedFieldCount > 3) && (hgFindMatches != NULL) && 
     (bedRow[3] != NULL)  && hashLookup(hgFindMatches, bedRow[3]) != NULL)
     return TRUE;
 
-struct bigBedFilter *filter;
-for(filter = filters; filter; filter = filter->next)
-    {
 double val = atof(bedRow[filter->fieldNum]);
 
 switch(filter->comparisonType)
     {
     case COMPARE_WILDCARD:
         if ( !wildMatch(filter->wildCardString, bedRow[filter->fieldNum]))
             return FALSE;
         break;
     case COMPARE_REGEXP:
         if (regexec(&filter->regEx,bedRow[filter->fieldNum], 0, NULL,0 ) != 0)
             return FALSE;
         break;
     case COMPARE_HASH_LIST_AND:
     case COMPARE_HASH_LIST_OR:
         {
@@ -345,31 +407,45 @@
             return FALSE;
         break;
     case COMPARE_LESS:
         if (!(val <= filter->value1))
             return FALSE;
         break;
     case COMPARE_MORE:
         if (!(val >= filter->value1))
             return FALSE;
         break;
     case COMPARE_BETWEEN:
         if (!((val >= filter->value1) && (val <= filter->value2)))
             return FALSE;
         break;
     }
+return TRUE;
 }
+
+
+boolean bigBedFilterInterval(struct bbiFile *bbi, char **bedRow, struct bigBedFilter *filters)
+/* Go through a row and filter based on filters.  Return TRUE if all filters are passed. */
+{
+if ((bbi->definedFieldCount > 3) && (hgFindMatches != NULL) && 
+    (bedRow[3] != NULL)  && hashLookup(hgFindMatches, bedRow[3]) != NULL)
+    return TRUE;
+
+struct bigBedFilter *filter;
+for(filter = filters; filter; filter = filter->next)
+    if (!bigBedFilterOne(filter, bedRow, bbi))
+        return FALSE;
 return TRUE;
 }
 
 
 struct bbiFile *fetchBbiForTrack(struct track *track)
 /* Fetch bbiFile from track, opening it if it is not already open. */
 {
 struct bbiFile *bbi = track->bbiFile;
 if (bbi == NULL)
     {
     char *fileName = NULL;
     if (track->parallelLoading) // do not use mysql during parallel fetch
 	{
 	fileName = cloneString(trackDbSetting(track->tdb, "bigDataUrl"));
         if (fileName == NULL)
@@ -477,30 +553,47 @@
 char* restField(struct bigBedInterval *bb, int fieldIdx) 
 /* return a given field from the bb->rest field, NULL on error */
 {
 if (fieldIdx==0) // we don't return the first(=name) field of bigBed
     return NULL;
 char *rest = cloneString(bb->rest);
 char *restFields[1024];
 int restCount = chopTabs(rest, restFields);
 char *field = NULL;
 if (fieldIdx < restCount)
     field = cloneString(restFields[fieldIdx]);
 freeMem(rest);
 return field;
 }
 
+void addHighlightToLinkedFeature(struct linkedFeatures *lf, struct bigBedFilter *highlights, struct bbiFile *bbi, char **bedRow, struct trackDb *tdb)
+/* Fill out the lf->highlightColor if the cart says to highlight this item. The color will
+ * be the 'average' of all the highlight colors specified */
+{
+struct bigBedFilter *highlight;
+char *cartHighlightColor = cartOrTdbString(cart, tdb, HIGHLIGHT_COLOR_CART_VAR, HIGHLIGHT_COLOR_DEFAULT);
+for (highlight = highlights; highlight != NULL; highlight = highlight->next)
+    {
+    if (bigBedFilterOne(highlight, bedRow, bbi))
+        {
+        unsigned rgb = bedParseColor(cartHighlightColor);
+        Color color = bedColorToGfxColor(rgb);
+        lf->highlightColor = color;
+        lf->highlightMode = highlightBackground;
+        }
+    }
+}
 
 void bigBedAddLinkedFeaturesFromExt(struct track *track,
 	char *chrom, int start, int end, int scoreMin, int scoreMax, boolean useItemRgb,
 	int fieldCount, struct linkedFeatures **pLfList, int maxItems)
 /* Read in items in chrom:start-end from bigBed file named in track->bbiFileName, convert
  * them to linkedFeatures, and add to head of list. */
 {
 struct lm *lm = lmInit(0);
 struct trackDb *tdb = track->tdb;
 char *mouseOverField = cartOrTdbString(cart, track->tdb, "mouseOverField", NULL);
 
 // check if this track can merge large items, this setting must be allowed in the trackDb
 // stanza for the track, but can be enabled/disabled via trackUi/right click menu so
 // we also need to check the cart for the current status
 int mergeCount = 0;
@@ -541,30 +634,31 @@
 
 char *squishField = cartOrTdbString(cart, track->tdb, "squishyPackField", NULL);
 int squishFieldIdx = bbExtraFieldIndex(bbi, squishField);
 
 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);
 
 // mouseOvers can be built constructed via trackDb settings instead
 // of embedded directly in bigBed
 char *mouseOverPattern = NULL;
 char **fieldNames = NULL;
 if (!mouseOverIdx)
     {
     mouseOverPattern = cartOrTdbString(cart, track->tdb, "mouseOver", NULL);
 
     if (mouseOverPattern)
         {
         AllocArray(fieldNames, bbi->fieldCount);
         struct slName *field = NULL, *fields = bbFieldNames(bbi);
@@ -622,30 +716,33 @@
                     {
                     bedCopy = cloneBed(bed);
                     lf = bedMungToLinkedFeatures(&bed, tdb, fieldCount,
                         scoreMin, scoreMax, useItemRgb);
                     }
                 }
             else
                 {
                 bed = bedLoadN(bedRow, 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 (track->visibility != tvDense && lf && doWindowSizeFilter && bb->start < winStart && bb->end > winEnd)
             {
             mergeCount++;
             struct bed *bed = bedLoadN(bedRow, fieldCount);
             struct linkedFeatures *tmp = bedMungToLinkedFeatures(&bed, tdb, fieldCount,
                 scoreMin, scoreMax, useItemRgb);
             if (spannedLf)
                 {
                 if (tmp->start < spannedLf->start)
                     spannedLf->start = tmp->start;
                 if (tmp->end > spannedLf->end)
                     spannedLf->end = tmp->end;