a4adfa326bf8bec73020ee1a8aa0cb6251ea8663
tdreszer
  Thu Jul 24 11:20:39 2014 -0700
Checking in changes to make 'spectrum' (aka 'useScore') actually work on any pair of color/altColor settings.  It used to be that only shadesOfGray, shadesOfBrown and shadesOfSea worked, leaving most cases to be shadesOfGray.  I have sat on these changes for months.  I am also checking in ifdef'd out code to calc the shade on the fly, rather than relaying on track->colorShades set of precomputed 10 shades.  The only reason this code is not used is that it would require changing many many places/uses of colorShades.  Since I am bein laid off, it seems inappropriate to make this more dramatic change at this time.
diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c
index 26abd58..7a32cdf 100644
--- src/hg/hgTracks/simpleTracks.c
+++ src/hg/hgTracks/simpleTracks.c
@@ -2414,32 +2414,32 @@
 		hvGfxFindColorIx(hvg, itemRgb.r, itemRgb.g, itemRgb.b);
 	}
     else
 	*retColor = *retBarbColor = lf->filterColor;
     }
 else if (tg->itemColor)
     {
     *retColor = tg->itemColor(tg, lf, hvg);
     *retBarbColor = tg->ixAltColor;
     }
 else if (tg->colorShades)
     {
     boolean isXeno = (tg->subType == lfSubXeno)
                                 || (tg->subType == lfSubChain)
                                 || startsWith("mrnaBla", tg->table);
-    *retColor =  tg->colorShades[lf->grayIx+isXeno];
-    *retBarbColor =  tg->colorShades[lf->grayIx];
+    *retColor     = colorBySpectrumOrDefault(hvg,tg,lf->grayIx+isXeno,*retColor);
+    *retBarbColor = colorBySpectrumOrDefault(hvg,tg,lf->grayIx,       *retBarbColor);
     }
 else
     {
     *retColor = tg->ixColor;
     *retBarbColor = tg->ixAltColor;
     }
 }
 
 Color linkedFeaturesNameColor(struct track *tg, void *item, struct hvGfx *hvg)
 /* Determine the color of the name for the linked feature. */
 {
 Color col, barbCol;
 lfColors(tg, item, hvg, &col, &barbCol);
 return col;
 }
