/* wigTrack - stuff to handle loading and display of
 * wig type tracks in browser. Wigs are arbitrary data graphs
 */

/* Copyright (C) 2014 The Regents of the University of California 
 * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */

#include "common.h"
#include "obscure.h"
#include "hash.h"
#include "linefile.h"
#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"
#include "maf.h"
#include "hgMaf.h"
#include "chromAlias.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 */
    unsigned validCount;        /* number of valid data values in this block */
    double sumData;     /* sum of the data points, for average and stddev calc */
    double sumSquares;      /* sum of data points squared, for stddev calc */
    double graphUpperLimit;	/* filled in by DrawItems	*/
    double graphLowerLimit;	/* filled in by DrawItems	*/
    };

static boolean doLogo(struct track *tg)
/* Are we going to draw a logo? */
{
struct wigCartOptions *wigCart = tg->wigCartData;
return trackDbSettingOn(tg->tdb, "logo") || ((trackDbSetting(tg->tdb, "logoMaf") != NULL) && wigCart->doSequenceLogo); 
}

static void wigFillInColorLfArray(struct track *wigTrack, Color *colArray, int colSize,
				  struct track *colorTrack)
/* Fill in a color array with the linkedFeatures based colorTrack's
   color where it would normally have an exon. */
{
struct linkedFeatures *lf = NULL, *lfList = colorTrack->items;
struct simpleFeature *sf = NULL;
double scale = scaleForPixels(colSize);
int x1 = 0, x2 = 0;
int i = 0;
for(lf = lfList; lf != NULL; lf = lf->next)
    {
    for (sf = lf->components; sf != NULL; sf = sf->next)
	{
	x1 = round((double)((int)sf->start-winStart)*scale);
	x2 = round((double)((int)sf->end-winStart)*scale);
	if(x1 < 0)
	    x1 = 0;
	if(x2 > colSize)
	    x2 = colSize;
	if(x1 == x2)
	    x2++;
	for(i = x1; i < x2; i++)
	    colArray[i] = colorTrack->ixAltColor;
	}
    }
}

static void wigFillInColorBedArray(struct track *wigTrack, Color *colArray, int colSize,
				  struct track *colorTrack, struct hvGfx *hvg)
/* Fill in a color array with the simple bed based colorTrack's
   color where it would normally have an block. */
{
struct bed *bed = NULL, *bedList = colorTrack->items;
double scale = scaleForPixels(colSize);
int x1 = 0, x2 = 0;
int i = 0;
for (bed = bedList; bed != NULL; bed = bed->next)
    {
    x1 = round((double)((int)bed->chromStart-winStart)*scale);
    x2 = round((double)((int)bed->chromEnd-winStart)*scale);
    if(x1 < 0)
	x1 = 0;
    if(x2 > colSize)
	x2 = colSize;
    if(x1 == x2)
	x2++;
    for(i = x1; i < x2 && i < colSize; i++)
	{
	if(colorTrack->itemColor != NULL)
	    colArray[i] = colorTrack->itemColor(colorTrack, bed, hvg);
	else
	    colArray[i] = colorTrack->ixColor;
        }
    }
}

void wigFillInColorArray(struct track *wigTrack, struct hvGfx *hvg,
                         Color *colorArray, int colSize, struct track *colorTrack)
/* Fill in a color array with the colorTrack's color where
   it would normally have an exon. */
{
boolean trackLoaded = FALSE;
/* If the track is hidden currently, load the items. */
if(colorTrack->limitedVis == tvHide)
    {
    trackLoaded = TRUE;
    colorTrack->loadItems(colorTrack);
    colorTrack->ixColor = hvGfxFindRgb(hvg, &colorTrack->color);
    colorTrack->ixAltColor = hvGfxFindRgb(hvg, &colorTrack->altColor);
    }

if(colorTrack->drawItemAt == linkedFeaturesDrawAt)
    wigFillInColorLfArray(wigTrack, colorArray, colSize, colorTrack);
else if(colorTrack->drawItemAt == bedDrawSimpleAt)
    wigFillInColorBedArray(wigTrack, colorArray, colSize, colorTrack, hvg);

if(trackLoaded && colorTrack->freeItems != NULL)
    colorTrack->freeItems(colorTrack);
}

void wigSetCart(struct track *track, char *dataID, void *dataValue)
/*	set one of the variables in the wigCart.  Actually just MIN_Y or MAX_Y	*/
{
struct wigCartOptions *wigCart;
wigCart = (struct wigCartOptions *) track->wigCartData;

if (sameWord(dataID, MIN_Y))
    wigCart->minY = *((double *)dataValue);
else if (sameWord(dataID, MAX_Y))
    wigCart->maxY = *((double *)dataValue);
else
    internalErr();
}

/*	these two routines unused at this time	*/
#if defined(NOT)
static void wigItemFree(struct wigItem **pEl)
    /* Free up a wigItem. */
{
struct wigItem *el = *pEl;
if (el != NULL)
    {
    /* freeMem(el->name);	DO NOT - this belongs to tg->mapName */
    freeMem(el->db);
    freeMem(el->file);
    freez(pEl);
    }
}

static void wigItemFreeList(struct wigItem **pList)
    /* Free a list of dynamically allocated wigItem's */
{
struct wigItem *el, *next;

for (el = *pList; el != NULL; el = next)
    {
    next = el->next;
    wigItemFree(&el);
    }
*pList = NULL;
}
#endif

/*	trackSpans - hash of hashes, first hash is keyed via trackName
 *	the value for key trackName is a hash itself where each element
 *	is a Span found in the data (==zoom level indication)
 *	The key for the second hash is the ascii string for the span
 *	value, and the value at that key is the binary equivalent for
 *	that number.
 */
static struct hash *trackSpans = NULL;	/* hash of hashes */

/*	The item names have been massaged during the Load.  An
 *	individual item may have been read in on multiple table rows and
 *	had an extension on it to make it unique from the others.  Also,
 *	each different zoom level had a different extension name.
 *	All these names were condensed into the root of the name with
 *	the extensions removed.
 */
char *wigNameCallback(struct track *tg, void *item)
/* Return name of wig level track. */
{
return tg->track;
}

/*	NOT used at this time, maybe later	*/
#if defined(NOT)
/*	This is practically identical to sampleUpdateY in sampleTracks.c
 *	In fact is is functionally identical except jkLib functions are
 *	used instead of the actual string functions.  I will consult
 *	with Ryan to see if one of these copies can be removed.
 */
static boolean sameWigGroup(char *name, char *nextName, int lineHeight)
/* Only increment height when name root (extension removed)
 * is different from previous one.  Assumes entries are sorted by name.
 */
{
int different = 0;
char *s0;
char *s1;
s0 = cloneString(name);
s1 = cloneString(nextName);
chopSuffix(s0);
chopSuffix(s1);
different = differentString(s0,s1);
freeMem(s0);
freeMem(s1);
if (different)
    return lineHeight;
else
    return 0;
}
#endif

#define FONT_HEIGHT       (tl.fontHeight)
#define WIG_DENSE_HEIGHT  FONT_HEIGHT
#define WIG_PACK_HEIGHT   FONT_HEIGHT
#define WIG_SQUISH_HEIGHT (((FONT_HEIGHT/2) & 1) ? (FONT_HEIGHT/2) : (FONT_HEIGHT/2) - 1)


int wigTotalHeight(struct track *tg, enum trackVisibility vis)
/* Wiggle track will use this to figure out the height they use
   as defined in the cart */
{
struct wigCartOptions *wigCart;
int saveHeight = tg->height;

wigCart = (struct wigCartOptions *) tg->wigCartData;

/*
 *      A track is just one
 *      item, so there is nothing to do here, either it is the tvFull
 *	height as chosen by the user from TrackUi, or it is the dense
 *	mode.
 *  ADDENDUM: wiggle options for squish and pack are being added.
 */
/*	Wiggle tracks depend upon clipping.  They are reporting
 *	totalHeight artifically high by 1 so this will leave a
 *	blank area one pixel high below the track.  hgTracks will set
 *	our clipping rectangle one less than what we report here to get
 *	this accomplished.  In the meantime our actual drawing height is
 *	recorded properly in lineHeight, heightPer and height
 */
if (vis == tvDense)
    tg->lineHeight = WIG_DENSE_HEIGHT;
else if (vis == tvPack)
    tg->lineHeight = WIG_PACK_HEIGHT;
else if (vis == tvSquish)
    tg->lineHeight = WIG_SQUISH_HEIGHT;
else if (vis == tvFull)
    tg->lineHeight = max(wigCart->minHeight, wigCart->defaultHeight);

tg->heightPer = tg->lineHeight;
tg->height = tg->lineHeight;

if (saveHeight == tg->height + 1)
    {
    tg->height = saveHeight;
    return tg->height;
    }
else
    return tg->height + 1;
}

static void wigSetItemData(struct track *tg, struct wigItem *wi,
    struct wiggle *wiggle, struct hash *spans)
