02f9444f186888d2da5d65d9db583ddf2d7b95ca
jcasper
  Thu Oct 22 01:48:20 2020 -0700
Adding trackDb options to change defaults for hic tracks.  Also made some of those
settings case-insensitive.  refs #26312

diff --git src/hg/lib/hicUi.c src/hg/lib/hicUi.c
index d06f07f..485fa2e 100644
--- src/hg/lib/hicUi.c
+++ src/hg/lib/hicUi.c
@@ -1,346 +1,373 @@
 /* hic track controls */
 
 /* Copyright (C) 2019 The Regents of the University of California 
  * See README in this or parent directory for licensing information. */
 
 #include "cheapcgi.h"
 #include "cart.h"
 #include "hui.h"
 #include "web.h"
 #include "trackDb.h"
 #include "hicUi.h"
 #include "Cstraw.h"
 #include "regexHelper.h"
 #include "obscure.h"
 #include "htmshell.h"
+#include "htmlColor.h"
 
 char *hicUiFetchNormalization(struct cart *cart, struct trackDb *tdb, struct hicMeta *meta)
 /* Return the current normalization selection, or the default if none
  * has been selected.  Right now this is a hard-coded set specifically for
  * .hic files, but in the future this list might be dynamically determined by
  * the contents and format of the Hi-C file. */
 {
 char cartVar[1024];
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_NORMALIZATION);
 char *selected = cartNonemptyString(cart, cartVar);
+if (selected == NULL)
+    selected = trackDbSetting(tdb, HIC_TDB_NORMALIZATION);
 char *menu[] = {"NONE", "VC", "VC_SQRT", "KR"};
-int i, sanityCheck = 0;
-for (i=0; i<4; i++)
-    {
-    if (sameOk(selected, menu[i]))
-        sanityCheck = 1;
-    }
-if (!sanityCheck)
+int i;
+char *result = menu[0];
+for (i=1; i<4; i++)
     {
-    selected = menu[0];
+    if (sameWordOk(selected, menu[i]))
+        result = menu[i];
     }
-return selected;
+return result;
 }
 
 void hicUiNormalizationDropDown(struct cart *cart, struct trackDb *tdb, struct hicMeta *meta)
 {
 char cartVar[1024];
 char* selected = hicUiFetchNormalization(cart, tdb, meta);
 char *menu[] = {"NONE", "VC", "VC_SQRT", "KR"};
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_NORMALIZATION);
 cgiMakeDropList(cartVar, menu, 4, selected);
 }
 
 void hicUiNormalizationMenu(struct cart *cart, struct trackDb *tdb, struct hicMeta *meta)
 /* Draw a menu to select the normalization method to use. */
 {
 printf("<b>Score normalization:</b> ");
 hicUiNormalizationDropDown(cart, tdb, meta);
 }
 
 char *hicUiFetchResolution(struct cart *cart, struct trackDb *tdb, struct hicMeta *meta)
 /* Return the current resolution selection, or the default if none
  * has been selected. */
 {
 char cartVar[1024];
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_RESOLUTION);
 char *selected = cartNonemptyString(cart, cartVar);
-int sanityCheck = sameOk(selected, "Auto");
+if (selected == NULL)
+    selected = trackDbSetting(tdb, HIC_TDB_RESOLUTION);
 int i;
+char *result = "Auto";
 for (i=0; i<meta->nRes; i++)
     {
     if (sameOk(selected, meta->resolutions[i]))
-        sanityCheck = 1;
+        result = selected;
     }
