src/hg/instinct/hgBamBam/hgAnnotations.c 1.1

1.1 2010/05/25 20:22:44 jsanborn
initial commit
Index: src/hg/instinct/hgBamBam/hgAnnotations.c
===================================================================
RCS file: src/hg/instinct/hgBamBam/hgAnnotations.c
diff -N src/hg/instinct/hgBamBam/hgAnnotations.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/hg/instinct/hgBamBam/hgAnnotations.c	25 May 2010 20:22:44 -0000	1.1
@@ -0,0 +1,713 @@
+/********************************************************************************/
+/* Copyright 2007-2009 -- The Regents of the University of California           */
+/********************************************************************************/
+
+/* hgAnnotations.c
+ *    Work in progress -- to draw small region to full genome annotation tracks (i.e. RefSeq) 
+ * in concise space.
+ */
+
+#define EXPR_DATA_SHADES 16
+#define DEFAULT_MAX_DEVIATION 0.7
+#define COLOR_SCALE 1
+
+#include <limits.h> 
+#include "common.h"
+#include "cart.h"
+#include "vGfx.h"
+#include "hvGfx.h"
+#include "hCommon.h"
+#include "hdb.h"
+#include "cytoBand.h"
+#include "hCytoBand.h"
+#include "hgBamBam.h"
+
+static char const rcsid[] = "$Id$";
+
+static char *heatMapDbProfile = "localDb";  // database profile to use
+
+struct bed *getTable(struct sqlConnection *conn, char *tableName, char *chromName)
+{
+char **row = NULL; 
+char query[256];
+if (chromName)
+    safef(query, sizeof(query), "select * from %s where chrom = \"%s\"", 
+	  tableName, chromName);
+else
+    safef(query, sizeof(query), "select * from %s", tableName);
+
+if (!sqlExists(conn, query))
+    return NULL;
+
+struct sqlResult *sr = sqlGetResult(conn, query);
+struct bed *tuple = NULL;
+struct bed *tupleList = NULL;
+
+while ((row = sqlNextRow(sr)) != NULL)
+    {
+    struct genePred *gp = genePredLoad(row+1);
+    tuple = bedFromGenePred(gp);
+    genePredFree(&gp);
+    slAddHead(&tupleList, tuple);
+    }
+
+sqlFreeResult(&sr);
+return tupleList;
+}
+
+int hmPixelCmpCount(const void *va, const void *vb)
+/* Compare to sort columns based on priority. */
+{
+const struct hmPixel *a = *((struct hmPixel **)va);
+const struct hmPixel *b = *((struct hmPixel **)vb);
+float dif = a->count - b->count;
+if (dif < 0)
+    return -1;
+else if (dif > 0)
+    return 1;
+else
+    return 0;
+}
+      
+void addHmPixel(struct hmPixel **hmList, struct hash *pixelHash, 
+		char mod, double x1, double x2, int pStart, int y, int h)
+{
+/* calculate starting position and width of box to draw */
+int x = round(x1) + pStart;
+int w = round(x2) - round(x1);
+if (w == 0)
+    w = 1;
+
+char pixelStr[32];
+safef(pixelStr, sizeof(pixelStr), "%d,%c", x, mod);
+
+struct hmPixel *hm;
+struct hashEl *el = hashLookup(pixelHash, pixelStr);
+
+if (!el)
+    {
+    hm = AllocA(struct hmPixel);
+    hm->x = x;
+    hm->y = y;
+    hm->w = w;
+    hm->h = h;
+    hm->val = 0.0;
+    hm->count = 0;
+    slAddHead(hmList, hm);
+    hashAdd(pixelHash, pixelStr, hm);
+    }
+else
+    hm = el->val;
+
+if (w > hm->w)
+    hm->w = w;
+hm->count += 1;
+}
+
+
+void drawTable(struct vGfx *vg, struct settings *settings,
+	       char *db, char *tableName, int height)
+/* Draw chromosome graph on all chromosomes in layout at given
+ * y offset and height. */
+{
+if (!settings)
+    return;
+
+/* get remote database connection */
+struct sqlConnection *conn = hAllocConn(db);
+
+struct bed *nb, *ghBed = NULL;
+
+double pixPerBase = (double) settings->width / (double) settings->totalBases;
+
+Color upShades[EXPR_DATA_SHADES];
+static struct rgbColor zeroColor = {0, 0, 0}; // black
+static struct rgbColor highColor = {255, 0, 0};     // red
+vgMakeColorGradient(vg, &zeroColor, &highColor, EXPR_DATA_SHADES, upShades);
+
+struct hash *pixelHash = hashNew(0);
+struct hmPixel *hm, *hmList = NULL;
+
+struct chromLay *cl;
+for(cl = settings->layoutList; cl; cl = cl->next)
+    {
+    ghBed = getTable(conn, tableName, cl->chrom);
+    if (!ghBed)
+	continue;
+
+    struct bed *filterList =
+        bedFilterListInRange(ghBed, NULL, cl->chrom, cl->baseStart, cl->baseEnd);
+
+    int pStart = cl->pxStart;
+    int baseStart = cl->baseStart;
+
+    for(nb = filterList; nb; nb = nb->next)
+        {
+        int cStart = nb->chromStart;
+        int cEnd = nb->chromEnd;
+
+	double x1, x2;
+	int baseline = cStart - baseStart;
+
+	x1 = pixPerBase * (double) (cStart - baseStart);
+	x2 = pixPerBase * (double) (cEnd - baseStart);
+
+	if ( (x2 - x1) > 2.0)
+	    { /* width > two pixels, attempt to draw exon/utr structure
+	       * similar to genome browser */
+
+	    addHmPixel(&hmList, pixelHash, 'i', x1, x2, pStart, 4, 1);
+
+	    int tStart = nb->thickStart - nb->chromStart;
+	    int tEnd = nb->thickEnd - nb->chromStart;
+
+	    int i, bStart, bEnd;
+	    for (i = 0; i < nb->blockCount; i++)
+		{
+		bStart = nb->chromStarts[i];
+		bEnd = bStart + nb->blockSizes[i];
+	       
+		if (bStart < tStart)
+		    { /* 5' utr region */
+		    x1 = pixPerBase * (double) (bStart + baseline);
+		    if (bEnd < tStart)
+			x2 = pixPerBase * (double) (bEnd + baseline);
+		    else
+			x2 = pixPerBase * (double) (tStart + baseline);
+
+		    addHmPixel(&hmList, pixelHash, 'u', x1, x2, pStart, 2, 5);
+
+		    if (bEnd > tEnd)
+			{ /* coding region inside single block */
+			x1 = pixPerBase * (double) (tStart + baseline);
+			x2 = pixPerBase * (double) (tEnd + baseline);
+
+			addHmPixel(&hmList, pixelHash, 'c', x1, x2, pStart, 0, 9);
+
+			x1 = pixPerBase * (double) (tEnd + baseline);
+			x2 = pixPerBase * (double) (bEnd + baseline);
+
+			addHmPixel(&hmList, pixelHash, 'u', x1, x2, pStart, 2, 5);
+			}
+		    else if (bEnd > tStart)
+			{ /* coding region downstream of 5' utr */
+			x1 = pixPerBase * (double) (tStart + baseline);
+			x2 = pixPerBase * (double) (bEnd + baseline);
+
+			addHmPixel(&hmList, pixelHash, 'c', x1, x2, pStart, 0, 9);
+			}
+		    }
+		else if (bEnd > tEnd)
+		    { /* 3' utr region */
+		    if (bStart > tEnd)
+			x1 = pixPerBase * (double) (bStart + baseline);
+		    else
+			x1 = pixPerBase * (double) (tEnd + baseline);
+		    x2 = pixPerBase * (double) (bEnd + baseline);
+
+		    addHmPixel(&hmList, pixelHash, 'u', x1, x2, pStart, 2, 5);
+
+		    if (bStart < tEnd)
+			{ /* coding region upstream of 3' utr */
+			x1 = pixPerBase * (double) (bStart + baseline);
+			x2 = pixPerBase * (double) (tEnd + baseline);
+			addHmPixel(&hmList, pixelHash, 'c', x1, x2, pStart, 0, 9);
+			}
+		    }
+		else
+		    { /* block is all coding region, no utr */ 
+		    x1 = pixPerBase * (double) (bStart + baseline);
+		    x2 = pixPerBase * (double) (bEnd + baseline);
+		    
+		    addHmPixel(&hmList, pixelHash, 'c', x1, x2, pStart, 0, 9);
+		    }
+		}
+	    }
+	else 
+	    { /* width < 2 pixels -- gene too dense, so just draw one large block */
+	    x1 = pixPerBase * (double) (cStart - baseStart);
+	    x2 = pixPerBase * (double) (cEnd - baseStart);
+	    
+	    addHmPixel(&hmList, pixelHash, 'f', x1, x2, pStart, 0, 9);
+	    }
+        }
+    bedFreeList(&ghBed);
+    }
+hFreeConn(&conn);
+
+if (!hmList)  // nothing to draw.
+    return;
+
+slSort(&hmList, hmPixelCmpCount);
+struct hmPixel *last = slLastEl(hmList);
+int maxVal = last->count;
+
+Color valCol;
+for (hm = hmList; hm ; hm = hm->next)
+    {
+    double val = (double) hm->count / (double) maxVal;
+    
+    int colorIndex = (int)(val * (EXPR_DATA_SHADES-1.0) );
+    
+    /* Clip color index to fit inside of array, since we may have brightened it. */
+    if (colorIndex < 0) colorIndex = 0;
+    if (colorIndex >= EXPR_DATA_SHADES)
+	colorIndex = EXPR_DATA_SHADES-1;
+
+    valCol = upShades[colorIndex];
+    
+    vgBox(vg, hm->x, hm->y, hm->w, hm->h, valCol);
+    }
+} 
+
+
+char *annotationGif(struct settings *settings, char *tableName)
+/* Create genome GIF file and HT that includes it. */
+{
+if (!settings || !tableName)
+    return NULL;
+
+struct hvGfx *vg;
+
+char *db = "hg18";
+
+int width = settings->width;
+int height = settings->height;  // was set to 10
+
+if (width * height == 0)
+    return NULL;
+
+struct tempName md5Tn;
+char *strToHash = cartSettingsString(bbPrefix, "annotationGif");
+trashDirMD5File(&md5Tn, "hgh", ".gif", strToHash);
+
+off_t size = fileSize(md5Tn.forCgi);
+if (!fileExists(md5Tn.forCgi) || (size == 0) || DEBUG)
+    {
+    vg = hvGfxOpenGif(width, height, md5Tn.forCgi, FALSE);
+    drawTable(vg->vg, settings, db, tableName, height);
+    hvGfxClose(&vg);
+    }
+
+char *filename = replaceChars(md5Tn.forHtml, "..", "");
+return filename;
+}
+
+double basesInScale(double numBases)
+{
+double exp = floor(log(numBases)/log(10.0));
+if (exp < 0.0)
+    exp = 0.0;
+double divisor = pow(10.0, exp);
+
+double rescaledBases = numBases / divisor;
+double maxBases = rescaledBases / 2.0;
+
+double bases = 0.0;
+if (maxBases >= 5.0)
+    bases = 5.0 * divisor;
+else if (maxBases >= 2.0)
+    bases = 2.0 * divisor;
+else if (maxBases >= 1.0)
+    bases = 1.0 * divisor;
+else if (maxBases >= 0.5)
+    bases = 0.5 * divisor;
+else if (maxBases >= 0.2)
+    bases = 0.2 * divisor;
+else if (maxBases >= 0.1)
+    bases = 0.1 * divisor;
+
+bases = floor(bases);
+
+return bases;
+}
+
+struct hash *getCytoBandHash(struct sqlConnection *conn)
+{
+if (!sqlTableExists(conn, "cytoBand"))
+    return NULL;
+
+char **row;
+char query[128];
+safef(query, sizeof(query), "select * from cytoBand");
+
+struct hash *cbHash = hashNew(0);
+struct sqlResult *sr = sqlGetResult(conn, query);
+
+while ((row = sqlNextRow(sr)) != NULL)
+    {
+    struct cytoBand *cb = cytoBandLoad(row);
+    
+    struct hashEl *el = hashLookup(cbHash, cb->chrom);
+    struct cytoBand *cbList = NULL;
+    if (el)
+	cbList = el->val;
+    slAddTail(&cbList, cb);
+
+    if (!el) // add first member of list to hash
+	hashAdd(cbHash, cb->chrom, cbList);
+    }
+sqlFreeResult(&sr);
+
+return cbHash;
+}
+
+
+void drawIdeogram(struct hvGfx *hvg, struct settings *settings, int yOff)
+{
+if (!settings)
+    return;
+
+struct vGfx *vg = hvg->vg;
+
+struct sqlConnection *conn = hAllocConnProfile(heatMapDbProfile, "hg18");
+struct hash *cbHash = getCytoBandHash(conn);
+hFreeConn(&conn);
+
+if (!cbHash)
+    return;
+
+/* Make grayscale for bands */
+int maxShade = 9;
+Color shadesOfGray[10+1];
+static struct rgbColor black = {0, 0, 0};
+static struct rgbColor white = {255, 255, 255};
+vgMakeColorGradient(vg, &white, &black, 10, shadesOfGray);
+shadesOfGray[10] = MG_RED;  // to check for overflow, should never see red.
+
+int width = settings->width;
+
+struct chromLay *cl;
+vgSetClip(vg, 0, yOff, width, CYTO_HEIGHT);
+
+for (cl = settings->layoutList; cl; cl = cl->next)
+    {
+    struct hashEl *el = hashLookup(cbHash, cl->chrom);
+    if (!el)
+	continue;
+    struct cytoBand *cb, *cbList = el->val;
+
+    int pixStart = cl->pxStart;
+    int pixEnd   = cl->pxStart + cl->pxWidth;
+    int pixSize  = cl->pxWidth;
+
+    unsigned bStart = cl->baseStart;
+    unsigned bEnd   = cl->baseEnd;
+    unsigned bSize  = bEnd - bStart;
+
+    double basesPerPixel = (double) bSize / (double) pixSize;
+ 
+    int xBuffer = 0; //Start = pixStart; 
+    if (pixStart == 0)
+	xBuffer = 0;
+
+    /* Draw box around cytoband */
+    vgBox(vg, pixStart+1, yOff, pixSize-1, 1, MG_BLACK);
+    vgBox(vg, pixStart+1, yOff + CYTO_HEIGHT-1, pixSize-1, 1, MG_BLACK);
+    
+    unsigned minBand = INT_MAX;
+    unsigned maxBand = 0;
+    for (cb = cbList; cb; cb = cb->next)
+	{
+	unsigned start = cb->chromStart;  // our bases start at 1 TODO: FIX!
+	unsigned stop = cb->chromEnd;
+	if ((stop < bStart) || (start > bEnd)) // out of scope
+	    continue;
+
+	int pStart = round(((double) start - (double) bStart) / basesPerPixel) + pixStart;
+	if (pStart < pixStart)
+	    pStart = pixStart;
+	if (pStart <= 0)
+	    pStart = 1;
+
+	int pEnd = round(((double) stop - (double) bStart) / basesPerPixel) + pixStart;
+	if (pEnd >= pixEnd)
+	    pEnd = pixEnd;
+	
+	Color col = hCytoBandColor(cb, hvg, FALSE, MG_BLACK, MG_WHITE, 
+				   shadesOfGray, maxShade);    
+	vgBox(vg, pStart, yOff+1, (pEnd - pStart), CYTO_HEIGHT-2, col);
+
+	Color textCol = hvGfxContrastingColor(hvg, col);
+	char *pt = cb->name;
+	int fullWidth = mgFontStringWidth(settings->font, pt);
+	int shortWidth = mgFontStringWidth(settings->font, pt+1);
+        if (fullWidth < (pEnd - pStart))
+            vgTextCentered(vg, pStart, yOff+1, (pEnd - pStart), CYTO_HEIGHT-2,
+                           textCol, settings->font, pt);
+	else if (shortWidth < (pEnd - pStart))
+            vgTextCentered(vg, pStart, yOff+1, (pEnd - pStart), CYTO_HEIGHT-2,
+                           textCol, settings->font, pt+1);
+	
+	if (start < minBand)
+	    minBand = start;
+	if (stop > maxBand)
+	    maxBand = stop;
+	}
+
+    /* If very near left/right edges, if we see the chromosome edge */
+    if (abs(minBand - bStart) < 100)
+	vgBox(vg, pixStart, yOff+1, 1, CYTO_HEIGHT-2, MG_BLACK);
+    if (abs(maxBand- bEnd) < 100)
+	vgBox(vg, pixEnd-1, yOff+1, 1, CYTO_HEIGHT-2, MG_BLACK);
+    
+    for (cb = cbList; cb; cb = cb->next)
+	{
+        /* If centromere do some drawing. */
+	if(!sameString(cb->gieStain, "acen"))
+	    continue;
+	int cenLeft, cenRight, cenTop, cenBottom;
+	
+	/* Get the coordinates of the edges of the centromere. */
+	cenLeft = round(((double) cb->chromStart - (double) bStart) / basesPerPixel) + pixStart; 
+	cenRight = round(((double) cb->next->chromEnd - (double) bStart)/basesPerPixel) + pixStart;
+	cenTop = yOff + CYTO_HEIGHT;
+	cenBottom = yOff;
+	
+	if (cenLeft < 0 && cenRight < 0)
+	    break;
+	if (cenLeft > pixEnd || cenRight < pixStart)
+	    break;
+
+	/* Draw centromere itself. */
+	hCytoBandDrawCentromere(hvg, cenLeft, yOff, cenRight - cenLeft,
+				CYTO_HEIGHT, MG_WHITE, hCytoBandCentromereColor(hvg));
+	break; 
+	}
+    }
+}
+
+void drawChromLabels(struct vGfx *vg, struct settings *settings)
+{
+if (!settings)
+    return;
+
+int width       = settings->width;
+int labelHeight = DEFAULT_LABEL_HEIGHT;
+
+struct chromLay *cl;
+vgSetClip(vg, 0, 0, width, labelHeight);
+
+boolean singleChrom = FALSE;
+if (slCount(settings->layoutList) == 1)
+    singleChrom = TRUE;
+
+for(cl = settings->layoutList; cl; cl = cl->next)
+    {
+    int chromWidth = cl->pxWidth;
+
+    int leftX = cl->pxStart;
+    if (leftX < 0)
+        leftX = 0;
+
+    int rightX = leftX + chromWidth;
+    if (rightX > width - 1)
+        rightX = width - 1;
+
+    vgBox(vg, leftX, 0, 1, labelHeight, MG_GRAY);
+    vgBox(vg, rightX, 0, 1, labelHeight, MG_GRAY);
+
+    int strWidth;
+    if (!singleChrom)
+        {
+        char *pt = cl->chrom;
+        strWidth = mgFontStringWidth(settings->font, pt);
+        if (strWidth < chromWidth)
+            vgTextCentered(vg, cl->pxStart, 0, chromWidth, labelHeight,
+                           MG_BLACK, settings->font, pt);
+        else
+            {
+            pt = cl->chrom + 3;
+            strWidth = mgFontStringWidth(settings->font, pt);
+            if (strWidth < chromWidth)
+		vgTextCentered(vg, cl->pxStart, 0, chromWidth, labelHeight,
+			       MG_BLACK, settings->font, pt);
+            } 
+        }
+    else
+        {
+        char str[128]; 
+        unsigned long x;
+
+        int y = labelHeight/2 - settings->fontHeight/4;
+        vgText(vg, cl->pxStart + 3, y, MG_BLACK, settings->font, cl->chrom);
+        int minX = cl->pxStart + mgFontStringWidth(settings->font, cl->chrom);
+
+        double numBases = cl->baseEnd - cl->baseStart;
+        if (numBases == 1)
+            {
+            safef(str, sizeof(str), "%lu", (unsigned long int) cl->baseStart);
+            strWidth = mgFontStringWidth(settings->font, str);
+            vgText(vg, rightX - strWidth, y, MG_BLACK, settings->font, str);
+            continue;
+            }
+
+        unsigned long tickBases = (unsigned long) ceil(0.5 * basesInScale(settings->totalBases));
+        int tickPixels = round(tickBases/settings->totalBases * (double) chromWidth);
+
+        if (tickBases == 0)
+            continue;
+
+        int buffer = 10;
+        unsigned long firstX = floor(cl->baseStart / tickBases) * tickBases;
+        for (x = firstX; x <= cl->baseEnd; x += tickBases)
+            {
+            int tickX = round((x - cl->baseStart + 1) / settings->totalBases * (double) chromWidth)
+                + cl->pxStart;
+
+            if (tickX <= minX || tickX > rightX)
+                continue;
+
+            int tickY = labelHeight/2 - settings->fontHeight/2 - 1;
+            vgBox(vg, tickX, tickY, 1, settings->fontHeight+1, MG_BLACK);
+            safef(str, sizeof(str), "%lu", x);
+
+            strWidth = mgFontStringWidth(settings->font, str);
+            if ((strWidth < tickPixels - buffer) && (tickX - strWidth > minX + buffer))
+                vgText(vg, tickX - strWidth - 1, y, MG_BLACK, settings->font, str);
+            }
+	}
+    }
+
+vgUnclip(vg);
+}
+
+
+char *genomeLabelsGif(struct sqlConnection *conn, struct settings *settings)
+/* Create genome label GIF file and HT that includes it. */
+{
+if (!settings)
+    return NULL;
+
+struct hvGfx *vg;
+
+int width = settings->width;
+int height = settings->height;
+if (width * height == 0)
+    return NULL;
+
+struct tempName md5Tn;
+char *strToHash = cartSettingsString(bbPrefix, "genomeLabelsGif");
+trashDirMD5File(&md5Tn, "hgh", ".gif", strToHash);
+
+off_t size = fileSize(md5Tn.forCgi);
+if (!fileExists(md5Tn.forCgi) || (size == 0) || DEBUG)
+    {
+    vg = hvGfxOpenGif(width, height, md5Tn.forCgi, FALSE);
+
+    drawChromLabels(vg->vg, settings);
+    drawIdeogram(vg, settings, DEFAULT_LABEL_HEIGHT);
+
+    hvGfxClose(&vg);
+    }
+
+char *filename = replaceChars(md5Tn.forHtml, "..", "");
+return filename;
+}   
+
+double basesInScaleAndString(double numBases, char **scaleStr)
+{
+double bases = basesInScale(numBases);
+
+double divisor = 1.0;
+char *suffix = NULL;
+if (bases == 1.0)
+    {
+    divisor = 1.0;
+    suffix = "base";
+    }
+else if (bases < 10.0)
+    {
+    divisor = 1.0;
+    suffix = "bases";
+    }
+else if (bases < 1000.0)
+    {
+    divisor = 1.0;
+    suffix = "bp";
+    }
+else if (bases < 1000000.0)
+    {
+    divisor = 1000.0;
+    suffix = "Kb";
+    }
+else if (bases < 1000000000.0)
+    {
+    divisor = 1000000.0;
+    suffix = "Mb";
+    }
+else if (bases < 1000000000000.0)
+    {
+    divisor = 1000000000.0;
+    suffix = "Gb";
+    }
+	 
+char str[128];
+safef(str, sizeof(str), "%d %s", (int) floor(bases/divisor), suffix);
+*scaleStr = cloneString(str);
+
+return bases;
+}
+
+void drawGenomeScale(struct vGfx *vg, struct settings *settings, 
+		     int width, int height, double numBases)
+{
+char *scaleStr; 
+double scaleBases = basesInScaleAndString(numBases, &scaleStr);
+
+if (!scaleStr)
+    return;
+
+vgSetClip(vg, 0, 0, width, height);
+vgBox(vg, 0, 0, width, height, MG_WHITE); 
+
+int midY = height / 2;
+int buffer = 3;
+int scaleWidth = round(((double) width) * scaleBases / numBases);
+int startX = round((double)width / 2.0) - scaleWidth / 2;
+
+int dashTop = (height - settings->fontHeight)/2;
+/* draw line on left/right side */
+vgBox(vg, startX, dashTop, 1, settings->fontHeight, MG_BLACK);
+vgBox(vg, startX+scaleWidth, dashTop, 1, settings->fontHeight, MG_BLACK);
+
+/* draw line connecting */
+vgBox(vg, startX, midY, scaleWidth, 1, MG_BLACK);
+
+int textY = midY - settings->fontHeight/2;
+vgTextRight(vg, 0, textY, startX - buffer, settings->fontHeight,
+	    MG_BLACK, settings->font, scaleStr);
+ 
+vgUnclip(vg);
+}
+
+
+char *genomeScaleGif(struct settings *settings)
+/* Create genome GIF file and HT that includes it. */
+{
+if (!settings || settings->totalBases < 1)
+    return NULL;
+
+int totalW = settings->width;
+int totalH = settings->height;
+
+struct tempName md5Tn;
+char *strToHash = cartSettingsString(bbPrefix, "genomeScaleGif");
+
+trashDirMD5File(&md5Tn, "hgh", ".gif", strToHash);
+
+off_t size = fileSize(md5Tn.forCgi);
+if (!fileExists(md5Tn.forCgi) || (size == 0) || DEBUG)
+    {
+    struct hvGfx *vg = hvGfxOpenGif(totalW, totalH, md5Tn.forCgi, FALSE);
+    
+    drawGenomeScale(vg->vg, settings, totalW, totalH, settings->totalBases);
+
+    hvGfxClose(&vg);
+    }
+
+char *filename = replaceChars(md5Tn.forHtml, "..", "");
+return filename;
+}
+
+