/* copy values from *wiggle to *wi, maintain trackSpans hash	*/
{
static char *previousFileName = (char *)NULL;
char spanName[SMALLBUF];
struct hashEl *el;
char *trackName = tg->track;

/*	Allocate trackSpans one time only, for all tracks	*/
if (! trackSpans)
    trackSpans = newHash(4);

wi->start = wiggle->chromStart;
wi->end = wiggle->chromEnd;

if ((previousFileName == (char *)NULL) ||
	differentString(previousFileName,wiggle->file))
    {
    freez(&previousFileName);
    previousFileName = cloneString(wiggle->file);
    }
wi->file = cloneString(wiggle->file);

wi->span = wiggle->span;
wi->count = wiggle->count;
wi->offset = wiggle->offset;
wi->lowerLimit = wiggle->lowerLimit;
wi->dataRange = wiggle->dataRange;
wi->validCount = wiggle->validCount;
wi->sumData = wiggle->sumData;
wi->sumSquares = wiggle->sumSquares;

/*	see if we have a spans hash for this track already */
el = hashLookup(trackSpans, trackName);
/*	no, then let's start one	*/
if ( el == NULL)
	hashAdd(trackSpans, trackName, spans);
/*	see if this span is already in our hash for this track */
safef(spanName, sizeof(spanName), "%d", wi->span);
el = hashLookup(spans, spanName);
/*	no, then add this span to the spans list for this track */
if ( el == NULL)
	hashAddInt(spans, spanName, wi->span);
}

#ifndef GBROWSE
void ctWigLoadItems(struct track *tg)
/*	load custom wiggle track data	*/
{
struct customTrack *ct;
char *row[13];
struct lineFile *lf = NULL;
struct wiggle wiggle;
struct wigItem *wiList = NULL;
int itemsLoaded = 0;
struct hash *spans = NULL;	/* Spans encountered during load */

ct = tg->customPt;

/*	Verify this is a custom track	*/
if (ct == (void *)NULL)
    errAbort("ctWigLoadItems: did not find a custom wiggle track: %s", tg->track);

/*	and should *not* be here for a database custom track	*/
if (ct->dbTrack)
    errAbort("ctWigLoadItems: this custom wiggle track is in database: %s", tg->track);

/*	Each instance of this LoadItems will create a new spans hash
 *	It will be the value included in the trackSpans hash
 */
spans = newHash(3);
/*	Each row read will be turned into an instance of a wigItem
 *	A growing list of wigItems will be the items list to return
 */
itemsLoaded = 0;

tg->items = wiList;

lf = lineFileOpen(ct->wigFile, TRUE);
while (lineFileChopNextTab(lf, row, ArraySize(row)))
    {
    wiggleStaticLoad(row, &wiggle);
    /*  we have to do hRangeQuery's job here since we are reading a
     *  file.  We need to be on the correct chromosome, and the data
     *	needs to be in the current view.
     */
    if (sameWord(chromName,wiggle.chrom))
	{
	if ((winStart < wiggle.chromEnd) && (winEnd > wiggle.chromStart))
	    {
	    struct wigItem *wi;
	    ++itemsLoaded;
	    AllocVar(wi);
	    wigSetItemData(tg, wi, &wiggle, spans);
	    slAddHead(&wiList, wi);
	    }	/*	if in viewing window	*/
	}	/*	if in same chromosome	*/
    }	/*	while reading lines	*/

slReverse(&wiList);
tg->items = wiList;
tg->mapsSelf = TRUE;

lineFileClose(&lf);
}
#endif /* GBROWSE */

void wigLoadItems(struct track *tg)
/*      wigLoadItems - read the table rows that hRangeQuery returns
 *      With appropriate adjustment to help hRangeQuery limit its
 *	result to specific "Span" based on the basesPerPixel.
 *	From the rows returned, turn each one into a wigItem, add it to
 *	the growing wiList, and return that wiList as the tg->items.
 *
 *	To help DrawItems, we are going to make up a list of Spans that
 *	were read in for this track.  DrawItems can use that list to
 *	limit itself to the appropriate span.  (Even though we tried to
 *	use Span to limit the hRangeQuery, there may be other Spans in
 *	the result that are not needed during the drawing.)
 *	This list of spans is actually a hash of hashes.
 *	The first level is a hash of track names from each call to
 *	wigLoadItems.  The second level chosen from the track name is
 *	the actual hash of Spans for this particular track.
 *
 *	With 1K zoom Spans available, no more than approximately 1024
 *	rows will need to be loaded at any one time.
 */
{
struct sqlConnection *conn = NULL ;

// if this is a custom track we don't need an SQL connection to the database
if (!isCustomTrack(tg->table))
    conn = hAllocConn(database);
struct sqlResult *sr;
char **row;
int rowOffset;
struct wiggle wiggle;
struct wigItem *wiList = NULL;
int itemsLoaded = 0;
struct hash *spans = NULL;	/* Spans encountered during load */
/*	Check our scale from the global variables that exist in
 *	hgTracks.c - This can give us a guide about which rows to load.
 *	If the scale is more than 1000 bases per pixel, we can try loading
 *	only those rows with Span >= 1000 to see if an appropriate zoom
 *	level exists.
 */
int basesPerPixel = (int)((double)(winEnd - winStart)/(double)insideWidth);
char whereSpan[SMALLBUF];
int spanMinimum = 1;
char *dbTableName = NULL;
struct trackDb *tdb = NULL;
int loadStart = winStart, loadEnd = winEnd;

#ifndef GBROWSE
struct customTrack *ct = NULL;
/*	custom tracks have different database	*/
if (isCustomTrack(tg->table) && tg->customPt)
    {
    hFreeConn(&conn);
    conn = hAllocConn(CUSTOM_TRASH);
    ct = tg->customPt;
    dbTableName = ct->dbTableName;
    tdb = ct->tdb;
    }
else if (isCustomTrack(tg->table) )
    {
    // we can get custom tracks through track hubs that don't have customPt
    hFreeConn(&conn);
    conn = hAllocConn(CUSTOM_TRASH);
    dbTableName = trackDbSetting(tg->tdb, "dbTableName");
    tdb = tg->tdb;
    }
else
#endif /* GBROWSE */
    {
    dbTableName = tg->table;
    tdb = tg->tdb;
    }

/*	Allocate trackSpans one time only	*/
if (! trackSpans)
    trackSpans = newHash(0);

/*	find the minimum span to see if there are actually any data
 *	points in this area at that span.  If there are not, then there
 *	is no data here even if a zoomed view covers this section.
 *	protect against less than 1 with the max(1,minSpan());
 *	This business will fix the problem mentioned in RT #1186
 */

spanMinimum = max(1,
	minSpan(conn, dbTableName, chromName, winStart, winEnd, cart, tdb));

itemsLoaded = 0;
sqlSafef(whereSpan, sizeof(whereSpan), "span=%d limit 1", spanMinimum);

sr = hRangeQuery(conn, dbTableName, chromName, loadStart, loadEnd,
    whereSpan, &rowOffset);

while ((row = sqlNextRow(sr)) != NULL)
    ++itemsLoaded;
sqlFreeResult(&sr);

/*	if nothing here, bail out	*/
if (itemsLoaded < 1)
    {
    tg->items = (struct wigItem *)NULL;
    hFreeConn(&conn);
    return;
    }

itemsLoaded = 0;
if (basesPerPixel >= 1000)
    {
    sqlSafef(whereSpan, sizeof(whereSpan), "Span >= 1000 limit 1");
    sr = hRangeQuery(conn, dbTableName, chromName, loadStart, loadEnd,
	whereSpan, &rowOffset);
    while ((row = sqlNextRow(sr)) != NULL)
	    ++itemsLoaded;
    sqlFreeResult(&sr);
    }
/*	If that worked, excellent, then we have at least another zoom level
 *	So, for our actual load, use spanOver1K to fetch not only the 1K
 *	zooms, but potentially others that may be useful.  This will
 *	save us a huge amount in loaded rows.  On a 250 Mbase chromosome
 *	there would be 256,000 rows at the 1 base level and only
 *	256 rows at the 1K zoom level.  Otherwise, we go back to the
 *	regular query which will give us all rows.
 */
/* JK - Can't we figure out here one, and only one span to load?  This
 * would simplify drawing logic. */
if (itemsLoaded)
    {
    sqlSafef(whereSpan, sizeof(whereSpan), "Span >= 1000");
    sr = hRangeQuery(conn, dbTableName, chromName, loadStart, loadEnd,
	     whereSpan, &rowOffset);
    }
else
    {
    sr = hRangeQuery(conn, dbTableName, chromName, loadStart, loadEnd,
	NULL, &rowOffset);
    }

/*	Allocate trackSpans one time only	*/
if (! trackSpans)
    trackSpans = newHash(4);
/*	Each instance of this LoadItems will create a new spans hash
 *	It will be the value included in the trackSpans hash
 */
spans = newHash(4);
/*	Each row read will be turned into an instance of a wigItem
 *	A growing list of wigItems will be the items list to return
 */
itemsLoaded = 0;
while ((row = sqlNextRow(sr)) != NULL)
    {
    struct wigItem *wi;
    ++itemsLoaded;
    wiggleStaticLoad(row + rowOffset, &wiggle);
    AllocVar(wi);
    wigSetItemData(tg, wi, &wiggle, spans);
    slAddHead(&wiList, wi);
    }

sqlFreeResult(&sr);
hFreeConn(&conn);

slReverse(&wiList);
tg->items = wiList;
}	/*	wigLoadItems()	*/

static void wigFreeItems(struct track *tg) {
#if defined(DEBUG)
safef(dbgMsg, DBGMSGSZ, "I haven't seen wigFreeItems ever called ?");
wigDebugPrint("wigFreeItems");
#endif
}

struct preDrawContainer *initPreDrawContainer(int width)
/* Initialize preDraw of given size */
{
struct preDrawContainer *pre;
AllocVar(pre);
int size = pre->preDrawSize = width + 2*wiggleSmoothingMax;	
pre->preDrawZero = wiggleSmoothingMax;
pre->width = width;
struct preDrawElement *preDraw = AllocArray(pre->preDraw, pre->preDrawSize);
int i;
for (i = 0; i < size; ++i)
    {
    preDraw[i].count = 0;
    preDraw[i].max = wigEncodeStartingUpperLimit;
    preDraw[i].min = wigEncodeStartingLowerLimit;
    }
return pre;
}