@@ -5063,35 +5063,35 @@
 tg->drawItemAt  = gadDrawAt;
 tg->mapItem     = bedPlusLabelMapItem;
 tg->nextPrevExon = simpleBedNextPrevEdge;
 }
 
 void rgdQtlDrawAt(struct track *tg, void *item,
                   struct hvGfx *hvg, int xOff, int y,
                   double scale, MgFont *font, Color color, enum trackVisibility vis)
 /* Draw a single rgdQtl item at position. */
 {
 struct bed *bed = item;
 struct trackDb *tdb = tg->tdb;
 
 if (tg->itemColor != NULL)
     color = tg->itemColor(tg, bed, hvg);
-else if (tg->colorShades)
+else
     {
     int scoreMin = atoi(trackDbSettingClosestToHomeOrDefault(tdb, "scoreMin", "0"));
     int scoreMax = atoi(trackDbSettingClosestToHomeOrDefault(tdb, "scoreMax", "1000"));
-    color = tg->colorShades[grayInRange(bed->score, scoreMin, scoreMax)];
+    color = colorBySpectrumOrDefault(hvg, tg, grayInRange(bed->score, scoreMin, scoreMax),color);
     }
 if (color)
     {
     int heightPer = tg->heightPer;
     int s = max(bed->chromStart, winStart), e = min(bed->chromEnd, winEnd);
     if (s > e)
 	return;
     int x1 = round((s-winStart)*scale) + xOff;
     int x2 = round((e-winStart)*scale) + xOff;
     int w = x2 - x1;
     if (w < 1)
 	w = 1;
     hvGfxBox(hvg, x1, y, w, heightPer, color);
     if (tg->drawName && vis != tvSquish)
 	{
@@ -6179,34 +6179,31 @@
 //boolean thickDrawItem = (trackDbSetting(tdb, "thickDrawItem") != NULL);
 int colors[8] =
 {
 orangeColor,
 greenColor,
 blueColor,
 brickColor,
 darkBlueColor,
 darkGreenColor,
 1,
 MG_RED} ;
 
 if (tg->itemColor != NULL)
     color = tg->itemColor(tg, bed, hvg);
 else
-    {
-    if (tg->colorShades)
-	color = tg->colorShades[grayInRange(bed->score, scoreMin, scoreMax)];
-    }
+    color = colorBySpectrumOrDefault(hvg,tg,grayInRange(bed->score, scoreMin, scoreMax),color);
 
 w = x2-x1;
 if (w < 1)
     w = 1;
 //if (color)
     {
     int ii;
 
     for(ii=0; ii < 8; ii++)
 	{
 	if (bed->score & (1 << ii))
 	    hvGfxBox(hvg, x1, y+(ii*6), w, 6, colors[ii]);
 	}
     if (tg->drawName && vis != tvSquish)
 	{
@@ -7522,31 +7519,31 @@
 {
 return getSeqColorDefault(seqName, hvg, chromColor[0]);
 }
 
 Color lfChromColor(struct track *tg, void *item, struct hvGfx *hvg)
 /* Return color of chromosome for linked feature type items
  * where the chromosome is listed somewhere in the lf->name. */
 {
 struct linkedFeatures *lf = item;
 return getSeqColorDefault(lf->name, hvg, tg->ixColor);
 }
 
 Color interactionColor(struct track *tg, void *item, struct hvGfx *hvg)
 {
 struct linkedFeatures *lf = item;
-return  tg->colorShades[lf->grayIx];
+return colorBySpectrumOrDefault(hvg,tg,lf->grayIx,hvGfxFindRgb(hvg, &tg->color));
 
 #ifdef NOTNOW  // leaving this in the code in case we want chrom color again
 
 char *name = tg->itemName(tg, item);
 struct linkedFeatures *lf = item;
 if (slCount(lf->components) == 2)
     return MG_BLACK;
 return getSeqColorDefault(name, hvg, tg->ixColor);
 #endif
 }
 
 void interactionLeftLabels(struct track *tg, int seqStart, int seqEnd,
 	struct hvGfx *hvg, int xOff, int yOff, int width, int height,
 	boolean withCenterLabels, MgFont *font, Color color,
 	enum trackVisibility vis)
@@ -9175,30 +9172,125 @@
     if (!subtrack->limitedVisSet)
 	{
 	if (isSubtrackVisible(subtrack))
 	    subtrack->visibility = track->visibility;
 	else
 	    subtrack->visibility = tvHide;
 	}
 }
 
 boolean colorsSame(struct rgbColor *a, struct rgbColor *b)
 /* Return true if two colors are the same. */
 {
 return a->r == b->r && a->g == b->g && a->b == b->b;
 }
 
+//#define SPECTRUM_LIVE
+#ifdef SPECTRUM_LIVE
+// Define SPECTRUM_LIVE to enable live calculation of color rather than using track->colorShades
+// History: trackDb setting 'spectrum' (aka 'useScore') is defined to use an item's score to
+//          determine color on a spectrum.  This was originally limited to gray scale and only
+//          the discrete 10 shades defined in the colorShades array.  Two additional color scales
+//          were added (shadesOfBrown and shadesOfSea, but with no documentation on how a trackDb
+//          user could make use of these.  The array of shades has been widely used on track types
+//          beyond beds.
+// Spectrum support has been added to simply fill in the colorShades array on first use, based
+//          upon color/altColor.  This simple change does not need to touch on ubiquitous code that
+//          relies upon colorShades.  It is a targeted change with limited footprint, but maintains
+//          a hash of spectrums that gets built as needed.
+// Live spectrum, on the other hand would do away with colorShades and calculate the color whenever
+//          needed.  There would no longer be a limit to 10 shades, but would require extensive
+//          code changes to eliminate dependence upon the colorShades array.  There is no
+//          compelling efficiency of one method versus another.  Using live calculation should
+//          ultimately simplify code and structures.  But as I am being laid off, I feel it would
+//          be irresponsible to check in a larger change at this time
+// TODO: move to hvGfx.c
+Color hvGfxColorInSpectrum(struct hvGfx *hvg, struct rgbColor *colorOfZero,
+                           struct rgbColor *colorOfOne, float aboveZero)
+// Returns the color that lies between zero and one in the spectrum.
+{
+assert(aboveZero >= 0 && aboveZero <= 1.0);
+float belowOne = 1.0 - aboveZero;
+int red   = (belowOne * colorOfZero->r) + (aboveZero * colorOfOne->r);
+int green = (belowOne * colorOfZero->g) + (aboveZero * colorOfOne->g);
+int blue  = (belowOne * colorOfZero->b) + (aboveZero * colorOfOne->b);
+return hvGfxFindColorIx(hvg, red, green, blue);
+}
+
+Color colorWithinRange(struct hvGfx *hvg, struct rgbColor *bottomColor, struct rgbColor *topColor,
+                       int value, int rangeBottom, int rangeTop)
+// Returns color within range.  Colors outside of range will top or bottom out.
+// If range not defined, then range defaults to normal score range (0-1000).
+{
+// requested spectrum must have 2 colors
+if (!colorsSame(bottomColor, topColor))
+    {
+    if (rangeBottom >= rangeTop)
+        {
+        rangeBottom = 0;    // default this to normal score range
+        rangeTop    = 1000;
+        }
+    if (value < rangeBottom)
+        value = rangeBottom;
+    if (value > rangeTop)
+        value = rangeTop;
+    float aboveZero = ((float)value - rangeBottom)/(rangeTop - rangeBottom);
+    return hvGfxColorInSpectrum(hvg, bottomColor, topColor,aboveZero);
+    }
+else // no color range so use the only color
+    return hvGfxFindColorIx(hvg, topColor->r, topColor->g, topColor->b);
+}
+#endif//def SPECTRUM_LIVE
+
+Color colorBySpectrumOrDefault(struct hvGfx *hvg, struct track *track,int shade,Color defaultColor)
+// Returns color to use if spectrum exists, else returns default
+{
+if (track->tdb->useScore && track->colorShades == NULL)
+    {
+#ifdef SPECTRUM_LIVE
+    return colorWithinTrackIxRange(hvg,track,shade);
+#else// ifndef SPECTRUM_LIVE
+    struct rgbColor paintItBlack = {0, 0, 0};
+    if (!colorsSame(&paintItBlack, &track->color)
+    ||  !colorsSame(&paintItBlack, &track->altColor))
+        {
+        // Caching of the spectrum may be overkill, but if there are hundreds, this will be helpful
+        // An slPair list would be more space efficient, but with slower lookups
+        static struct hash *spectrumHash = NULL;
+        char spectrumName[32];
+        safef(spectrumName,32,"%02X%02X%02X,%02X%02X%02X",
+                             track->color.r,   track->color.g,   track->color.b,
+                          track->altColor.r,track->altColor.g,track->altColor.b);
+        if (spectrumHash == NULL)
+            spectrumHash = hashNew(4); // small hash
+        else
+            track->colorShades = hashFindVal(spectrumHash,spectrumName);
+        if (track->colorShades == NULL)
+            {
+            track->colorShades = needMem(sizeof(Color *) * maxShade + 2);
+            hvGfxMakeColorGradient(hvg, &track->color, &track->altColor,
+                                   maxShade+1, track->colorShades);
+            hashAddUnique(spectrumHash,spectrumName,track->colorShades);
+            }
+        }
+#endif//ndef SPECTRUM_LIVE
+    }
+if (track->colorShades == NULL || shade > maxShade + 1) // true range is 1 to 10.
+    return defaultColor;
+return track->colorShades[shade];
+}
+
 #ifndef GBROWSE
 void loadValAl(struct track *tg)
 /* Load the items in one custom track - just move beds in
  * window... */
 {
 struct linkedFeatures *lfList = NULL, *lf;
 struct bed *bed, *list = NULL;
 struct sqlConnection *conn = hAllocConn(database);
 struct sqlResult *sr;
 char **row;
 int rowOffset;
 
 sr = hRangeQuery(conn, tg->table, chromName, winStart, winEnd, NULL, &rowOffset);
 while ((row = sqlNextRow(sr)) != NULL)
     {
@@ -9841,34 +9933,31 @@
                            MgFont *font, Color color, enum trackVisibility vis)
 /* Draw a right- or left-pointing triangle at position.
  * If item has width > 1 or block/cds structure, those will be ignored --
  * this only draws a triangle (direction depending on strand). */
 {
 struct bed *bed = item;
 int x1 = round((double)((int)bed->chromStart-winStart)*scale) + xOff;
 int y2 = y + tg->heightPer-1;
 struct trackDb *tdb = tg->tdb;
 int scoreMin = atoi(trackDbSettingClosestToHomeOrDefault(tdb, "scoreMin", "0"));
 int scoreMax = atoi(trackDbSettingClosestToHomeOrDefault(tdb, "scoreMax", "1000"));
 
 if (tg->itemColor != NULL)
     color = tg->itemColor(tg, bed, hvg);
 else
-    {
-    if (tg->colorShades)
-	color = tg->colorShades[grayInRange(bed->score, scoreMin, scoreMax)];
-    }
+    color = colorBySpectrumOrDefault(hvg,tg,grayInRange(bed->score, scoreMin, scoreMax),color);
 
 drawTri(hvg, x1, y, y2, color, bed->strand[0]);
 }
 
 void simpleBedTriangleMethods(struct track *tg)
 /* Load up simple bed features methods, but use triangleDrawAt. */
 {
 bedMethods(tg);
 tg->drawItemAt = triangleDrawAt;
 }
 
 void loadColoredExonBed(struct track *tg)
 /* Load the items into a linkedFeaturesSeries. */
 {
 struct sqlConnection *conn = hAllocConn(database);
@@ -12359,119 +12448,86 @@
     }
 track->height = height;
 return track->height;
 }
 
 int trackPriCmp(const void *va, const void *vb)
 /* Compare for sort based on priority */
 {
 const struct track *a = *((struct track **)va);
 const struct track *b = *((struct track **)vb);
 
 return (a->priority - b->priority);
 }
 
 void makeCompositeTrack(struct track *track, struct trackDb *tdb)
-/* Construct track subtrack list from trackDb entry.
- * Sets up color gradient in subtracks if requested */
+// Construct track subtrack list from trackDb entry.
 {
-unsigned char finalR = track->color.r, finalG = track->color.g,
-                            finalB = track->color.b;
-unsigned char altR = track->altColor.r, altG = track->altColor.g,
-                            altB = track->altColor.b;
-unsigned char deltaR = 0, deltaG = 0, deltaB = 0;
-
 struct slRef *tdbRef, *tdbRefList = trackDbListGetRefsToDescendantLeaves(tdb->subtracks);
 
 struct trackDb *subTdb;
 int subCount = slCount(tdbRefList);
-int altColors = subCount - 1;
 struct track *subtrack = NULL;
 TrackHandler handler;
 boolean smart = FALSE;
 
 /* ignore if no subtracks */
 if (!subCount)
     return;
 
 char *compositeTrack = trackDbLocalSetting(tdb, "compositeTrack");
 /* look out for tracks that manage their own subtracks */
 if (startsWith("wig", tdb->type) || startsWith("bedGraph", tdb->type) ||
     (compositeTrack != NULL && rStringIn("smart", compositeTrack)))
         smart = TRUE;
 
 /* setup function handlers for composite track */
 handler = lookupTrackHandlerClosestToHome(tdb);
 if (smart && handler != NULL)
     /* handles it's own load and height */
     handler(track);
 else
     {
     track->loadItems = compositeLoad;
     track->totalHeight = compositeTotalHeight;
     }
 
-if (altColors && (finalR || finalG || finalB))
-    {
-    /* not black -- make a color gradient for the subtracks,
-                from black, to the specified color */
-    deltaR = (finalR - altR) / altColors;
-    deltaG = (finalG - altG) / altColors;
-    deltaB = (finalB - altB) / altColors;
-    }
-
 /* fill in subtracks of composite track */
 for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next)
     {
     subTdb = tdbRef->val;
 
     subtrack = trackFromTrackDb(subTdb);
     handler = lookupTrackHandlerClosestToHome(subTdb);
     if (handler != NULL)
         handler(subtrack);
 
     /* Add subtrack settings (table, colors, labels, vis & pri).  This is only
      * needed in the "not noInherit" case that hopefully will go away soon. */
     subtrack->track = subTdb->track;
     subtrack->table = subTdb->table;
     subtrack->shortLabel = subTdb->shortLabel;
     subtrack->longLabel = subTdb->longLabel;
     subtrack->priority = subTdb->priority;
     subtrack->parent = track;
 
-    /* Add color gradient. */
-    if (finalR || finalG || finalB)
-	{
-	subtrack->color.r = altR;
-	subtrack->altColor.r = (255+altR)/2;
-	altR += deltaR;
-	subtrack->color.g = altG;
-	subtrack->altColor.g = (255+altG)/2;
-	altG += deltaG;
-	subtrack->color.b = altB;
-	subtrack->altColor.b = (255+altB)/2;
-	altB += deltaB;
-	}
-    else
-	{
     subtrack->color.r = subTdb->colorR;
     subtrack->color.g = subTdb->colorG;
     subtrack->color.b = subTdb->colorB;
     subtrack->altColor.r = subTdb->altColorR;
     subtrack->altColor.g = subTdb->altColorG;
     subtrack->altColor.b = subTdb->altColorB;
-	}
     slAddHead(&track->subtracks, subtrack);
     }
 slSort(&track->subtracks, trackPriCmp);
 }
 
 struct track *trackFromTrackDb(struct trackDb *tdb)
 /* Create a track based on the tdb */
 {
 struct track *track = NULL;
 char *exonArrows;
 char *nextItem;
 
 if (!tdb)
     return NULL;
 track = trackNew();
@@ -12508,34 +12564,38 @@
 track->lineHeight = tl.fontHeight+1;
 track->heightPer = track->lineHeight - 1;
 track->private = tdb->private;
 track->defaultPriority = tdb->priority;
 char lookUpName[256];
 safef(lookUpName, sizeof(lookUpName), "%s.priority", tdb->track);
 tdb->priority = cartUsualDouble(cart, lookUpName, tdb->priority);
 track->priority = tdb->priority;
 track->groupName = cloneString(tdb->grp);
 /* save default priority and group so we can reset it later */
 track->defaultGroupName = cloneString(tdb->grp);
 track->canPack = tdb->canPack;
 if (tdb->useScore)
     {
     /* Todo: expand spectrum opportunities. */
+    struct rgbColor paintItBlack = {0, 0, 0};
     if (colorsSame(&brownColor, &track->color))
         track->colorShades = shadesOfBrown;
     else if (colorsSame(&darkSeaColor, &track->color))
         track->colorShades = shadesOfSea;
+    else if (!colorsSame(&paintItBlack, &track->color)
+         ||  !colorsSame(&paintItBlack, &track->altColor))
+        track->colorShades = NULL; // Signal to generate spectrum on first need
     else
         track->colorShades = shadesOfGray;
     }
 track->tdb = tdb;
 
 /* Handle remote database settings - just a JK experiment at the moment. */
 track->remoteSqlHost = trackDbSetting(tdb, "sqlHost");
 track->remoteSqlUser = trackDbSetting(tdb, "sqlUser");
 track->remoteSqlPassword = trackDbSetting(tdb, "sqlPassword");
 track->remoteSqlDatabase = trackDbSetting(tdb, "sqlDatabase");
 track->remoteSqlTable = trackDbSetting(tdb, "sqlTable");
 track->isRemoteSql =  (track->remoteSqlHost != NULL && track->remoteSqlUser != NULL
 			&& track->remoteSqlDatabase != NULL && track->remoteSqlTable !=NULL);
 
 exonArrows = trackDbSetting(tdb, "exonArrows");