5b1a4883cd3c9d4be801592648d729a394e07240
kate
  Mon Jul 10 15:31:42 2017 -0700
Add indicator of tissue color to eQTL right label for single-tissue items. refs #15646

diff --git src/hg/hgTracks/gtexEqtlClusterTrack.c src/hg/hgTracks/gtexEqtlClusterTrack.c
index a2b800b..8305e7a 100644
--- src/hg/hgTracks/gtexEqtlClusterTrack.c
+++ src/hg/hgTracks/gtexEqtlClusterTrack.c
@@ -15,52 +15,59 @@
 /* Track info for filtering (maybe later for draw, map) */
 {
     struct gtexTissue *tissues; /*  Tissue names, descriptions */
     struct hash *tissueHash;    /* Tissue info, keyed by UCSC name, filtered by UI */
     double minEffect;           /* Effect size filter (abs value) */
     double minProb;             /* Probability filter */
 };
 
 static struct gtexEqtlCluster *loadOne(char **row)
 /* Load up gtexEqtlCluster from array of strings. */
 {
 return gtexEqtlClusterLoad(row);
 }
 
 static boolean filterTissues(struct track *track, struct gtexEqtlClusterTrack *extras)
-/* Check cart for tissue selection */
+/* Check cart for tissue selection. Populate track tissues hash */
 {
 char *version = gtexVersionSuffix(track->table);
 extras->tissues = gtexGetTissues(version);
 extras->tissueHash = hashNew(0);
-if (cartListVarExistsAnyLevel(cart, track->tdb, FALSE, GTEX_TISSUE_SELECT))
-    {
+struct gtexTissue *tis = NULL;
+for (tis = extras->tissues; tis != NULL; tis = tis->next)
+    hashAdd(extras->tissueHash, tis->name, tis);
+
+// if all tissues included, return full hash
+if (!cartListVarExistsAnyLevel(cart, track->tdb, FALSE, GTEX_TISSUE_SELECT))
+    return FALSE;
+
+// create tissue hash with only included tissues
+struct hash *tisHash = hashNew(0);
 struct slName *selectedValues = cartOptionalSlNameListClosestToHome(cart, track->tdb,
                                                     FALSE, GTEX_TISSUE_SELECT);
-    if (selectedValues != NULL)
-        {
+if (selectedValues == NULL)
+    return FALSE;
+
 struct slName *name;
 for (name = selectedValues; name != NULL; name = name->next)
-            hashAdd(extras->tissueHash, name->name, name->name);
-        return TRUE;
-        }
+    {
+    tis = (struct gtexTissue *)hashFindVal(extras->tissueHash, name->name);
+    if (tis != NULL)
+        hashAdd(tisHash, name->name, tis);
     }
-/* no filter */
-struct gtexTissue *tis = NULL;
-for (tis = extras->tissues; tis != NULL; tis = tis->next)
-    hashAdd(extras->tissueHash, tis->name, tis->name);
-return FALSE;
+extras->tissueHash = tisHash;
+return TRUE;
 }
 
 static void excludeTissue(struct gtexEqtlCluster *eqtl, int i)
 /* Mark the tissue to exclude from display */
 {
 eqtl->expScores[i] = 0.0;
 }
 
 static boolean isExcludedTissue(struct gtexEqtlCluster *eqtl, int i)
 /* Check if eQTL is excluded */
 {
 return (eqtl->expScores[i] == 0.0);
 }
 
 static boolean eqtlIncludeFilter(struct track *track, void *item)
@@ -123,49 +130,92 @@
 boolean hasTissueFilter = filterTissues(track, extras);
 if (!hasTissueFilter && extras->minEffect == 0.0 && extras->minProb == 0.0)
     return;
 
 // more filtering
 filterItems(track, eqtlIncludeFilter, "include");
 }
 
 static char *gtexEqtlClusterItemName(struct track *track, void *item)
 /* Left label is gene name */
 {
 struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
 return eqtl->target;
 }
 
-static char *gtexEqtlClusterSourcesLabel(struct track *track, void *item)
-/* Right label is tissue (or number of tissues if >1) */
+static int itemTissueCount(void *item)
+/* Return count of non-excluded tissues in the item */
 {
 struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
-int i, included;
-for (i=0, included=0; i<eqtl->expCount; i++)
+int included = 0;
+int i;
+for (i=0; i<eqtl->expCount; i++)
     if (!isExcludedTissue(eqtl, i))
         included++;
-if (included == 1)
-    return eqtl->expNames[i-1];
+return included;
+}
+
+static int itemTissueIndex(void *item)
+/* Return index of first non-excluded tissue in an item. Used for single-tissue items. */
+{
+struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
+int i;
+for (i=0; i<eqtl->expCount; i++)
+    if (!isExcludedTissue(eqtl, i))
+        return i;
+return -1;
+}
+
+static char *itemSourcesLabel(void *item)
+/* Right label is tissue (or number of tissues if >1) */
+{
+struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
+int ct = itemTissueCount(item);
+if (ct == 1)
+    {
+    int i = itemTissueIndex(item);
+    if (i<0)
+        errAbort("GTEx eQTL %s/%s track tissue index is negative", eqtl->name, eqtl->target);
+    return eqtl->expNames[i];
+    }
 struct dyString *ds = dyStringNew(0);
-dyStringPrintf(ds, "%d tissues", included);
+dyStringPrintf(ds, "%d tissues", ct);
 return dyStringCannibalize(&ds);
 }
 