double wiggleLogish(double x)
/* Return log-like transform without singularity at 0. */
{
if (x >= 0)
    return log(1+x);
else
    return -log(1-x);
}

static double doTransform(double x, enum wiggleTransformFuncEnum transformFunc)
/* Do log-type transformation if asked. */
{
if (transformFunc == wiggleTransformFuncLog)
    {
    x = wiggleLogish(x);
    }
return x;
}


void preDrawWindowFunction(struct preDrawElement *preDraw, int preDrawSize,
	enum wiggleWindowingEnum windowingFunction,
	enum wiggleTransformFuncEnum transformFunc,
	boolean doNegative)
/*	apply windowing function to the values in preDraw array	*/
{
int i;

/*	Determine the raw plotting value	*/
for (i = 0; i < preDrawSize; ++i)
    {
    double dataValue;
    if (preDraw[i].count)
	{
	switch (windowingFunction)
	    {
	    case wiggleWindowingMin:
		if (fabs(preDraw[i].min)
				< fabs(preDraw[i].max))
		    dataValue = preDraw[i].min;
                else
		    dataValue = preDraw[i].max;
		break;
	    case wiggleWindowingSum:
		dataValue = preDraw[i].sumData;
                break;
	    case wiggleWindowingMean:
	    case wiggleWindowingWhiskers:
		dataValue =
		    preDraw[i].sumData / preDraw[i].count;
		break;
	    default:
	    case wiggleWindowingMax:
		if (fabs(preDraw[i].min)
			> fabs(preDraw[i].max))
		    dataValue = preDraw[i].min;
                else
		    dataValue = preDraw[i].max;
		break;
	    }

	dataValue = doTransform(dataValue, transformFunc);
	if (doNegative)
	    {
	    dataValue = -dataValue;
	    int swap = preDraw[i].min;
	    preDraw[i].min = -preDraw[i].max;
	    preDraw[i].max = -swap;
	    }
	preDraw[i].plotValue = dataValue;
	preDraw[i].smooth = dataValue;
	}
    }
}

void preDrawSmoothing(struct preDrawElement *preDraw, int preDrawSize,
    enum wiggleSmoothingEnum smoothingWindow)
/*      apply smoothing function to preDraw array       */
{
/*      Are we perhaps doing smoothing ?  smoothingWindow is 1 off due
 *      to enum funny business in inc/hui.h and lib/hui.c       */
if (smoothingWindow > 0)
    {
    int winSize = smoothingWindow + 1; /* enum funny business */
    int winBegin = 0;
    int winMiddle = -(winSize/2);
    int winEnd = -winSize;
    double sum = 0.0;
    unsigned long long points = 0LL;

    for (winBegin = 0; winBegin < preDrawSize; ++winBegin)
	{
	if (winEnd >=0)
	    {
	    if (preDraw[winEnd].count)
		{
		points -= preDraw[winEnd].count;
		sum -= preDraw[winEnd].plotValue * preDraw[winEnd].count;
		}
	    }
	if (preDraw[winBegin].count)
	    {
	    points += preDraw[winBegin].count;
	    sum += preDraw[winBegin].plotValue * preDraw[winBegin].count;
	    }
	if ((winMiddle >= 0) && points && preDraw[winMiddle].count)
		preDraw[winMiddle].smooth = sum / points;
	++winEnd;
	++winMiddle;
	}
    }
}

double preDrawAutoScale(struct preDrawElement *preDraw, int preDrawZero,
    int width, enum wiggleScaleOptEnum autoScale,
    enum wiggleWindowingEnum windowingFunction,
    double *graphUpperLimit, double *graphLowerLimit,
    double *epsilon, int lineHeight,
    double maxY, double minY, enum wiggleAlwaysZeroEnum alwaysZero)
/*	if autoScaling, scan preDraw array and determine limits */
{
if ((autoScale == wiggleScaleAuto) || (autoScale == wiggleScaleCumulative))
    {
    double overallUpperLimit = wigEncodeStartingUpperLimit;
    double overallLowerLimit = wigEncodeStartingLowerLimit;
    int i, lastI = preDrawZero+width;

    /* reset limits for auto scale */
    for (i = preDrawZero; i < lastI; ++i)
	{
	/*	count is non-zero meaning valid data exists here	*/
	if (preDraw[i].count)
	    {
	    double val =  preDraw[i].smooth;
	    if (windowingFunction ==  wiggleWindowingWhiskers)
		val =  preDraw[i].max;
	    if (val > overallUpperLimit)
		overallUpperLimit = val;

	    if (windowingFunction ==  wiggleWindowingWhiskers)
		val =  preDraw[i].min;
	    if (val < overallLowerLimit)
		overallLowerLimit = val;
	    }
	}
    if (alwaysZero == wiggleAlwaysZeroOn)
	{
	if ( overallUpperLimit < 0)
	    overallUpperLimit = 0.0;
	else if ( overallLowerLimit > 0)
	    overallLowerLimit = 0.0;
	}
    double overallRange = overallUpperLimit - overallLowerLimit;
    if (overallRange == 0.0)
	{
	if (overallUpperLimit > 0.0)
            {
            *graphUpperLimit = overallUpperLimit;
            *graphLowerLimit = 0.0;
            }
        else if (overallUpperLimit < 0.0)
            {
            *graphUpperLimit = 0.0;
            *graphLowerLimit = overallUpperLimit;
            }
        else
            {
            *graphUpperLimit = 1.0;
            *graphLowerLimit = -1.0;
            }
        }
    else
        {
        *graphUpperLimit = overallUpperLimit;
        *graphLowerLimit = overallLowerLimit;
        }
    }
else
    {
    *graphUpperLimit = maxY;
    *graphLowerLimit = minY;
    }

double graphRange = *graphUpperLimit - *graphLowerLimit;
*epsilon = graphRange / lineHeight;
return(graphRange);
}

static Color * makeColorArray(struct preDrawElement *preDraw, int width,
    int preDrawZero, struct wigCartOptions *wigCart, struct track *tg, struct hvGfx *hvg)
/*	allocate and fill in a coloring array based on another track */
{
char *colorTrack = wigCart->colorTrack;
int x1;
Color *colorArray = NULL;       /*      Array of pixels to be drawn.    */

/*      Set up the color by array. Determine color of each pixel
 *      based initially on the sign of the data point. If a colorTrack
 *      is specified also fill in the color array with that.
 */
AllocArray(colorArray, width);
for(x1 = 0; x1 < width; ++x1)
    {
    int preDrawIndex = x1 + preDrawZero;
    if (preDraw[preDrawIndex].count)
	{
	double dataValue;	/*	the data value in data space	*/

	dataValue = preDraw[preDrawIndex].smooth;
	/*	negative data is the alternate color	*/
	if (dataValue < 0.0)
	    colorArray[x1] = tg->ixAltColor;
	else
	    colorArray[x1] = tg->ixColor;
	}
    }

/* Fill in colors from alternate track if necessary. */
if (colorTrack != NULL)
    {
    struct track *cTrack = hashFindVal(trackHash, colorTrack);
    if (cTrack == NULL) // rightClick update of wigColorBy track may not have colorTrack in hash
        {               // so create it on the fly
        struct trackDb *tdb = hTrackDbForTrack(database,colorTrack);
        if (tdb != NULL)
            cTrack = trackFromTrackDb(tdb);
        }
    if (cTrack != NULL)
        wigFillInColorArray(tg, hvg, colorArray, width, cTrack);
    }

return colorArray;
}

void vLineViaHvg(void *image, int x, int y, int height, Color color)
/* A vertical line drawer that works via hvGfx system. */
{
hvGfxBox(image, x, y, 1, height, color);
}

Color somewhatLighterColor32(Color color)
/* Get a somewhat lighter shade of a color - 1/3 of the way towards white.
 * Specialized here to bypass image parameter requirement.*/
{
struct rgbColor rgbColor =  mgColorIxToRgb(NULL, color);
rgbColor.r = (2*rgbColor.r+255)/3;
rgbColor.g = (2*rgbColor.g+255)/3;
rgbColor.b = (2*rgbColor.b+255)/3;
return MAKECOLOR_32(rgbColor.r, rgbColor.g, rgbColor.b);
}

struct wigGraphOutput *wigGraphOutputStack(int xOff, int yOff,  int width, int numTracks, struct hvGfx *image)
/* Get appropriate wigGraphOutput for non-transparent stacked rendering */
{
struct wigGraphOutput *wgo;
AllocVar(wgo);
wgo->image = image;
wgo->vLine = vLineViaHvg;
wgo->xOff = xOff;
wgo->yOff = yOff;
if (numTracks)
    wgo->yOffsets = needHugeMem(width * numTracks * sizeof(double));
return wgo;
}

struct wigGraphOutput *wigGraphOutputSolid(int xOff, int yOff, struct hvGfx *image)
/* Get appropriate wigGraphOutput for non-transparent rendering */
{
struct wigGraphOutput *wgo;
AllocVar(wgo);
wgo->image = image;
wgo->vLine = vLineViaHvg;
wgo->xOff = xOff;
wgo->yOff = yOff;
return wgo;
}

