b5c461e5c49abf9eb5a708e2b63243555b2362da
braney
  Wed Nov 10 18:05:28 2021 -0800
first cut at a sequence logo track, also some makeDoc for gene tracks

diff --git src/hg/hgTracks/wigTrack.c src/hg/hgTracks/wigTrack.c
index c2f4211..42afbfa 100644
--- src/hg/hgTracks/wigTrack.c
+++ src/hg/hgTracks/wigTrack.c
@@ -12,30 +12,31 @@
 #include "jksql.h"
 #include "hdb.h"
 #include "hgTracks.h"
 #include "wiggle.h"
 #include "hmmstats.h"
 #include "scoredRef.h"
 #ifndef GBROWSE
 #include "customTrack.h"
 #endif /* GBROWSE */
 #include "wigCommon.h"
 #include "imageV2.h"
 #include "memgfx.h"
 #include "udc.h"
 #include "trashDir.h"
 #include "jsonWrite.h"
+#include "dnaMotif.h"
 
 struct wigItem
 /* A wig track item. */
     {
     struct wigItem *next;
     int start, end;	/* Start/end in chrom (aka browser) coordinates. */
     char *db;		/* Database */
     int ix;		/* Position in list. */
     int height;		/* Pixel height of item. */
     unsigned span;      /* each value spans this many bases */
     unsigned count;     /* number of values to use */
     unsigned offset;    /* offset in File to fetch data */
     char *file; /* path name to data file, one byte per value */
     double lowerLimit;  /* lowest data value in this block */
     double dataRange;   /* lowerLimit + dataRange = upperLimit */
@@ -1157,31 +1158,321 @@
 	    grayValue = min(grayValue,graphUpperLimit);
 	    grayIndex = ((grayValue-graphLowerLimit)/graphRange)*MAX_WIG_VALUE;
 
 	    drawColor =
 		tg->colorShades[grayInRange(grayIndex, 0, MAX_WIG_VALUE)];
 	    doLine(image, x, yOff, tg->lineHeight, drawColor);
             }   /*	vis == tvDense || vis == tvSquish	*/
 	}	/*	if (preDraw[].count)	*/
     }	/*	for (x1 = 0; x1 < width; ++x1)	*/
 
 if (dropMouseOverData)
     slFreeList(&mouseOverData);
 return(mouseOverData);
 }	/*	graphPreDraw()	*/
 