+static struct rgbColor itemTissueColor(struct track *track, void *item)
+/* Return tissue color for single-tissue item, or NULL if none found */
+{
+int i = itemTissueIndex(item);
+assert(i>=0);
+struct gtexEqtlClusterTrack *extras = (struct gtexEqtlClusterTrack *)track->extraUiData;
+struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
+struct gtexTissue *tis = (struct gtexTissue *)hashFindVal(extras->tissueHash, eqtl->expNames[i]);
+assert (tis);
+return (struct rgbColor){.r=COLOR_32_BLUE(tis->color), .g=COLOR_32_GREEN(tis->color), 
+                .b=COLOR_32_RED(tis->color)};
+}
+
+#define TISSUE_COLOR_DOT        "*"
+
 static int gtexEqtlClusterItemRightPixels(struct track *track, void *item)
 /* Return number of pixels we need to the right of an item (for sources label). */
 {
-return mgFontStringWidth(tl.font, gtexEqtlClusterSourcesLabel(track, item));
+int ret = mgFontStringWidth(tl.font, itemSourcesLabel(item));
+if (itemTissueCount(item) == 1)
+    ret += mgFontStringWidth(tl.font, TISSUE_COLOR_DOT);
+return ret;
 }
 
 static Color gtexEqtlClusterItemColor(struct track *track, void *item, struct hvGfx *hvg)
 /* Color by highest effect in list (blue -, red +), with brighter for higher effect (teal, fuschia) */
 {
 struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
 double maxEffect = 0.0;
 int i;
 for (i=0; i<eqtl->expCount; i++)
     {
     if (isExcludedTissue(eqtl, i))
         continue;
     double effect = eqtl->expScores[i];
     if (fabs(effect) > fabs(maxEffect))
         maxEffect = effect;
@@ -185,33 +235,43 @@
 }
 
 static void gtexEqtlClusterDrawItemAt(struct track *track, void *item, 
 	struct hvGfx *hvg, int xOff, int y, 
 	double scale, MgFont *font, Color color, enum trackVisibility vis)
 /* Draw GTEx eQTL cluster with right label indicating source(s) */
 {
 bedPlusLabelDrawAt(track, item, hvg, xOff, y, scale, font, color, vis);
 if (vis != tvFull && vis != tvPack)
     return;
 
 /* Draw text to the right */
 struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
 int x2 = round((double)((int)eqtl->chromEnd-winStart)*scale) + xOff;
 int x = x2 + tl.mWidth/2;
-char *label = gtexEqtlClusterSourcesLabel(track, item);
+char *label = itemSourcesLabel(item);
 int w = mgFontStringWidth(font, label);
 hvGfxTextCentered(hvg, x, y, w, track->heightPer, MG_BLACK, font, label);
+if (itemTissueCount(item) == 1)
+    {
+    // append asterisk in tissue color
+    struct rgbColor tisColor = itemTissueColor(track, item);
+    x += w;
+    w = mgFontStringWidth(font, TISSUE_COLOR_DOT);
+    int ix = hvGfxFindColorIx(hvg, tisColor.r, tisColor.g, tisColor.b);
+    font = mgFontForSizeAndStyle(tl.textSize, "bold");
+    hvGfxTextCentered(hvg, x, y, w, track->heightPer, ix, font, TISSUE_COLOR_DOT);
+    }
 }
 
 static void gtexEqtlClusterMapItem(struct track *track, struct hvGfx *hvg, void *item, 
                                 char *itemName, char *mapItemName, int start, int end, 
                                 int x, int y, int width, int height)
 /* Create a map box on item and label with list of tissues with colors and effect size */
 {
 char *title = itemName;
 if (track->limitedVis != tvDense)
     {
     struct gtexEqtlCluster *eqtl = (struct gtexEqtlCluster *)item;
     // Experiment: construct list of tissues with colors and effect sizes for mouseover
     //struct gtexEqtlClusterTrack *extras = (struct gtexEqtlClusterTrack *)track->extraUiData;
     //struct hash *tissueHash = extras->tissueHash;
     struct dyString *ds = dyStringNew(0);