struct wigMouseOver *getMouseOverData(struct track *tg, struct preDrawElement *preDraw, int width, int xOff, int preDrawZero)
/* Calculate the mouseOver data. */
{
int x1;
boolean skipMouseOvers = TRUE;	/* assuming not using */
boolean dropMouseOverData = FALSE;	// will become TRUE if noAverage
int mouseOverX2 = -1;
struct wigMouseOver *mouseOverData = NULL;
double previousValue = 0;
char *mouseOverFunction = trackDbSetting(tg->tdb, "mouseOverFunction");
boolean noAverage = FALSE;
if (sameOk(mouseOverFunction, "noAverage"))
    noAverage = TRUE;

if (enableMouseOver)
    skipMouseOvers = FALSE;
					// condition is encountered
/* ===== mouseOver calculations===== */
for (x1 = 0; x1 < width; ++x1)
    {
    //int x = x1 + xOff;
    int preDrawIndex = x1 + preDrawZero;
    struct preDrawElement *p = &preDraw[preDrawIndex];
    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->smooth;
                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;
    }
if (dropMouseOverData)
    slFreeList(&mouseOverData);

return mouseOverData;
}

struct wigMouseOver *graphPreDraw(struct preDrawElement *preDraw, int preDrawZero, int width,
    struct track *tg, void *image, WigVerticalLineVirtual vLine, int xOff, int yOff, double *yOffsets, int numTrack,
    double graphUpperLimit, double graphLowerLimit, double graphRange,
    double epsilon, Color *colorArray, enum trackVisibility vis,
    struct wigCartOptions *wigCart, struct pixelCountBin *pixelBins)
/*	graph the preDraw array, returns mouse over data, or NULL */
{
int x1;
int h = tg->lineHeight;	/*	the height of our drawing window */
double scaleFactor = h/graphRange;
Color oldDrawColor = colorArray[0] + 1;	/* Just to be different from 1st drawColor. */
Color mediumColor = MG_BLACK;	// Will be overriden
Color lightColor = MG_BLACK;	// Will be overriden
Color clipColor = MG_MAGENTA;
enum wiggleTransformFuncEnum transformFunc = wigCart->transformFunc;
enum wiggleGraphOptEnum lineBar = wigCart->lineBar;
boolean whiskers = (wigCart->windowingFunction == wiggleWindowingWhiskers
			&& width < winEnd-winStart);
	/* list of mouse over data, if created here */

struct wigMouseOver *mouseOverData = getMouseOverData(tg, preDraw, width, xOff, preDrawZero);

/*	right now this is a simple pixel by pixel loop.  Future
 *	enhancements could draw boxes where pixels
 *	are all the same height in a run.
 */
for (x1 = 0; x1 < width; ++x1)
    {
    int x = x1 + xOff;
    int preDrawIndex = x1 + preDrawZero;
    struct preDrawElement *p = &preDraw[preDrawIndex];

    assert(x1/pixelBins->binSize < pixelBins->binCount);
    unsigned long *bitCount = &pixelBins->bins[x1/pixelBins->binSize];

    Color drawColor = colorArray[x1];
    if (drawColor != oldDrawColor)
        {
	mediumColor = somewhatLighterColor32(drawColor);
	lightColor = somewhatLighterColor32(mediumColor);
	oldDrawColor = drawColor;
        }

    /*	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); }
	    if (lineBar == wiggleGraphBar)
		{
		if (whiskers)
		    {
		    int zeroPos = max(0,scaleHeightToPixels(0));
		    int scaledVal = scaleHeightToPixels(dataValue);
		    double std = calcStdFromSums(p->sumData, p->sumSquares, p->count);
		    double mean = p->sumData/p->count;

		    if (dataValue < 0)
		        {
			int scaledMin = scaleHeightToPixels(doTransform(p->min, transformFunc));
			int lightHeight = max(1,scaledMin-zeroPos);
			int mediumHeight = lightHeight;
			if (!isnan(std))
			    { // Test needed due to bug in version 1.5 bigWiles
			    double minus = doTransform(mean - std, transformFunc);
			    int scaledMinus = scaleHeightToPixels(minus);
			    mediumHeight = max(1,scaledMinus-zeroPos);
			    }
			int darkHeight = max(1,scaledVal-zeroPos);
			if (zeroPos == (yOff+h))  // bottom pixel special case
			    zeroPos -= 1;
		        if (((zeroPos-yOff)+darkHeight) == 0)
			    darkHeight += 1;	  // top pixel special case
			doLine(image, x,zeroPos, darkHeight, drawColor);
                        doLine(image, x, zeroPos+darkHeight, mediumHeight-darkHeight,
				mediumColor);
                        doLine(image, x, zeroPos+mediumHeight, lightHeight-mediumHeight,
				lightColor);
			}
		    else
		        {
                        /* The calculations here are a little convoluted because
                         * of the history.  Originally it drew from the baseline
                         * up to the max first in the lightest color, then from the
                         * baseline to the mean+std in medium color, and finally
                         * from baseline to mean in dark color.  This ended up
                         * drawing the same pixels up to three times which messed
                         * things up in transparent overlay mode.   The code was
                         * refactored to accomplish this without having to worry
                         * about +/- 1 differences.   In particular be aware the
                         * xyzHeight calculations are done assuming the other end is
                         * the baseline. */

                        /* Calculate dark part from smoothed mean. */
                        int boxHeight = max(1,zeroPos - scaledVal);
                        if (scaledVal == (yOff+h))  // bottom pixel special case
			    scaledVal -= 1;
		        if (((scaledVal-yOff)+boxHeight) == 0)
			    boxHeight += 1;	    // top pixel special case
			int darkTop = scaledVal, darkHeight = boxHeight;

			/* Calculate medium part from smoothed mean + std */
			int mediumTop = darkTop, mediumHeight = darkHeight;
			if (!isnan(std))
			    { // Test needed due to bug in version 1.5 bigWiles
			    double plus = doTransform(mean + std, transformFunc);
			    int scaledPlus = scaleHeightToPixels(plus);
			    int boxHeight = max(1,zeroPos-scaledPlus);
			    mediumTop = scaledPlus, mediumHeight = boxHeight;
			    }

			/* Calculate light part from max. */
			int scaledMax = scaleHeightToPixels(doTransform(p->max, transformFunc));
			if (scaledMax == (h+yOff))
                            scaledMax = (h+yOff) - 1;
                        boxHeight = max(1,zeroPos-scaledMax);
                        int lightTop = scaledMax, lightHeight = boxHeight;

                        /* Draw, making sure not to overwrite pixels since
                         * would mess up transparent drawing. */
                        doLine(image,x,darkTop, darkHeight, drawColor);
			doLine(image, x, mediumTop, mediumHeight-darkHeight, mediumColor);
			doLine(image,x,lightTop,lightHeight-mediumHeight, lightColor);
			}
		    }
		else
		    {
		    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
		    if (((boxTop+boxHeight) == 0) && !isnan(dataValue))
			boxHeight += 1;
		    doLine(image,x, yOff+boxTop, boxHeight, drawColor);
		    }
		}
	    else
		{	/*	draw a 3 pixel height box	*/
		if (whiskers)
		    {
		    int scaledMin = scaleHeightToPixels(doTransform(p->min, transformFunc));
		    int scaledMax = scaleHeightToPixels(doTransform(p->max, transformFunc));
		    double mean = p->sumData/p->count;
		    int boxHeight = max(1,scaledMin - scaledMax);
		    doLine(image, x, scaledMax, boxHeight, lightColor);
		    int scaledMean = scaleHeightToPixels(dataValue);
		    double std = calcStdFromSums(p->sumData, p->sumSquares, p->count);
		    if (!isnan(std))  // Test needed because of bug in version 1.5 bigWiles
			{
			int scaledPlus = scaleHeightToPixels(doTransform(mean+std, transformFunc));
			int scaledMinus = scaleHeightToPixels(doTransform(mean-std, transformFunc));
			int boxHeight = max(1,scaledMinus - scaledPlus);
			doLine(image, x, scaledPlus, boxHeight, mediumColor);
			}
		    doLine(image, x, scaledMean, 1, drawColor);
		    }
		else
		    {
		    double y0 = dataValue;
		    if ((yOffsets != NULL) && (numTrack > 0))
			y0 += yOffsets[(numTrack-1) *  width + x1];
		    int yPointGraph = scaleHeightToPixels(y0) - 1;
		    doLine(image, x, yPointGraph, 3, drawColor);
		    }
		}
	    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 */
        else if (vis == tvDense || vis == tvSquish)
	    {
	    double grayValue;
	    int grayIndex;
	    /* honor the viewLimits, data below is white, data above is black */
	    grayValue = max(dataValue,graphLowerLimit);
	    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)	*/

return(mouseOverData);
}	/*	graphPreDraw()	*/

struct probVal // the probability of a certain nucleotide being seen in a column
    {
    struct probVal *next;
    char *nuc;
    double prob;
    unsigned long color;
    };

struct probVal probVals[4]; // one per nucleotide
struct probVal *probList = probVals;  // a sortable list 

static int probListCmp(const void *va, const void *vb)
/* Compare probabilities for sorting. */
{
const struct probVal *a = *((struct probVal **)va);
const struct probVal *b = *((struct probVal **)vb);
    
if (a->prob < b->prob)
    return 1;
return -1;
}


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)
{
boolean baseCmpl = cartUsualBooleanDb(cart, database, COMPLEMENT_BASES_VAR, FALSE);
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);
if (baseCmpl)
    complement(seq->dna, seq->size);

struct pixelCountBin *pixelBins = wgo->pixelBins;
double *yOffsets = wgo->yOffsets;
int numTrack = wgo->numTrack;
Color clipColor = MG_MAGENTA;
#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;
struct wigMouseOver *mouseOverData = getMouseOverData(tg, preDraw, width, xOff, preDrawZero);