-if (!sanityCheck)
-    selected = "Auto";
-return selected;
+return result;
 }
 
 int hicUiFetchResolutionAsInt(struct cart *cart, struct trackDb *tdb, struct hicMeta *meta, int windowSize)
 /* Return the current resolution selection as an integer.  If there is no selection, or if "Auto"
  * has been selected, return the largest available value that still partitions the window into at
  * least 500 bins. */
 {
 char *resolutionString = hicUiFetchResolution(cart, tdb, meta);
 int result;
-if (sameOk(resolutionString, "Auto"))
+if (sameWordOk(resolutionString, "Auto"))
     {
     int idealRes = windowSize/500;
     int autoRes = atoi(meta->resolutions[meta->nRes-1]);
     int smallestRes = autoRes; // in case the ideal resolution is smaller than anything available
     int i, success = 0;
     for (i=meta->nRes-1; i>= 0; i--)
         {
         int thisRes = atoi(meta->resolutions[i]);
         if (thisRes < smallestRes)
             smallestRes = thisRes; // not sure about the sort order of the list
         if (thisRes < idealRes && thisRes >= autoRes)
             {
             autoRes = thisRes;
             success = 1;
             }
         }
     if (success)
         result = autoRes;
     else
         result = smallestRes;
     }
 else
     result = atoi(resolutionString);
 return result;
 }
 
 void hicUiResolutionDropDown(struct cart *cart, struct trackDb *tdb, struct hicMeta *meta)
 {
 char cartVar[1024];
 char autoscale[10] = "Auto";
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_RESOLUTION);
 char **menu = NULL;
 AllocArray(menu, meta->nRes+1);
 char **values = NULL;
 AllocArray(values, meta->nRes+1);
 menu[0] = autoscale;
 values[0] = autoscale;
 int i;
 for (i=1; i<meta->nRes+1; i++)
     {
     char buffer[1024];
     long long value = atoll(meta->resolutions[i-1]);
     sprintWithMetricBaseUnit(buffer, sizeof(buffer), value);
     menu[i] = cloneString(buffer);
     values[i] = cloneString(meta->resolutions[i-1]);
     }
 char *selected = hicUiFetchResolution(cart, tdb, meta);
 cgiMakeDropListWithVals(cartVar, menu, values, meta->nRes+1, selected);
 freeMem(menu);
 }
 
 void hicUiResolutionMenu(struct cart *cart, struct trackDb *tdb, struct hicMeta *meta)
 /* Draw a menu to select which binSize to use for fetching data */
 {
 printf("<b>Resolution:</b> ");
 hicUiResolutionDropDown(cart, tdb, meta);
 }
 
 
 char *hicUiFetchDrawMode(struct cart *cart, struct trackDb *tdb)
 /* Return the current draw mode selection, or the default if none
  * has been selected. */
 {
 char *selected = cartOptionalStringClosestToHome(cart, tdb, FALSE, HIC_DRAW_MODE);
-if (    !sameOk(selected, HIC_DRAW_MODE_SQUARE) &&
-        !sameOk(selected, HIC_DRAW_MODE_ARC) &&
-        !sameOk(selected, HIC_DRAW_MODE_TRIANGLE) )
+if (selected == NULL)
     {
-    selected = HIC_DRAW_MODE_DEFAULT;
+    selected = trackDbSetting(tdb, HIC_TDB_DRAW_MODE);
     }
-return selected;
+char *result = HIC_DRAW_MODE_DEFAULT;
+if (sameWordOk(selected, HIC_DRAW_MODE_SQUARE))
+    result = HIC_DRAW_MODE_SQUARE;
+else if (sameWordOk(selected, HIC_DRAW_MODE_ARC))
+    result = HIC_DRAW_MODE_ARC;
+else if (sameWordOk(selected, HIC_DRAW_MODE_TRIANGLE))
+    result = HIC_DRAW_MODE_TRIANGLE;
+return result;
 }
 
 
 void hicUiDrawMenu(struct cart *cart, struct trackDb *tdb)
 /* Draw the list of draw mode options for Hi-C tracks.  Square is the
  * standard square-shaped heatmap with the chromosome position axis on
  * a diagonal from top left to bottom right.  Triangle is the top half
  * of that square, after rotating it 45 degrees to have the position
  * axis lie horizontally.  Arc draws arcs between related positions,
  * but skips over self-relations. */
 {
 char cartVar[1024];
 printf("<b>Draw mode:</b> ");
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_DRAW_MODE);
 char *menu[] = {HIC_DRAW_MODE_SQUARE, HIC_DRAW_MODE_TRIANGLE, HIC_DRAW_MODE_ARC};
 char* selected = hicUiFetchDrawMode(cart, tdb);
 cgiMakeDropList(cartVar, menu, 3, selected);
 }
 
 
 char *hicUiFetchDrawColor(struct cart *cart, struct trackDb *tdb)
 /* Retrieve the HTML hex code for the color to draw the
  * track values in (e.g., #00ffa1) */
 {
+// Color might have been specified in the cart, probably in #aabbcc format
 char* selected = cartOptionalStringClosestToHome(cart, tdb, FALSE, HIC_DRAW_COLOR);
 if (selected == NULL)
-    selected = HIC_DRAW_COLOR_DEFAULT;
+    // Or color might be in trackDb, probably in R,G,B format
+    selected = trackDbSettingClosestToHomeOrDefault(tdb, HIC_TDB_COLOR, HIC_DRAW_COLOR_DEFAULT);
+const char *commaColorExpr = "^[0-9]+,[0-9]+,[0-9]+$";
+if (regexMatch(selected, commaColorExpr))
+    {
+    // Parse a color in %d,%d,%d format and convert to something the html color tool can use.
+    // Don't want to use pre-parsed trackDb color values just in case string came from the cart instead
+    unsigned char r, g, b;
+    unsigned unifiedColor;
+    parseColor(selected, &r, &g, &b);
+    htmlColorFromRGB(&unifiedColor, r, g, b);
+    char *hexColor = htmlColorToCode(unifiedColor);
+    selected = hexColor;
+    }
 const char *colorExpr ="^#[0-9a-fA-F]{6}$";
 if (!regexMatch(selected, colorExpr))
     {
     selected = HIC_DRAW_COLOR_DEFAULT;
     }
 return selected;
 }
 
 char *hicUiFetchBgColor(struct cart *cart, struct trackDb *tdb)
 /* Retrieve the HTML hex code of the background color for the 
  * track.  This is the color associated with scores at or close to 0. */
 {
 char* selected = cartOptionalStringClosestToHome(cart, tdb, FALSE, HIC_DRAW_BG_COLOR);
 if (selected == NULL)
     selected = HIC_DRAW_BG_COLOR_DEFAULT;
 const char *colorExpr ="^#[0-9a-fA-F]{6}$";
 if (!regexMatch(selected, colorExpr))
     {
     selected = HIC_DRAW_BG_COLOR_DEFAULT;
     }
 return selected;
 }
 
 double hicUiFetchMaxValue(struct cart *cart, struct trackDb *tdb)
 /* Retrieve the score value at which the draw color reaches its
  * its maximum intensity.  Any scores above this value will
  * share that same draw color. */
 {
-return cartUsualDoubleClosestToHome(cart, tdb, FALSE, HIC_DRAW_MAX_VALUE, HIC_DRAW_MAX_VALUE_DEFAULT);
+double defaultValue = HIC_DRAW_MAX_VALUE_DEFAULT;
+char *tdbString = trackDbSetting(tdb, HIC_TDB_MAX_VALUE);
+if (!isEmpty(tdbString))
+    defaultValue = atof(tdbString);
+return cartUsualDoubleClosestToHome(cart, tdb, FALSE, HIC_DRAW_MAX_VALUE, defaultValue);
 }
 
 
 void hicUiAddAutoScaleJS(struct cart *cart, char *track)
 /* Write out a bit of javascript to associate checking/unchecking the autoscale
  * checkbox with deactivating/activating (respectively) the maximum score
  * input.  */
 {
 struct dyString *new = dyStringNew(0);
 dyStringPrintf(new, "$('input[name=\"%s.%s\"]')[0].onclick = function() {", track, HIC_DRAW_AUTOSCALE);
 dyStringPrintf(new, "if (this.checked) {$('input[name=\"%s.%s\"]')[0].disabled = true; $('span#hicMaxText').css('color', 'gray');}", track, HIC_DRAW_MAX_VALUE);
 dyStringPrintf(new, "else {$('input[name=\"%s.%s\"]')[0].disabled = false; $('span#hicMaxText').css('color', '');} };\n", track, HIC_DRAW_MAX_VALUE);
 dyStringPrintf(new, "if ($('input[name=\"%s.%s\"]')[0].checked) {$('input[name=\"%s.%s\"]')[0].disabled = true; $('span#hicMaxText').css('color', 'gray');}\n",
         track, HIC_DRAW_AUTOSCALE, track, HIC_DRAW_MAX_VALUE);
 jsInline(dyStringContents(new));
 dyStringFree(&new);
 }
 
 boolean hicUiFetchAutoScale(struct cart *cart, struct trackDb *tdb)
 /* Returns whether the track is configured to automatically scale its color range
  * depending on the scores present in the window, or if it should stick to a
  * gradient based on the user's selected maximum value. */
 {
-return cartUsualBooleanClosestToHome(cart, tdb, FALSE, HIC_DRAW_AUTOSCALE, TRUE);
+boolean defaultVal = TRUE;
+char *tdbSetting = trackDbSetting(tdb, HIC_TDB_AUTOSCALE);
+if (tdbSetting != NULL)
+    defaultVal = trackDbSettingClosestToHomeOn(tdb, HIC_TDB_AUTOSCALE);
+return cartUsualBooleanClosestToHome(cart, tdb, FALSE, HIC_DRAW_AUTOSCALE, defaultVal);
 }
 
 
 void hicUiColorMenu(struct cart *cart, struct trackDb *tdb)
 /* Draw the menu inputs associated with selecting draw colors for the track. */
 {
 char cartVar[1024];
 printf("<b>Color:</b> ");
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_DRAW_COLOR);
 char* selected = hicUiFetchDrawColor(cart, tdb);
 printf("<input type='color' name='%s' value='%s' />\n", cartVar, selected);
 
 // Leaving out background color options for now.  We'll see if this option is requested.
 /*
 printf("<b>Background color:</b> ");
 safef(cartVar, sizeof(cartVar), "%s.%s", track, HIC_DRAW_BG_COLOR);
 selected = hicUiFetchBgColor(cart, track);
 printf("<input type='color' name='%s' value='%s' />\n", cartVar, selected);
 */
 }
 
 void hicUiMaxOptionsMenu(struct cart *cart, struct trackDb *tdb, boolean isComposite)
 /* Draw the menu inputs associated with selecting whether the track should automatically
  * scale its color gradient based on the scores present in the view window, or whether it
  * should stick to a gradient based on a user-selected maximum score. */
 {
 char cartVar[1024];
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_DRAW_MAX_VALUE);
 double currentMax = hicUiFetchMaxValue(cart, tdb);
 printf("<span id='hicMaxText'><b>%sMaximum:</b></span> ", isComposite ? "Score " : "");
 cgiMakeDoubleVar(cartVar, currentMax, 6);
 
 printf("&nbsp;&nbsp;<b>Auto-scale:</b> ");
 safef(cartVar, sizeof(cartVar), "%s.%s", tdb->track, HIC_DRAW_AUTOSCALE);
 boolean autoscaleChecked = hicUiFetchAutoScale(cart, tdb);
 cgiMakeCheckBox(cartVar, autoscaleChecked);
 
 hicUiAddAutoScaleJS(cart, tdb->track);
 }
 
 
 
 void hicUiFileDetails(struct hicMeta *trackMeta)
 {
 int i;
 printf("</p><hr>\nMetadata from file header:<br>\n");
 printf("<div style='margin-left: 2em'>\n");
 printf("<label class='trackUiHicLabel'>Genome: %s\n<br></label>", trackMeta->fileAssembly);
 char scriptline[2048];
 for (i=0; i<trackMeta->nAttributes-1; i+=2)
     {
     printf("<label class='trackUiHicLabelExpand trackUiHicAttrToggle%d'>%s <img src='%s' class='trackUiHicLabelArrow'></label><br>", i, htmlEncode(trackMeta->attributes[i]), "../images/ab_right.gif");
     printf("<div class='trackUiHicHiddenAttributes hicAttr%d'>\n", i);
     printf("<pre>%s</pre>", htmlEncode(trackMeta->attributes[i+1]));
     printf("</div>\n");
     
     safef(scriptline, sizeof(scriptline), "$('label.trackUiHicAttrToggle%d').click(function() {$(this).children('img').toggleClass('open'); $('div.hicAttr%d').toggle();});", i, i);
     jsInline(scriptline);
     }
 printf("</div>\n");
 printf("<p>For questions concerning the content of a file's metadata header, please contact the file creator.</p>\n");
 }
 
 void hicCfgUi(char *database, struct cart *cart, struct trackDb *tdb, char *track,
                         char *title, boolean boxed)
 /* Draw the list of track configuration options for Hi-C tracks */
 {
 
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 char *filename = trackDbSetting(tdb, "bigDataUrl");
 if (filename == NULL)
     errAbort("Error: no bigDataUrl provided in trackDb for hic track %s", tdb->track);
 struct hicMeta *trackMeta;
 char *errMsg = hicLoadHeader(filename, &trackMeta, database);
 if (errMsg != NULL)
     errAbort("Error retrieving file info: %s", errMsg);
 
 puts("<p>");
 printf("Items are drawn in shades of the chosen color depending on score - scores above the "
         "chosen maximum are drawn at full intensity.</p><p>\n");
 hicUiNormalizationMenu(cart, tdb, trackMeta);
 puts("&nbsp;&nbsp;");
 hicUiMaxOptionsMenu(cart, tdb, FALSE);
 
 puts("</p><p>");
 hicUiDrawMenu(cart, tdb);
 puts("&nbsp;&nbsp;");
 hicUiResolutionMenu(cart, tdb, trackMeta);
 puts("&nbsp;&nbsp;");
 hicUiColorMenu(cart, tdb);
 puts("</p><p>\n");
 hicUiFileDetails(trackMeta);
 cfgEndBox(boxed);
 }
 
 void hicCfgUiComposite(struct cart *cart, struct trackDb *tdb, char *track,
                         char *title, boolean boxed)
 /* Draw the (empty) list of track configuration options for a composite of Hi-C tracks */
 {
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 puts("<p>");
 printf("Items are drawn in shades of the chosen color depending on score - scores above the "
         "chosen maximum are drawn at full intensity.</p><p>\n");
 hicUiMaxOptionsMenu(cart, tdb, TRUE);
 puts("</p><p>");
 hicUiDrawMenu(cart, tdb);
 puts("&nbsp;&nbsp;");
 hicUiColorMenu(cart, tdb);
 puts("</p><p>\n");
 puts("Subtracks below have additional file-specific configuration options for resolution and normalization.\n</p>");
 cfgEndBox(boxed);
 }