-static struct wigMouseOver *graphPreDrawContainer(struct preDrawContainer *preDrawContainer,
+static void drawLogoChar( struct hvGfx *hvg, int xOff, int yOff, struct dnaMotif *motif, int width, int height, int count, boolean flip)
+{
+int orangeColor = hvGfxFindColorIx(hvg, 230, 130, 0);
+int blueColor = hvGfxFindColorIx(hvg, 0,114,198);
+int greenColor = hvGfxFindColorIx(hvg, 28,206,40);
+
+int ii;
+FILE *f;
+struct tempName pngTn;
+unsigned char *buf;
+
+buf = needMem(width + 1);
+makeTempName(&pngTn, "logo", ".pgm");
+dnaMotifToLogoPGM(motif, width / count, width  , (double)height - 2, NULL, "../trash", pngTn.forCgi);
+
+f = mustOpen(pngTn.forCgi, "r");
+
+/* get rid of header */
+for(ii=0; ii < 4; ii++)
+    while(fgetc(f) != '\n')
+	;
+
+/* map colors from PGM to browser colors */
+Color *colors = needMem(sizeof(Color) * (1+width));
+for(ii=0; ii < height; ii++)
+    {
+    int jj;
+    mustRead(f, buf, width);
+
+    for(jj=0; jj < width + 2; jj++)
+	{
+	if (buf[jj] == 255) colors[jj] = MG_WHITE;
+	else if (buf[jj] == 0x87) colors[jj] = MG_RED;
+	else if (buf[jj] == 0x60) colors[jj] = greenColor;
+	else if (buf[jj] == 0x7f) colors[jj] = blueColor;
+	else if (buf[jj] == 0x62) colors[jj] = orangeColor;
+	}
+
+    if (flip)
+        {
+        int pos = yOff + height - ii;
+        hvGfxVerticalSmear(hvg,xOff,pos,width ,1, colors,TRUE);
+            //        hvGfxBox(hvg, xOff, pos, width, 1, MG_BLACK);
+        }
+    else
+        hvGfxVerticalSmear(hvg,xOff,yOff+ii,width ,1, colors,TRUE);
+    }
+hvGfxUnclip(hvg);
+
+fclose(f);
+remove(pngTn.forCgi);
+}
+
+struct dnaMotif *getMotif(int numBases)
+{
+struct dnaMotif *motif;
+AllocVar(motif);
+motif->name = NULL;
+motif->columnCount = numBases;
+motif->aProb = needMem(sizeof(float) * motif->columnCount);
+motif->cProb = needMem(sizeof(float) * motif->columnCount);
+motif->gProb = needMem(sizeof(float) * motif->columnCount);
+motif->tProb = needMem(sizeof(float) * motif->columnCount);
+
+return motif;
+}
+
+static struct wigMouseOver *logoPreDrawContainer(struct preDrawContainer *preDrawContainer,
+    int preDrawZero, int width, struct track *tg, struct hvGfx *hvg,
+    int xOff, int yOff, double graphUpperLimit, double graphLowerLimit,
+    double graphRange, enum trackVisibility vis, struct wigCartOptions *wigCart, int seqStart, int seqEnd)
+{
+struct preDrawElement *preDraw = preDrawContainer->preDraw;
+struct wigGraphOutput *wgo = tg->wigGraphOutput;
+struct wigMouseOver *mouseOverData = NULL;
+unsigned numBases = seqEnd - seqStart;
+struct dnaSeq *seq = hChromSeq(database, chromName, seqStart, seqEnd);
+struct pixelCountBin *pixelBins = wgo->pixelBins;
+double *yOffsets = wgo->yOffsets;
+int numTrack = wgo->numTrack;
+Color clipColor = MG_MAGENTA;
+WigVerticalLineVirtual vLine = wgo->vLine;
+void *image = wgo->image;
+#define doLine(image, x, y, height, color) {vLine(image, x, y, height, color); }
+
+int h = tg->lineHeight;	/*	the height of our drawing window */
+double scaleFactor = h/graphRange;
+
+boolean skipMouseOvers = TRUE;	/* assuming not using */
+int mouseOverX2 = -1;
+double previousValue = 0;
+if (enableMouseOver)
+    skipMouseOvers = FALSE;
+
+boolean noAverage = FALSE;
+boolean dropMouseOverData = FALSE;	// will become TRUE if noAverage
+					// condition is encountered
+char *mouseOverFunction = trackDbSetting(tg->tdb, "mouseOverFunction");
+if (sameOk(mouseOverFunction, "noAverage"))
+    noAverage = TRUE;
+
+struct dnaMotif *motifPos = getMotif(numBases);
+struct dnaMotif *motifNeg = getMotif(numBases);
+
+double xIncr = (double)width / numBases;
+unsigned baseNum;
+int lastX = xOff;
+for(baseNum = 0; baseNum < numBases; baseNum++)
+    {
+    int x1 = baseNum * xIncr;
+    int x = x1 + xOff;
+    int width = x - lastX;
+    int base = seq->dna[baseNum];
+    lastX = x;
+    int preDrawIndex = x1 + preDrawZero;
+    struct preDrawElement *p = &preDraw[preDrawIndex];
+    /* ===== mouseOver calculations===== */
+    if (enableMouseOver && !dropMouseOverData)
+        {
+        /* checking if mouseOver construction is allowed */
+        if (!skipMouseOvers && (p->count > 0) && !(noAverage && p->count>1))
+            {
+            if (p->count > 0)	/* allow any number of values to display */
+                {
+                double thisValue = p->sumData/p->count;	/*average if count > 1*/
+                if (mouseOverX2 < 0)    /* first valid data found */
+                    {
+		    struct wigMouseOver *dataItem;
+		    AllocVar(dataItem);
+                    mouseOverX2 = x1+1;
+                    dataItem->x1 = x1;
+                    dataItem->x2 = mouseOverX2;
+                    dataItem->value = thisValue;
+		    dataItem->valueCount = p->count;
+		    slAddHead(&mouseOverData, dataItem);
+                    previousValue = thisValue;
+                    }
+                else	/* see if we need a new item */
+                    {
+#define epsilonLimit 1.0e-6
+                    if (fabs(thisValue - previousValue) > epsilonLimit)
+                        {
+                        /* finish off the existing run of data (list head)*/
+                        mouseOverData->x2 = mouseOverX2;
+                        mouseOverX2 = x1+1;
+			struct wigMouseOver *dataItem;
+			AllocVar(dataItem);
+                        dataItem->x1 = x1;
+                        dataItem->x2 = mouseOverX2;
+                        dataItem->value = thisValue;
+			dataItem->valueCount = p->count;
+			slAddHead(&mouseOverData, dataItem);
+                        previousValue = thisValue;
+                        }
+                    else	/* continue run of same data value */
+                        mouseOverX2 = x1+1;
+                    }
+                }
+            else
+                skipMouseOvers = TRUE;	/* has become too dense to make sense */
+            }
+        else /* perhaps entered region without values after some data already */
+            {
+
+            if (noAverage && p->count>1)
+              dropMouseOverData = TRUE;
+            else if (mouseOverX2 > 0)	/* yes, been in data, end it here */
+                {
+		mouseOverData->x2 = mouseOverX2;
+                mouseOverX2 = -1;	/* start over with new data when found*/
+                }
+            }
+        /* potentially end the last mouseOver box */
+        if (mouseOverX2 > 0 && mouseOverX2 > mouseOverData->x2)
+                mouseOverData->x2 = mouseOverX2;
+
+        }       //      if (enableMouseOver)
+    else
+	skipMouseOvers = TRUE;
+
+    /* ===== done with mouseOver calculations===== */
+
+    assert(x1/pixelBins->binSize < pixelBins->binCount);
+    unsigned long *bitCount = &pixelBins->bins[x1/pixelBins->binSize];
+
+    /*	count is non-zero meaning valid data exists here	*/
+    if (p->count)
+	{
+	/*	data value has been picked by previous scanning.
+	 *	Could be smoothed, maybe not.
+	 */
+	double dataValue = p->smooth;
+
+        /* save a number that represents how many pixels that would be set if we were drawing bars.
+         * This may used for sorting later on */
+        int iy0 = graphUpperLimit * scaleFactor;
+        int iy1 = (graphUpperLimit - dataValue)*scaleFactor;
+        int boxHeight = max(1,abs(iy1 - iy0));
+        *bitCount += boxHeight;
+
+
+	/*	The graphing coordinate conversion situation is:
+	 *	graph coordinate y = 0 is graphUpperLimit data space
+	 *	and total graph height is h which is graphRange in data space
+	 *	The Y axis is positive down, negative up.
+	 *
+	 *	Taking a simple coordinate conversion from data space
+	 *	to the graphing space, the data value is at:
+	 *	h * ((graphUpperLimit - dataValue)/graphRange)
+	 *	and a data value zero line is at:
+	 *	h * (graphUpperLimit/graphRange)
+	 *	These may end up to be negative meaning they are above
+	 *	the upper graphing limit, or be very large, meaning they
+         *      are below the lower graphing limit.  This is OK, the
+         *      clipping will be taken care of by the vgBox() function.
+         */
+
+        if (vis == tvFull || vis == tvPack)
+            {
+#define scaleHeightToPixels(val) (min(BIGNUM,(scaleFactor * (graphUpperLimit - (val)) + yOff)))
+#define doLine(image, x, y, height, color) {vLine(image, x, y, height, color); }
+                {
+                int y0 = graphUpperLimit * scaleFactor;
+                int y1 = (graphUpperLimit - dataValue)*scaleFactor;
+                if (yOffsets)
+                    {
+                    if (numTrack > 0)
+                        {
+                        y0 = (graphUpperLimit  - yOffsets[(numTrack-1) *  width + x1]) *scaleFactor;
+                        y1 = (graphUpperLimit - dataValue - yOffsets[(numTrack-1) *  width + x1])*scaleFactor;
+                        }
+                    }
+
+                int boxHeight = max(1,abs(y1 - y0));
+                int boxTop = min(y1,y0);
+
+                //	positive data value exactly equal to Bottom pixel
+                //  make sure it draws at least a pixel there
+                if (boxTop == h)
+                    boxTop = h - 1;
+
+                // negative data value exactly equal to top pixel
+                // make sure it draws something
+                double prob = (double)(dataValue) / (graphUpperLimit - graphLowerLimit);
+                    
+                if (((boxTop+boxHeight) == 0) && !isnan(dataValue))
+                    boxHeight += 1;
+                struct dnaMotif *motif;
+                if (dataValue < 0)
+                    {
+                    motif = motifNeg;
+                    prob = -prob;
+                    }
+                else
+                    motif = motifPos;
+                if (base == 'a')
+                    motif->aProb[baseNum] = prob;
+                if (base == 't')
+                    motif->tProb[baseNum] = prob;
+                if (base == 'c')
+                    motif->cProb[baseNum] = prob;
+                if (base == 'g')
+                    motif->gProb[baseNum] = prob;
+                }
+	    double stackValue = dataValue;
+
+	    if ((yOffsets != NULL) && (numTrack > 0))
+		stackValue += yOffsets[(numTrack-1) *  width + x1];
+	    if (stackValue > graphUpperLimit)
+                {
+		doLine(image, x, yOff, 2, clipColor);
+                }
+	    else if (stackValue < graphLowerLimit)
+                {
+		doLine(image, x, yOff + h - 1, 2, clipColor);
+                }
+#undef scaleHeightToPixels	/* No longer use this symbol */
+            }   /*	vis == tvFull || vis == tvPack */
+        }
+    }	/*	for (x1 = 0; x1 < width; ++x1)	*/
+
+drawLogoChar( hvg, xOff, yOff + graphLowerLimit * scaleFactor, motifPos, width, tg->lineHeight, numBases, FALSE);
+drawLogoChar( hvg, xOff, yOff + graphUpperLimit * scaleFactor, motifNeg, width, tg->lineHeight, numBases, TRUE);
+
+if (dropMouseOverData)
+    slFreeList(&mouseOverData);
+return(mouseOverData);
+}
+
+
+struct wigMouseOver *graphPreDrawContainer(struct preDrawContainer *preDrawContainer,
     int preDrawZero, int width, struct track *tg, struct hvGfx *hvg,
     int xOff, int yOff, double graphUpperLimit, double graphLowerLimit,
     double graphRange, enum trackVisibility vis, struct wigCartOptions *wigCart)
 /* Draw the graphs for all tracks in container. */
 {
 double epsilon = graphRange / tg->lineHeight;
 struct preDrawElement *preDraw = preDrawContainer->preDraw;
 Color *colorArray = makeColorArray(preDraw, width, preDrawZero, wigCart, tg, hvg);
 struct wigGraphOutput *wgo = tg->wigGraphOutput;
 struct wigMouseOver *mouseOverData = graphPreDraw(preDraw, preDrawZero, width,
 	tg, wgo->image, wgo->vLine, wgo->xOff, wgo->yOff, wgo->yOffsets,
 	wgo->numTrack, graphUpperLimit, graphLowerLimit, graphRange,
 	epsilon, colorArray, vis, wigCart, wgo->pixelBins);
 
 freez(&colorArray);
@@ -1424,31 +1715,37 @@
     tg->graphUpperLimit = graphUpperLimit = tg->tdb->parent->tdbExtras->minMax->max;
     }
 
 /* if we're autoscaling and the range is 0 this implies that all values
  * in the given range are the same.  We create a bottom of the scale
  * by subtracting one from the only value.
  * This results in drawing a box that fills the range. */
 if (graphUpperLimit == graphLowerLimit)
     {
     graphLowerLimit = graphUpperLimit - 1;
     }
 graphRange = graphUpperLimit - graphLowerLimit;
 
 wigTrackSetGraphOutputDefault(tg, xOff, yOff, width, hvg);
 
-struct wigMouseOver *mouseOverData = graphPreDrawContainer(preContainer,
+struct wigMouseOver *mouseOverData = NULL;
+if (zoomedToCodonLevel && trackDbSettingOn(tg->tdb, "logo"))
+    mouseOverData = logoPreDrawContainer(preContainer,
+        preDrawZero, width, tg, hvg, xOff, yOff,
+        graphUpperLimit, graphLowerLimit, graphRange, vis, wigCart, seqStart, seqEnd);
+else
+    mouseOverData = graphPreDrawContainer(preContainer,
         preDrawZero, width, tg, hvg, xOff, yOff,
         graphUpperLimit, graphLowerLimit, graphRange, vis, wigCart);
 
 drawZeroLine(vis, wigCart->horizontalGrid,
     graphUpperLimit, graphLowerLimit,
     hvg, xOff, yOff, width, tg->lineHeight);
 
 drawArbitraryYLine(vis, (enum wiggleGridOptEnum)wigCart->yLineOnOff,
     graphUpperLimit, graphLowerLimit,
     hvg, xOff, yOff, width, tg->lineHeight, wigCart->yLineMark, graphRange,
     wigCart->yLineOnOff);
 
 if (enableMouseOver && mouseOverData)
     {
     jsonWriteObjectStart(mouseOverJson, tg->track);