struct mafBaseProbs *baseProbs = wigCart->baseProbs;
if (baseProbs != NULL)
    {
    // initialize our nucleotide probability data structure
    probVals[0].next = &probVals[1];
    probVals[1].next = &probVals[2];
    probVals[2].next = &probVals[3];
    probVals[3].next = NULL;

    boolean baseCmpl = cartUsualBooleanDb(cart, database, COMPLEMENT_BASES_VAR, FALSE);
    if (baseCmpl)
        {
        probVals[3].nuc = "A";
        probVals[3].color = MG_RED;
        probVals[2].nuc = "C";
        probVals[2].color = MG_BROWN;
        probVals[1].nuc = "G";
        probVals[1].color = MG_BLUE;
        probVals[0].nuc = "T";
        probVals[0].color = MG_GREEN;
        }
    else
        {
        probVals[0].nuc = "A";
        probVals[0].color = MG_RED;
        probVals[1].nuc = "C";
        probVals[1].color = MG_BROWN;
        probVals[2].nuc = "G";
        probVals[2].color = MG_BLUE;
        probVals[3].nuc = "T";
        probVals[3].color = MG_GREEN;
        }
    probList = probVals;  // a sortable list 
    }

double xIncr = (double)width / numBases;
int baseWidth = xIncr;
int letterWidth = baseWidth - 1;
if (h < letterWidth)
    letterWidth = h;
unsigned baseNum;

for(baseNum = 0; baseNum < numBases; baseNum++)
    {
    int x1 = ceil(baseNum * xIncr);
    int x = x1 + xOff;
    int base = seq->dna[baseNum];
    // grab our data value from the middle of a zoomed-in region because the edges may be rounded off 
    // by the math function
    int preDrawIndex = ceil(x1 + xIncr/2)  + preDrawZero; 
    struct preDrawElement *p = &preDraw[preDrawIndex];

    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)
            {
            double origDataValue = dataValue;  // save our original data value to draw the "clipped" lines
#define scaleHeightToPixels(val) (min(BIGNUM,(scaleFactor * (graphUpperLimit - (val)) + yOff)))
#define doLine(image, x, y, height, color) {vLine(image, x, y, height, color); }
                {
                // since we're drawing a sequence logo, we want to scale by the part of the line within the clipping window
                if (dataValue > graphUpperLimit)
                    dataValue = graphUpperLimit;
                else if (dataValue < graphLowerLimit)
                    dataValue = graphLowerLimit;
                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));
                // if our viewing region is clipped on the lower end we need to shrink
                // the box to fit all the letters into it
                if (graphLowerLimit > 0 ) 
                    boxHeight -=  graphLowerLimit * scaleFactor;
                int boxTop = min(y1,y0);
                if (graphUpperLimit < 0 )
                    {
                    // if our viewing region is clipped on the upper end we need to shrink
                    // the box AND lower the bottom of the box to fit all the letters into it
                    boxHeight +=  graphUpperLimit * scaleFactor;
                    boxTop -=  graphUpperLimit * scaleFactor;
                    }

                //	positive data value exactly equal to Bottom pixel
                //  make sure it draws at least a pixel there
                if (boxTop == h)
                    boxTop = h - 1;

                unsigned color = MG_BLACK;

                char *setting;
                if (tg->tdb->parent && ((setting = trackDbSetting(tg->tdb->parent,"container")) != NULL) && startsWith("multiWig", setting))
                    {
                    /* for multiwig display, use track colors */
                    if (dataValue < 0)
                        color = tg->ixAltColor;
                    else
                        color = tg->ixColor;

                    switch(numTrack)
                        {
                        case 3: if (baseCmpl) base='t'; else base='a';break;
                        case 2: if (baseCmpl) base='g'; else base='c';break;
                        case 1: if (baseCmpl) base='c'; else base='g';break;
                        case 0: if (baseCmpl) base='a'; else base='t';break;
                        }
                    }
                else  /* hard-coded colors for dynSeq display */
                    {
                    if (base == 'a')
                        color = MG_RED;
                    else if (base == 't')
                        color = MG_GREEN;
                    else if (base == 'c')
                        color = MG_BROWN;
                    else if (base == 'g')
                        color = MG_BLUE;
                    }

                char string[2];
                string[0] = toupper(base);
                string[1] = 0;
                MgFont *font = tl.font;
                if (boxHeight != 0)
                    {
                    if (baseProbs)
                        {
                        int thisHeight;

                        // we want a sorted list so the most probable gets drawn on top
                        probVals[0].prob = baseProbs[baseNum].aProb;
                        probVals[1].prob = baseProbs[baseNum].cProb;
                        probVals[2].prob = baseProbs[baseNum].gProb;
                        probVals[3].prob = baseProbs[baseNum].tProb;
                        slSort(&probList,probListCmp);

                        int y = yOff+boxTop;
                        struct probVal *pl = probList;
                        for(; pl; pl = pl->next)
                            {
                            thisHeight = pl->prob * boxHeight;
                            if (thisHeight)
                                hvGfxTextInBox(hvg, x + (baseWidth - letterWidth)/2, y, letterWidth, thisHeight,
                                    pl->color, font, pl->nuc);
                            y += thisHeight;
                            }
                        }
                    else
                        {
                        if (dataValue < 0)
                            {
                            hvGfxTextInBox(hvg, x + (baseWidth - letterWidth)/2, yOff+boxTop, letterWidth, -boxHeight,
                                color, font, string);
                            }
                        else
                            {
                            hvGfxTextInBox(hvg, x + (baseWidth - letterWidth)/2, yOff+boxTop, letterWidth, boxHeight,
                                color, font, string);
                            }
                        }
                    }
                if (((boxTop+boxHeight) == 0) && !isnan(dataValue))
                    boxHeight += 1;
                }
	    double stackValue = origDataValue;

	    //if ((yOffsets != NULL) && (numTrack > 0))
		//stackValue += yOffsets[(numTrack-1) *  width + x1];
	    if (stackValue > graphUpperLimit)
                {
                hvGfxLine(hvg, x, yOff, x+baseWidth, yOff, clipColor);
                }
	    else if (stackValue < graphLowerLimit)
                {
                hvGfxLine(hvg, x, yOff + h - 1, x+baseWidth, yOff + h - 1, clipColor);
                }
#undef scaleHeightToPixels	/* No longer use this symbol */
            }   /*	vis == tvFull || vis == tvPack */
        }
    }	/*	for (x1 = 0; x1 < width; ++x1)	*/

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);
return mouseOverData;
}

void drawZeroLine(enum trackVisibility vis,
    enum wiggleGridOptEnum horizontalGrid,
    double graphUpperLimit, double graphLowerLimit,
    struct hvGfx *hvg, int xOff, int yOff, int width, int lineHeight)
/*	draw a line at y=0 on the graph	*/
{
/*	Do we need to draw a zero line ?
 *	This is to be generalized in the future to allow horizontal grid
 *	lines, perhaps user specified to indicate thresholds.
 */
if ((vis == tvFull) && (horizontalGrid == wiggleHorizontalGridOn))
    {
    Color black = hvGfxFindColorIx(hvg, 0, 0, 0);
    int x1, x2, y1, y2;

    x1 = xOff;
    x2 = x1 + width;

    /*	Let's see if the zero line can be drawn	*/
    if ((0.0 <= graphUpperLimit) && (0.0 >= graphLowerLimit))
	{
	int zeroOffset;

	zeroOffset = (int)((graphUpperLimit * lineHeight) /
			(graphUpperLimit - graphLowerLimit));
	y1 = yOff + zeroOffset;
	if (y1 >= (yOff + lineHeight)) y1 = yOff + lineHeight - 1;
	y2 = y1;
	hvGfxLine(hvg,x1,y1,x2,y2,black);
	}

    }	/*	drawing horizontalGrid	*/
}	/*	drawZeroLine()	*/

void drawArbitraryYLine(enum trackVisibility vis,
    enum wiggleGridOptEnum horizontalGrid,
    double graphUpperLimit, double graphLowerLimit,
    struct hvGfx *hvg, int xOff, int yOff, int width, int lineHeight,
    double yLineMark, double graphRange, enum wiggleYLineMarkEnum yLineOnOff)
/*	draw a line at y=yLineMark on the graph	*/
{
/*	Optionally, a user requested Y marker line at some value */
if ((vis == tvFull) && (yLineOnOff == wiggleYLineMarkOn))
    {
    int x1, x2, y1, y2;
    Color black = hvGfxFindColorIx(hvg, 0, 0, 0);

    x1 = xOff;
    x2 = x1 + width;

    /*	Let's see if this marker line can be drawn	*/
    if ((yLineMark <= graphUpperLimit) && (yLineMark >= graphLowerLimit))
	{
	int Offset;

	Offset = lineHeight * ((graphUpperLimit - yLineMark)/graphRange);
	y1 = yOff + Offset;
	if (y1 >= (yOff + lineHeight)) y1 = yOff + lineHeight - 1;
	y2 = y1;
	hvGfxLine(hvg,x1,y1,x2,y2,black);
	}

    }	/*	drawing y= line marker	*/
}	/*	drawArbitraryYLine()	*/

void wigMapSelf(struct track *tg, struct hvGfx *hvg, int seqStart, int seqEnd,
    int xOff, int yOff, int width)
/*	if self mapping, create the mapping box	*/
{
/*	Map this wiggle area if we are self mapping	*/
if (tg->mapsSelf)
    {
    char *itemName;
#ifndef GBROWSE
    if (isCustomTrack(tg->table) && tg->customPt)
	{
	struct customTrack *ct = tg->customPt;
	itemName = (char *)needMem(LARGEBUF * sizeof(char));
	safef(itemName, LARGEBUF, "%s %s", ct->wigFile, tg->track);
	}
    else
#endif /* GBROWSE */
    itemName = cloneString(tg->track);

    // Don't bother if we are imageV2 and a dense child.
    if (!theImgBox || tg->limitedVis != tvDense || !tdbIsCompositeChild(tg->tdb))
        {
        char *title = NULL;
        if (trackDbSetting(tg->tdb, "hoverMetadata"))
            title = trackDbSetting(tg->tdb, "metadata");
        mapBoxHc(hvg, seqStart, seqEnd, xOff, yOff, width, tg->height, tg->track,
                        itemName, title);
        }
    freeMem(itemName);
    }
}

int wigFindSpan(struct track *tg, double basesPerPixel)
/* Return span to use at this scale */
{
int usingDataSpan = 1;
int minimalSpan = 100000000;	/*	a lower limit safety check */
struct hashEl *el, *elList;

/*	Take a look through the potential spans, and given what we have
 *	here for basesPerPixel, pick the largest usingDataSpan that is
 *	not greater than the basesPerPixel
 */
el = hashLookup(trackSpans, tg->track);	/*  What Spans do we have */
elList = hashElListHash(el->val);		/* Our pointer to spans hash */
for (el = elList; el != NULL; el = el->next)
    {
    int Span;
    Span = ptToInt(el->val);
    if ((Span < basesPerPixel) && (Span > usingDataSpan))
	usingDataSpan = Span;
    if (Span < minimalSpan)
	minimalSpan = Span;
    }
hashElFreeList(&elList);

/*	There may not be a span of 1, use whatever is lowest	*/
if (minimalSpan > usingDataSpan)
    usingDataSpan = minimalSpan;

return usingDataSpan;
}

void wigTrackSetGraphOutputDefault(struct track *tg, int xOff, int yOff, int width, struct hvGfx *hvg)
/* Set up to draw on hvg if no other destination set already */
{
if (tg->wigGraphOutput == NULL)
    {
    tg->wigGraphOutput = wigGraphOutputSolid(xOff, yOff, hvg);
void AllocPixelBins(struct wigGraphOutput *wgo, int width);
    AllocPixelBins(tg->wigGraphOutput, width);
    }
}

static void setMinMax(struct track *tg, double graphLowerLimit, double graphUpperLimit)
// We need to check to see if this track changes our min/max for all tracks with this parent.
// We use tdb entry to store this because it may be per view, which has been smashed out in track list
{
struct trackDb *parent = tg->tdb->parent;
struct tdbExtras *extras = parent->tdbExtras;

if (extras == NULL)
    {
    AllocVar(extras);
    parent->tdbExtras = extras;
    }

struct minMax *minMax = extras->minMax;
if (minMax == NULL)
    {
    AllocVar(minMax);
    extras->minMax = minMax;
    minMax->min = graphLowerLimit;
    minMax->max = graphUpperLimit;
    }
else
    {
    if (minMax->min > graphLowerLimit)
        minMax->min = graphLowerLimit;
    if (minMax->max < graphUpperLimit)
        minMax->max = graphUpperLimit;
    }
}

void wigPreDrawPredraw(struct track *tg, int seqStart, int seqEnd,
                    struct hvGfx *hvg, int xOff, int yOff, int width,
                    MgFont *font, Color color, enum trackVisibility vis,
                    struct preDrawContainer *preContainer, int preDrawZero,
                    int preDrawSize, double *retGraphUpperLimit, double *retGraphLowerLimit)
/* Figure out graph limits after running windowingFunction and smoothing if needed.
 * This code is shared by wig, bigWig, and bedGraph drawers. */
{

/*	determined from data	*/
double graphUpperLimit=0;	/*	scaling choice will set these	*/
double graphLowerLimit=0;	/*	scaling choice will set these	*/
double epsilon;			/*	range of data in one pixel	*/
struct wigCartOptions *wigCart = (struct wigCartOptions *) tg->wigCartData;

/*	width - width of drawing window in pixels
 *	pixelsPerBase - pixels per base
 *	basesPerPixel - calculated as 1.0/pixelsPerBase
 */

struct preDrawElement *preDraw = preContainer->preDraw;

preDrawWindowFunction(preDraw, preDrawSize, wigCart->windowingFunction,
	wigCart->transformFunc, wigCart->doNegative);
preDrawSmoothing(preDraw, preDrawSize, wigCart->smoothingWindow);
if (!preContainer->skipAutoscale) // multiWig does own autoscaling
    {
    preDrawAutoScale(preDraw, preDrawZero, width,
	wigCart->autoScale, wigCart->windowingFunction,
	&preContainer->graphUpperLimit, &preContainer->graphLowerLimit,
	&epsilon, tg->lineHeight,
	wigCart->maxY, wigCart->minY, wigCart->alwaysZero);
    }

graphUpperLimit = preContainer->graphUpperLimit;
graphLowerLimit = preContainer->graphLowerLimit;

if (retGraphUpperLimit != NULL)
    *retGraphUpperLimit = graphUpperLimit;
if (retGraphLowerLimit != NULL)
    *retGraphLowerLimit = graphLowerLimit;

if (sameString(tg->tdb->type, "mathWig") && (wigCart->autoScale == wiggleScaleCumulative))
    wigCart->autoScale = wiggleScaleAuto;

if (wigCart->autoScale == wiggleScaleCumulative)
    setMinMax(tg, graphLowerLimit, graphUpperLimit);
}

void wigDrawPredraw(struct track *tg, int seqStart, int seqEnd,
                    struct hvGfx *hvg, int xOff, int yOff, int width,
                    MgFont *font, Color color, enum trackVisibility vis,
                    struct preDrawContainer *preContainer, int preDrawZero,
                    int preDrawSize, double graphUpperLimit, double graphLowerLimit)
/* Draw once we've figured out predraw (numerical values to graph) we draw it here.
 * This code is shared by wig, bigWig, and bedGraph drawers. */
{
struct wigCartOptions *wigCart = (struct wigCartOptions *) tg->wigCartData;
double graphRange;   /*	scaling choice will set this */

// if we want the cumulative autoscale, grab it from our parent
if (wigCart->autoScale == wiggleScaleCumulative)
    {
    tg->graphLowerLimit = graphLowerLimit = tg->tdb->parent->tdbExtras->minMax->min;
    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 = NULL;
if (zoomedToCodonLevel && doLogo(tg) && vis != tvDense && vis != tvSquish)
    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);
    jsonWriteString(mouseOverJson, "t", tg->tdb->type);
    jsonWriteListStart(mouseOverJson, "d");
    slReverse(&mouseOverData);
    struct wigMouseOver *dataItem = mouseOverData;
    for (; dataItem; dataItem = dataItem->next)
        {
        jsonWriteObjectStart(mouseOverJson, NULL);
        jsonWriteNumber(mouseOverJson, "x1", (long long)dataItem->x1);
        jsonWriteNumber(mouseOverJson, "x2", (long long)dataItem->x2);
        jsonWriteDouble(mouseOverJson, "v", dataItem->value);
        jsonWriteNumber(mouseOverJson, "c", dataItem->valueCount);
        jsonWriteObjectEnd(mouseOverJson);
        }
    jsonWriteListEnd(mouseOverJson);
    jsonWriteObjectEnd(mouseOverJson);
    slFreeList(&mouseOverData);
    // hidden element to pass along jsonUrl file name and also the trigger
    // that this track has data to display.
    hPrintf("<div id='mouseOver_%s' name='%s' class='hiddenText mouseOverData' jsonUrl='%s'></div>\n", tg->track, tg->track, mouseOverJsonFile->forCgi);
    }
else if (enableMouseOver)
    {
    jsonWriteObjectStart(mouseOverJson, tg->track);
    jsonWriteString(mouseOverJson, "t", tg->tdb->type);
    jsonWriteString(mouseOverJson, "mo", "noAverage");
    jsonWriteObjectEnd(mouseOverJson);
    }

wigMapSelf(tg, hvg, seqStart, seqEnd, xOff, yOff, width);
}	/*	void wigDrawPredraw()	*/

struct preDrawContainer *wigLoadPreDraw(struct track *tg, int seqStart, int seqEnd, int width)
/* Do bits that load the predraw buffer tg->preDrawContainer. */
{
/* Just need to do this once... */
if (tg->preDrawContainer)
    return tg->preDrawContainer;

struct wigItem *wi;
double pixelsPerBase = scaleForPixels(width);
double basesPerPixel = 1.0;
int itemCount = 0;
char *currentFile = NULL;
//char *currentFileRewrite = NULL;
struct udcFile *wibFH = NULL;	/*	file handle to binary file */
int i;				/* an integer loop counter	*/
int x1 = 0;			/*	screen coordinates	*/
int x2 = 0;			/*	screen coordinates	*/
int usingDataSpan = 1;		/* will become larger if possible */

if (tg->items == NULL)
    return NULL;

if (pixelsPerBase > 0.0)
    basesPerPixel = 1.0 / pixelsPerBase;

/*	width - width of drawing window in pixels
 *	pixelsPerBase - pixels per base
 *	basesPerPixel - calculated as 1.0/pixelsPerBase
 */
itemCount = 0;

/* Allocate predraw and save it and related info in the track. */
struct preDrawContainer *pre = tg->preDrawContainer = initPreDrawContainer(width);
struct preDrawElement *preDraw = pre->preDraw;	/* to accumulate everything in prep for draw */
int preDrawZero = pre->preDrawZero;		/* location in preDraw where screen starts */
int preDrawSize = pre->preDrawSize;		/* size of preDraw array */

usingDataSpan = wigFindSpan(tg, basesPerPixel);

/*	walk through all the data and prepare the preDraw array	*/
for (wi = tg->items; wi != NULL; wi = wi->next)
    {
    int dataOffset = 0;		/*	within data block during drawing */

    ++itemCount;


    /*	Now that we know what Span to draw, see if this item should be
     *	drawn at all.
     */
    if (usingDataSpan == wi->span)
	{
	/*	Check our data file, see if we need to open a new one */
	if (differentStringNullOk(currentFile,""))
	    {
	    if (differentStringNullOk(currentFile,wi->file))
		{
		if (wibFH > 0)
		    {
		    udcFileClose(&wibFH);
		    freeMem(currentFile);
		    }
                currentFile = cloneString(wi->file);
		wibFH = udcFileMayOpen(hReplaceGbdb(currentFile), NULL);
		if (wibFH==NULL)
		    errAbort("hgTracks/wigLoadPreDraw: failed to open wiggle %s", currentFile);
		}
	    }
	else
	    {
            currentFile = cloneString(wi->file);
            wibFH = udcFileMayOpen(hReplaceGbdb(currentFile), NULL);
	    if (wibFH==NULL)
		errAbort("hgTracks/wigLoadPreDraw: failed to open wiggle %s", currentFile);
	    }
/*	Ready to draw, what do we know:
 *	the feature being processed:
 *	chrom coords:  [wi->start : wi-end)
 *
 *	The data to be drawn: to be read from file f at offset wi->Offset
 *	data points available: wi->Count, representing wi->Span bases
 *	for each data point
 *
 *	The drawing window, in pixels:
 *	xOff = left margin, yOff = top margin, h = height of drawing window
 *	drawing window in chrom coords: seqStart, seqEnd
 *      'basesPerPixel' is known, 'pixelsPerBase' is known
 */
        /*      let's check end point screen coordinates.  If they are
         *      the same, then this entire data block lands on one pixel,
         *      no need to walk through it, just use the block's specified
         *      max/min.  It is OK if these end up + or - values, we do want to
         *      keep track of pixels before and after the screen for
         *      later smoothing operations.  x1d,x2d are pixel coordinates
         */
double x1d = (double)(wi->start - seqStart) * pixelsPerBase;
	x1 = round(x1d);
double x2d = (double)((wi->start+(wi->count * usingDataSpan))-seqStart) * pixelsPerBase;
	x2 = round(x2d);

        /* this used to be if (x2 > x1) which often caused reading of blocks
	 * when they were merely x2 = x1 + 1 due to rounding errors as
	 * they became integers.  This double comparison for something over
	 * 0.5 will account for rounding errors that are really small, but
	 * still handle a slipping window size as it walks across the screen
	 */
	if ((x2d - x1d) > 0.5)
	    {
	    unsigned char *readData;	/* the bytes read in from the file */
	    udcSeek(wibFH, wi->offset);
	    readData = (unsigned char *) needMem((size_t) (wi->count + 1));
	    udcRead(wibFH, readData,
		(size_t) wi->count * (size_t) sizeof(unsigned char));
	    /*	walk through all the data in this wiggle data block	*/
	    for (dataOffset = 0; dataOffset < wi->count; ++dataOffset)
		{
		unsigned char datum = readData[dataOffset];
		if (datum != WIG_NO_DATA)
		    {
		    /* (wi->start-seqStart) == base where this wiggle data block
		     *	begins.  Add to that (dataOffset * usingDataSpan) which
		     *	is how many bases this specific datum is from the start
		     *	of this wiggle data block.
		     * x1,x2 are the pixel begin and end for this data item */
		    x1 = ((wi->start-seqStart) + (dataOffset * usingDataSpan)) * pixelsPerBase;
		    /* (usingDataSpan * pixelsPerBase) is the number of pixels
		     *	occupied by this one data item
		     */
		    x2 = x1 + (usingDataSpan * pixelsPerBase);
		    for (i = x1; i <= x2; ++i)
			{
			int xCoord = preDrawZero + i;
			if ((xCoord >= 0) && (xCoord < preDrawSize))
			    {
			    double dataValue =
			       BIN_TO_VALUE(datum,wi->lowerLimit,wi->dataRange);

			    ++preDraw[xCoord].count;
			    if (dataValue > preDraw[xCoord].max)
				preDraw[xCoord].max = dataValue;
			    if (dataValue < preDraw[xCoord].min)
				preDraw[xCoord].min = dataValue;
			    preDraw[xCoord].sumData += dataValue;
			    preDraw[xCoord].sumSquares += dataValue * dataValue;
			    }
			}
		    }
		}
	    freeMem(readData);
            }
        else
	    {	/*	only one pixel for this block of data */
	    int xCoord = preDrawZero + x1;
	    /*	if the point falls within our array, record it.
	     *	the (wi->validCount > 0) is a safety check.  It
	     *	should always be true unless the data was
	     *	prepared incorrectly.
	     */
	    if ((wi->validCount > 0) && (xCoord >= 0) && (xCoord < preDrawSize))
		{
		double upperLimit;
		preDraw[xCoord].count += wi->validCount;
		upperLimit = wi->lowerLimit + wi->dataRange;
		if (upperLimit > preDraw[xCoord].max)
		    preDraw[xCoord].max = upperLimit;
		if (wi->lowerLimit < preDraw[xCoord].min)
		    preDraw[xCoord].min = wi->lowerLimit;
		preDraw[xCoord].sumData += wi->sumData;
		preDraw[xCoord].sumSquares += wi->sumSquares;
		}
	    }
	}	/*	Draw if span is correct	*/
    }	/*	for (wi = tg->items; wi != NULL; wi = wi->next)	*/
if (wibFH > 0)
    {
    udcFileClose(&wibFH);
    wibFH = 0;
    freeMem(currentFile);
    }
return pre;
}

void wigLogoMafCheck(struct track *tg,  int start, int end)
/* Check to see if we should draw a sequence logo for the wiggle contents. */
{
char *logoMaf = trackDbSetting(tg->tdb, "logoMaf");

if (logoMaf != NULL)
    {
    struct wigCartOptions *wigCart = tg->wigCartData;
    if (wigCart->doSequenceLogo)
        {
        // see if the MAF is a bigBed
        if (endsWith(logoMaf, ".bb") || endsWith(logoMaf, ".bigMaf"))
            {
            struct bbiFile *bbi = bigBedFileOpenAlias(logoMaf, chromAliasFindAliases);
            wigCart->baseProbs = hgBigMafProbs(database, bbi, chromName, start, end, '+');
            bbiFileClose(&bbi);
            tg->bbiFile = NULL;
            }
        else  // otherwise it's a table
            wigCart->baseProbs = hgMafProbs(database, logoMaf, chromName, start, end, '+');
        }
    }
}

static void wigPreDrawItems(struct track *tg, int seqStart, int seqEnd,
	struct hvGfx *hvg, int xOff, int yOff, int width,
	MgFont *font, Color color, enum trackVisibility vis)
/* Draw wiggle items that resolve to doing a box for each pixel. */
{
wigLogoMafCheck(tg, seqStart, seqEnd);

struct preDrawContainer *pre = wigLoadPreDraw(tg, seqStart, seqEnd, width);
if (pre != NULL)
    {
    wigPreDrawPredraw(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis,
                   pre, pre->preDrawZero, pre->preDrawSize,
                   &tg->graphUpperLimit, &tg->graphLowerLimit);
    }
}

void wigMultiRegionGraphLimits(struct track *tg)
/* Set common graphLimits across all windows */
{
double graphUpperLimit = -BIGDOUBLE;
double graphLowerLimit = BIGDOUBLE;
struct track *tgSave = tg;
struct window *w;
// find graphLimits across all windows.
for(w=windows,tg=tgSave; w; w=w->next,tg=tg->nextWindow)
    {
    if (tg->graphUpperLimit > graphUpperLimit)
	graphUpperLimit = tg->graphUpperLimit;
    if (tg->graphLowerLimit < graphLowerLimit)
	graphLowerLimit = tg->graphLowerLimit;
    }
// set same common graphLimits in all windows.
for(w=windows,tg=tgSave; w; w=w->next,tg=tg->nextWindow)
    {
    tg->graphUpperLimit = graphUpperLimit;
    tg->graphLowerLimit = graphLowerLimit;
    }
}

static void wigDrawItems(struct track *tg, int seqStart, int seqEnd,
	struct hvGfx *hvg, int xOff, int yOff, int width,
	MgFont *font, Color color, enum trackVisibility vis)
/* Draw wiggle items that resolve to doing a box for each pixel. */
{
struct preDrawContainer *pre = tg->preDrawContainer;
if (pre != NULL)
    {
    wigDrawPredraw(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis,
                   pre, pre->preDrawZero, pre->preDrawSize,
                   tg->graphUpperLimit, tg->graphLowerLimit);
    }
}

void wigLeftAxisLabels(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, char *shortLabel,
                       double graphUpperLimit, double graphLowerLimit, boolean showNumbers)
/* Draw labels on left for a wiggle-type track. */
{
int fontHeight = tl.fontHeight+1;
int centerOffset = 0;
double lines[2];	/*	lines to label	*/
int numberOfLines = 1;	/*	at least one: 0.0	*/
int i;			/*	loop counter	*/
struct wigCartOptions *wigCart = (struct wigCartOptions *) tg->wigCartData;

lines[0] = 0.0;
lines[1] = wigCart->yLineMark;
if (wigCart->yLineOnOff == wiggleYLineMarkOn)
    ++numberOfLines;

if (withCenterLabels)
    centerOffset = fontHeight;

/*	We only do Dense and Full	*/
if (tg->limitedVis == tvDense)
    {
    hvGfxTextRight(hvg, xOff, yOff+centerOffset, width - 1, height-centerOffset,
	color, font, shortLabel);
    }
else if (tg->limitedVis == tvFull)
    {
    int centerLabel = (height/2)-(fontHeight/2);
    int labelWidth = mgFontStringWidth(font, shortLabel);

    /* track label is centered in the whole region */
    hvGfxText(hvg, xOff, yOff+centerLabel, color, font, shortLabel);

    /*	Is there room left to draw the min, max ?	*/
    if (showNumbers && height >= (3 * fontHeight))
	{
	boolean zeroOK = TRUE;
	char upper[SMALLBUF];
        char lower[SMALLBUF];
        char upperTic = '-';    /* as close as we can get with ASCII */
                        /* the ideal here would be to draw tic marks in
                         * exactly the correct location.
                         */
        Color drawColor;
        if (withCenterLabels)
	    {
	    centerOffset = fontHeight;
	    upperTic = '_';	/*	this is correct	*/
	    }

	/*  In areas where there is no data, these limits do not change */
	if (graphUpperLimit < graphLowerLimit)
	    {
	    double d = graphLowerLimit;
	    graphLowerLimit = graphUpperLimit;
	    graphUpperLimit = d;
            if (hvg->rc)
                {
                safef(upper, sizeof(upper), " %c No data", upperTic);
                safef(lower, sizeof(lower), "_ No data");
                }
            else
                {
                safef(upper, sizeof(upper), "No data %c", upperTic);
                safef(lower, sizeof(lower), "No data _");
                }
	    zeroOK = FALSE;
	    }
	else
	    {
	    enum wiggleTransformFuncEnum transformFunc = wigCart->transformFunc;
	    boolean gotLog = (transformFunc == wiggleTransformFuncLog);
	    char *transform = (gotLog ? "ln(x+1) " : "");
            if (hvg->rc)
                {
                safef(upper, sizeof(upper), "%c %s%g", upperTic, transform, graphUpperLimit);
                safef(lower, sizeof(lower), "_ %g", graphLowerLimit);
                }
            else
                {
                safef(upper, sizeof(upper), "%s%g %c", transform, graphUpperLimit, upperTic);
                safef(lower, sizeof(lower), "%g _", graphLowerLimit);
                }
	    }
	drawColor = color;
	if (graphUpperLimit < 0.0) drawColor = tg->ixAltColor;
	hvGfxTextRight(hvg, xOff, yOff, width - 1, fontHeight, drawColor,
	    font, upper);
	drawColor = color;
	if (graphLowerLimit < 0.0) drawColor = tg->ixAltColor;
	hvGfxTextRight(hvg, xOff, yOff+height-fontHeight, width - 1, fontHeight,
	    drawColor, font, lower);

	for (i = 0; i < numberOfLines; ++i )
	    {
	    double lineValue = lines[i];
	    /*	Maybe zero can be displayed */
	    /*	It may overwrite the track label ...	*/
	    if (zeroOK && (lineValue < graphUpperLimit) &&
		(lineValue > graphLowerLimit))
		{
		int offset;
		int Width;

		drawColor = hvGfxFindColorIx(hvg, 0, 0, 0);
		offset = centerOffset +
		    (int)(((graphUpperLimit - lineValue) *
				(height - centerOffset)) /
			(graphUpperLimit - graphLowerLimit));
		/*	reusing the lower string here	*/
                if (hvg->rc)
                    safef(lower, sizeof(lower), "- %g", ((i == 0) ? 0.0 : lineValue));
                else
                    safef(lower, sizeof(lower), "%g -", ((i == 0) ? 0.0 : lineValue));
		/*	only draw if it is far enough away from the
		 *	upper and lower labels, and it won't overlap with
		 *	the center label.
		 */
		Width = mgFontStringWidth(font,lower);
		if ( !( (offset < centerLabel+fontHeight) &&
		    (offset > centerLabel-(fontHeight/2)) &&
		    (Width+labelWidth >= width) ) &&
		    (offset > (fontHeight*2)) &&
		    (offset < height-(fontHeight*2)) )
		    {
		    hvGfxTextRight(hvg, xOff, yOff+offset-(fontHeight/2),
			width - 1, fontHeight, drawColor, font, lower);
		    }
		}	/*	drawing a zero label	*/
	    }	/*	drawing 0.0 and perhaps yLineMark	*/
	}	/* if (height >= (3 * fontHeight))	*/
    }	/*	if (tg->visibility == tvFull)	*/
} /* wigAxisLeftLabels */

void wigLeftLabels(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)
/*	drawing left labels	*/
{
wigLeftAxisLabels(tg, seqStart, seqEnd, hvg, xOff, yOff, width, height, withCenterLabels,
                  font, color, vis, tg->shortLabel, tg->graphUpperLimit, tg->graphLowerLimit,TRUE);
}

struct wigCartOptions *wigCartOptionsNew(struct cart *cart, struct trackDb *tdb, int wordCount, char *words[])
/* Create a wigCartOptions from cart contents and tdb. */
{
struct wigCartOptions *wigCart;

int defaultHeight;	/*	truncated by limits	*/
double yLineMark;	/*	from trackDb or cart */
int maxHeight = atoi(DEFAULT_HEIGHT_PER);
int minHeight = MIN_HEIGHT_PER;

AllocVar(wigCart);

/*	These Fetch functions look for variables in the cart bounded by
 *	limits specified in trackDb or returning defaults
 */
wigCart->lineBar = wigFetchGraphTypeWithCart(cart,tdb,tdb->track, (char **) NULL);
wigCart->horizontalGrid = wigFetchHorizontalGridWithCart(cart,tdb,tdb->track, (char **) NULL);

wigCart->autoScale = wigFetchAutoScaleWithCart(cart,tdb,tdb->track, (char **) NULL);
wigCart->windowingFunction = wigFetchWindowingFunctionWithCart(cart,tdb,tdb->track, (char **) NULL);
wigCart->smoothingWindow = wigFetchSmoothingWindowWithCart(cart,tdb,tdb->track, (char **) NULL);

wigFetchMinMaxPixelsWithCart(cart,tdb,tdb->track, &minHeight, &maxHeight, &defaultHeight);
wigFetchYLineMarkValueWithCart(cart,tdb,tdb->track, &yLineMark);
wigCart->yLineMark = yLineMark;
wigCart->yLineOnOff = wigFetchYLineMarkWithCart(cart,tdb,tdb->track, (char **) NULL);
wigCart->alwaysZero = (enum wiggleAlwaysZeroEnum)wigFetchAlwaysZeroWithCart(cart,tdb,tdb->track, (char **) NULL);
wigCart->transformFunc = (enum wiggleTransformFuncEnum)wigFetchTransformFuncWithCart(cart,tdb,tdb->track, (char **) NULL);
wigCart->doNegative = wigFetchDoNegativeWithCart(cart,tdb,tdb->track, (char **) NULL);
if (zoomedToCodonLevel)
    wigCart->doSequenceLogo = wigFetchDoSequenceLogoWithCart(cart,tdb,tdb->track, (char **) NULL);

wigCart->maxHeight = maxHeight;
wigCart->defaultHeight = defaultHeight;
wigCart->minHeight = minHeight;

wigFetchMinMaxYWithCart(cart,tdb,tdb->track, &wigCart->minY, &wigCart->maxY, NULL, NULL, wordCount, words);

wigCart->colorTrack = trackDbSetting(tdb, "wigColorBy");

char *containerType = trackDbSetting(tdb, "container");
if (containerType != NULL && sameString(containerType, "multiWig"))
     wigCart->isMultiWig = TRUE;

wigCart->aggregateFunction = wigFetchAggregateFunctionWithCart(cart,tdb,tdb->track, (char **) NULL);

// can't do mean with whiskers in stacked mode
if ((wigCart->aggregateFunction == wiggleAggregateStacked) &&
    ( wigCart->windowingFunction == wiggleWindowingWhiskers))
    wigCart->windowingFunction = wiggleWindowingMax;
return wigCart;
}

/* Make track group for wig multiple alignment.
 *	WARNING ! - track->visibility is merely the default value
 *	from the trackDb entry at this time.  It will be set after this
 *	 by hgTracks from its cart UI setting.  When called in
 *	 TotalHeight it will then be the requested visibility.
 */
void wigMethods(struct track *track, struct trackDb *tdb,
	int wordCount, char *words[])
{
struct wigCartOptions *wigCart = wigCartOptionsNew(cart, tdb, wordCount, words);
track->minRange = wigCart->minY;
track->maxRange = wigCart->maxY;
track->graphUpperLimit = wigEncodeStartingUpperLimit;
track->graphLowerLimit = wigEncodeStartingLowerLimit;
wigCart->bedGraph = FALSE;	/*	signal to left labels	*/

track->loadItems = wigLoadItems;
track->freeItems = wigFreeItems;
track->preDrawItems = wigPreDrawItems;
track->preDrawMultiRegion = wigMultiRegionGraphLimits;
track->drawItems = wigDrawItems;
track->itemName = wigNameCallback;
track->mapItemName = wigNameCallback;
track->totalHeight = wigTotalHeight;
track->itemHeight = tgFixedItemHeight;

track->itemStart = tgItemNoStart;
track->itemEnd = tgItemNoEnd;
/*	the wigMaf parent will turn mapsSelf off	*/
track->mapsSelf = TRUE;
track->wigCartData = (void *) wigCart;
track->colorShades = shadesOfGray;
track->drawLeftLabels = wigLeftLabels;
track->loadPreDraw = wigLoadPreDraw;
/*	the lfSubSample type makes the image map function correctly */
track->subType = lfSubSample;     /*make subType be "sample" (=2)*/

}	/*	wigMethods()	*/
