18b8815d717cb178232321e32c31dba40fd5e72d
markd
  Wed Jul 13 17:12:25 2022 -0700
integrate bigRmsk track updates from Robert Hubley

diff --git src/hg/hgTracks/bigRmskTrack.c src/hg/hgTracks/bigRmskTrack.c
index caf0ac1..449bf3b 100644
--- src/hg/hgTracks/bigRmskTrack.c
+++ src/hg/hgTracks/bigRmskTrack.c
@@ -1,24 +1,25 @@
 /* bigRmskTrack - A comprehensive RepeatMasker visualization track
  *                handler for bigRmsk file ( BED9+5 ) track hubs.
  *                Based on previous visualization work with table
  *                supported rmskJoined tracks.
  *
  *
  *  Written by Robert Hubley 10/2021
  *
  *  Modifications:
+ *     6/6/22  : Added doLeftLabels functionality to Full/Pack modes
  *
  */
 
 /* 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 "hash.h"
 #include "linefile.h"
 #include "jksql.h"
 #include "hdb.h"
 #include "hgTracks.h"
 #include "bigRmskUi.h"
 
 /* Track Development Notes:
@@ -86,30 +87,31 @@
     unsigned chromEnd;           /* End position feature in chromosome */
     char *name;                  /* Annotation name [ typically TE family identifier ] */
     unsigned score;              /* Score from 0-1000 */
     char strand[2];              /* + or - */
     unsigned alignStart;         /* Start position of aligned portion of feature */
     unsigned alignEnd;           /* End position of aligned portion of feature */
     unsigned reserved;           /* Used as itemRgb */
     int blockCount;              /* Number of blocks */
     int *blockSizes;             /* Comma separated list of block sizes */
     int *blockRelStarts;         /* Start positions rel. to chromStart or -1 for unaligned blocks */
     unsigned id;                 /* ID to bed used in URL to link back */
     char *description;           /* Long description of item for the details page */
     unsigned visualStart;        /* For use by layout routines -- calc visual start (in bp coord)*/
     unsigned visualEnd;          /* For use by layout routines -- calc visual end (in bp coord)*/
     int layoutLevel;             /* For use by layout routines -- layout row */
+    int leftLabel;               /* For use by visualization routines -- left side labels */
     };
 
 /* Datastructure for graph-based layout */
 struct Graph {
     // the number of nodes in the graph
     int n;
     // an array of edges for each node, size n^2
     //     access: g->edges[(i * n) + j]
     int *edges;
     // an array of degrees for each node
     //     access: g->degrees[i]
     int *degrees;
 };
 
 /* DETAIL_VIEW_MAX_SCALE: The size ( window bp ) at which the
@@ -159,47 +161,44 @@
 };
 
 // Repeat Classes: Data names
 static char *rptClasses[] = {
 "SINE", "LINE", "LTR", "DNA", "Simple_repeat", "Low_complexity",
 "Satellite", "RNA", "Other", "Unknown",
 };
 
 /* Repeat class to color mappings.
  *   - Currently borrowed from snakePalette.
  *
  *  NOTE: If these are changed, do not forget to update the
  *        help table in joinedRmskTrack.html
  */
 static Color rmskJoinedClassColors[] = {
-0xff1f77b4,            // SINE - red
-0xffff7f0e,            // LINE - lime
-0xff2ca02c,            // LTR - maroon
-0xffd62728,            // DNA - fuchsia
-0xff9467bd,            // Simple - yellow
-0xff8c564b,            // LowComplex - olive
-0xffe377c2,            // Satellite - blue
-0xff7f7f7f,            // RNA - green
-0xffbcbd22,            // Other - teal
-0xff17becf,            // Unknown - aqua
+              // Current             // Previously
+0xff1f77b4,   // SINE - blue         // SINE - red
+0xffff7f0e,   // LINE - orange       // LINE - lime
+0xff2ca02c,   // LTR - green         // LTR - maroon
+0xffd62728,   // DNA - red           // DNA - fuchsia
+0xff9467bd,   // Simple - purple     // Simple - yellow
+0xff8c564b,   // LowComplex - brown  // LowComplex - olive
+0xffe377c2,   // Satellite - pink    // Satellite - blue
+0xff7f7f7f,   // RNA - grey          // RNA - green
+0xffbcbd22,   // Other - lime        // Other - teal
+0xff17becf,   // Unknown - aqua      // Unknown - aqua
 };
 
 
-
-
-
-
 static int cmpStartEnd(const void *va, const void *vb)
 /* Sort repeats by alignStart (thickStart) ascending and alignEnd
  * (thickEnd) descending. ie. sort by position (longest first)
  */
 {
 struct bigRmskRecord *a = *((struct bigRmskRecord **) va);
 struct bigRmskRecord *b = *((struct bigRmskRecord **) vb);
 
 unsigned startDiff = (a->alignStart - b->alignStart);
 if ( startDiff != 0 ) 
   return (startDiff);
 return (b->alignEnd - a->alignEnd);
 }
 
 
@@ -212,30 +211,33 @@
  * text elements within the glyph.
  */
 {
 int start;
 int end;
 char lenLabel[20];
 
 // Start position is anchored by the alignment start
 // coordinates.  Then we subtract from that space for
 // the label.
 if ( showLabels ) 
     {
     start = rm->alignStart -
             (int) ((mgFontStringWidth(tl.font, rm->name) +
             LABEL_PADDING) / pixelsPerBase);
+    // Fix for redmine #29473
+    if ( start < 0 )
+        start = 0;
     }
 else
     {
     start = rm->alignStart;
     }
 
 // Now subtract out space for the unaligned sequence
 // bar starting off the graphics portion of the glyph.
 if ( showUnalignedExtents ) 
     {
     if ((rm->blockSizes[0] * pixelsPerBase) > MAX_UNALIGNED_PIXEL_LEN)
         {
         // Unaligned sequence bar needs length label -- account for that.
         safef(lenLabel, sizeof(lenLabel), "%d", rm->blockSizes[0]);
         start -= (int) (MAX_UNALIGNED_PIXEL_LEN / pixelsPerBase) +
@@ -265,30 +267,31 @@
         end += rm->blockSizes[rm->blockCount - 1];
         }
     }
 
 *pStart = start;
 *pEnd = end;
 }
 
 
 bool overlap(struct bigRmskRecord *a, struct bigRmskRecord *b, int tolerance) 
 /* Determine if two glyphs will overlap */
 {
 return ((a->visualStart - tolerance) <= b->visualEnd) && ((a->visualEnd + tolerance) >= b->visualStart);
 }
 
+
 struct Graph *newGraph(struct bigRmskRecord **recs, int n) 
 /* Create a new layout graph */
 {
 struct Graph *g = malloc(sizeof(struct Graph));
 g->n = n;
 g->edges = malloc((n * n) * sizeof(int));
 g->degrees = malloc((n) * sizeof(int));
 
 for (int i = 0; i < n; i++) 
     {
     g->degrees[i] = 0;
     for (int j = 0; j < n; j++) 
         {
         g->edges[i * n + j] = -1;
         }
@@ -301,64 +304,75 @@
     for (int idxB = 0; idxB < n; idxB++) 
         {
         struct bigRmskRecord annB = *recs[idxB];
         if (idxA == idxB)
             continue;
         if (overlap(&annA, &annB, 0)) 
             {
             g->edges[(idxA * n) + g->degrees[idxA]] = idxB;
             g->degrees[idxA]++;
             }
         }
     }
 return (g);
 }
 
+
 void removeIdx(int idx, int *arr, int nElements) 
+/* Remove entry in list by overwriting element at position idx and
+ * shifting all remaining elements down by one position.
+ * Support routine for detailedLayout(). 
+ */
 {
 for (int i = idx; i < nElements - 1; i++) 
     {
     arr[i] = arr[i + 1];
     }
 arr[nElements - 1] = -1;
 }
 
+
 void swap(int *xp, int *yp) 
+/* Swap elements in a list.  Support routine
+ * for detailedLayout()
+ */
 {
 int temp = *xp;
 *xp = *yp;
 *yp = temp;
 }
 
+
 void sortRemainingByWidth(int *remaining, struct bigRmskRecord **recs, int n) 
 {
 int i, j, maxIdx;
 struct bigRmskRecord *a, *b;
 for (i = 0; i < n - 1; i++) 
     {
     maxIdx = i;
     for (j = i + 1; j < n; j++) 
         {
         a = recs[remaining[j]];
         b = recs[remaining[maxIdx]];
         if ((a->alignEnd - a->alignStart) > (b->alignEnd - b->alignStart))
             maxIdx = j;
         }
     swap(&remaining[maxIdx], &remaining[i]);
     }
 }
 
+
 void detailedLayout(struct bigRmskRecord *firstRec, double pixelsPerBase) {
 /*
  * Compute a non-overlapping layout for a list of annotations
  *
  * We define a graph in which each annotation is represented as a node.
  * Nodes share an edge if the annotations they represent horizontally overlap.
  *
  * In each iteration, this algorithm picks the first available node and then
  * greedily grows an independent set. The nodes in each independent set are
  * assigned a unique color/row. Once a node is assigned a color, it is permanently
  * removed from further consideration.
  *
  * For book keeping, we keep two lists: a list of remaining uncolored node
  * indices, and a boolean list that describes whether or not a node is available
  * during the current iteration.
@@ -443,30 +457,33 @@
 /* After the items have been loaded and before any rendering occurs, 
  * this function delegates the layout based on the current visualization
  * type.  
  *
  * I am not sure how permissible it is to access winStart/winEnd/insideWidth
  * as globals.  This seems to be the pattern that I see used in other tracks
  * but it makes me uncomfortable.
  */
 int baseWidth = winEnd - winStart;
 double pixelsPerBase = scaleForPixels(insideWidth);
 struct bigRmskRecord *items = tg->items;
 
 boolean showLabels = cartUsualBoolean(cart, BIGRMSK_SHOW_LABELS, BIGRMSK_SHOW_LABELS_DEFAULT);
 boolean origPackViz = cartUsualBoolean(cart, BIGRMSK_ORIG_PACKVIZ, BIGRMSK_ORIG_PACKVIZ_DEFAULT);
 
+if (tg->visibility == tvSquish )
+  showLabels = FALSE;
+
 if (tg->visibility == tvFull && baseWidth <= DETAIL_VIEW_MAX_SCALE)
     {
     // Perform the most detailed glyph layout aka "Full" mode.
     detailedLayout(items, pixelsPerBase);
     } 
 else
     {
     // Either class based or single row...dense
     char class[256];
     struct classRecord *cr;
 
     if ( origPackViz ) 
         {
         // Original "pack" visualization displays greyscale box annotations
         // for each "unjoined" feature on a repeat class specific row.
@@ -553,58 +570,56 @@
  * of the layout to determine how many "items" to generate.  
  */
 {
 int i;
 if ( classHashBR == NULL )
     {
     struct classRecord *cr;
     int numClasses = ArraySize(rptClasses);
     classHashBR = newHash(6);
     for (i = 0; i < numClasses; ++i)
         {
         AllocVar(cr);
         cr->className = rptClassNames[i];
         cr->layoutLevel = i;
         unsigned int colorInt = rmskJoinedClassColors[i];
-        cr->color = MAKECOLOR_32(((colorInt >> 16) & 0xff),((colorInt >> 8)
-& 0xff),((colorInt >> 0) & 0xff));
+        cr->color = MAKECOLOR_32(((colorInt >> 16) & 0xff),((colorInt >> 8) & 0xff),((colorInt >> 0) & 0xff));
         hashAdd(classHashBR, rptClasses[i], cr);
         }
     }
 
 struct bigRmskRecord *detailList = NULL;
 if (tg->isBigBed)
     {
     struct bbiFile *bbi = fetchBbiForTrack(tg);
     if (bbi->fieldCount < 14)
         errAbort("track %s has a bigBed being read as a bed14 that has %d columns.", tg->track, bbi->fieldCount);
     struct lm *lm = lmInit(0);
     struct bigBedInterval *bb, *bbList =  bigBedIntervalQuery(bbi, chromName, winStart, winEnd, 0, lm);
     char *bedRow[bbi->fieldCount];
     /* One might ask, "Why didn't you use a linkedFeature set here?" Good question!  I am not using
        a standard encoding of blockStarts as would be expected by a BED12 validator, and therefore
        I must use BED9+ and handle the blocks myself. */
     char startBuf[16], endBuf[16];
 
     char *filterString = cartUsualString(cart, BIGRMSK_NAME_FILTER, BIGRMSK_NAME_FILTER_DEFAULT);
     boolean isFilterRegexp = cartUsualBoolean(cart, BIGRMSK_REGEXP_FILTER, BIGRMSK_REGEXP_FILTER_DEFAULT);
     regex_t regEx;
 
     if (isFilterRegexp)
         regcomp(&regEx, filterString, REG_NOSUB);
 
-
     for (bb = bbList; bb != NULL; bb = bb->next)
         {
         bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow));
 
         if ( (isFilterRegexp && (regexec(&regEx,bedRow[3], 0, NULL,0 ) == 0)) ||
              (!isFilterRegexp && wildMatch(filterString, bedRow[3])) )
 
             {
             struct bigRmskRecord *rm = NULL;
             AllocVar(rm);
             rm->next = NULL;
             rm->chrom = cloneString(bedRow[0]);
             rm->chromStart = sqlUnsigned(bedRow[1]);
             rm->chromEnd = sqlUnsigned(bedRow[2]);
             rm->name = cloneString(bedRow[3]);
@@ -615,49 +630,118 @@
             // RGB is unused? bedRow[8]
             rm->blockCount = sqlUnsigned(bedRow[9]);
             {
             int sizeOne;
             sqlSignedDynamicArray(bedRow[10], &rm->blockSizes, &sizeOne);
             assert(sizeOne == rm->blockCount);
             }
             {
             int sizeOne;
             sqlSignedDynamicArray(bedRow[11], &rm->blockRelStarts, &sizeOne);
             assert(sizeOne == rm->blockCount);
             }
             rm->id = sqlUnsigned(bedRow[12]);
             rm->description = cloneString(bedRow[13]);
             rm->layoutLevel = -1;  // Indicates layout has not been calculated.
+            rm->leftLabel = 0;     // Flag for labels that need to be leftLabeled
             slAddHead(&detailList, rm);
             }
         } // for(bb = bbList...
     bbiFileClose(&bbi);
     lmCleanup(&lm);
     } // if (tg->isBigBed...
       //Could also...have DB loader here in the future.
     tg->items = detailList;
     // Man, I went back and forth on this one.  I *wish* there was
     // an API for layout in between the loadItems() and totalHeight()
     // stubs. The totalHeight() method is called multiple times which
     // isn't ideal for layout operations since the layout logic needs
     // to detect if it needs to really re-layout the data (expensive).
     // For now, this is the only way to be assured that it gets called
     // once for real changes in the visual appearance.
     bigRmskLayoutItems(tg);
 }
   
 
+int bigRmskItemHeight(struct track *tg, void *item)
+{
+if (tg->limitedVis == tvDense) 
+    tg->heightPer = tl.fontHeight;
+else if (tg->limitedVis == tvPack)
+    tg->heightPer = tl.fontHeight;
+else if (tg->limitedVis == tvSquish)
+    tg->heightPer = tl.fontHeight/2;
+else if (tg->limitedVis == tvFull )
+    {
+    if ( winBaseCount <= DETAIL_VIEW_MAX_SCALE)
+        tg->heightPer = max(tl.fontHeight, MINHEIGHT);
+    else 
+        tg->heightPer = tl.fontHeight;
+    }
+tg->lineHeight = tg->heightPer + 1;
+
+return(tg->heightPer);
+}
+
+
+int bigRmskTotalHeight(struct track *tg, enum trackVisibility vis)
+{
+boolean origPackViz = cartUsualBoolean(cart, BIGRMSK_ORIG_PACKVIZ, BIGRMSK_ORIG_PACKVIZ_DEFAULT);
+
+bigRmskItemHeight(tg, (void *)NULL);
+
+int count = slCount(tg->items);
+if ( count > MAX_PACK_ITEMS || vis == tvDense ) 
+    {
+    tg->height = tg->lineHeight;
+    return(tg->height);
+    }
+else
+    {
+    // Count the layout depth 
+    struct bigRmskRecord *cr;
+    int numLevels = 0;
+    for ( cr = tg->items; cr != NULL; cr = cr->next )
+        {
+        if ( cr->layoutLevel > numLevels )
+            numLevels = cr->layoutLevel;
+        }
+    numLevels++;
+
+    if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
+        {
+        tg->height = numLevels * max(tg->heightPer, MINHEIGHT);
+        }
+    else 
+        {
+        if ( origPackViz ) 
+            {
+            // Original class-per-line track
+            int numClasses = ArraySize(rptClasses);
+            tg->height = numClasses * tg->lineHeight;
+            }
+        else
+            {
+            // Color pack track
+            tg->height = numLevels * tg->lineHeight;
+            }
+        }
+    return(tg->height);
+    }
+}
+
+
 static void drawDenseGlyphs(struct track *tg, int seqStart, int seqEnd,
          struct hvGfx *hvg, int xOff, int yOff, int width,
          MgFont * font, Color color, enum trackVisibility vis)
 /* draw 'dense' mode track */
 {
 int x1, x2, w;
 int baseWidth = seqEnd - seqStart;
 int heightPer = tg->heightPer;
 struct classRecord *ri;
 Color col;
 
 struct bigRmskRecord *cr;
 for (cr = tg->items; cr != NULL; cr = cr->next)
     {
     // Break apart the name and get the class of the
@@ -713,78 +797,103 @@
 }
 
 
 static void drawOrigPackGlyphs(struct track *tg, int seqStart, int seqEnd,
          struct hvGfx *hvg, int xOff, int yOff, int width,
          MgFont * font, Color color, enum trackVisibility vis)
 /* Do grayscale representation spread out individual class-specific rows */
 {
 int y = yOff;
 int percId;
 int grayLevel;
 Color col;
 int x1, x2, w;
 int baseWidth = seqEnd - seqStart;
 int heightPer = tg->heightPer;
+// Added per ticket #29469
+char idStr[80];
+char statusLine[128];
+
+// bugfix ticket #28644
+int trackHeight = bigRmskTotalHeight(tg, vis);
+hvGfxSetClip(hvg, xOff, yOff, width, trackHeight);
 
 struct bigRmskRecord *cr;
 for (cr = tg->items; cr != NULL; cr = cr->next)
     {
     percId = 1000 - cr->score;
     grayLevel = grayInRange(percId, 500, 1000);
     col = shadesOfGray[grayLevel];
 
+    // Get id for click handler
+    sprintf(idStr,"%d",cr->id);
+
     int idx = 0;
     for (idx = 0; idx < cr->blockCount; idx++)
         {
         if (cr->blockRelStarts[idx] >= 0)
             {
             int blockStart = cr->chromStart + cr->blockRelStarts[idx];
             int blockEnd = cr->chromStart + cr->blockRelStarts[idx] +
                        cr->blockSizes[idx];
 
             x1 = roundingScale(blockStart - winStart, width,
                            baseWidth) + xOff;
             x1 = max(x1, 0);
             x2 = roundingScale(blockEnd - winStart, width,
                            baseWidth) + xOff;
             y = yOff + (cr->layoutLevel * tg->lineHeight);
             w = x2 - x1;
             if (w <= 0)
                 w = 1;
             hvGfxBox(hvg, x1, y, w, heightPer, col);
+
+            // feature change ticket #29469
+            safef(statusLine, sizeof(statusLine), "%s", cr->name);
+            mapBoxHc(hvg, cr->alignStart, cr->alignEnd, x1, y,
+                w, heightPer, tg->track, idStr, statusLine);
             }
         }
     }//for (cr = ri->itemData...
 }
 
 
 static void drawPackGlyphs(struct track *tg, int seqStart, int seqEnd,
          struct hvGfx *hvg, int xOff, int yOff, int width,
          MgFont * font, Color color, enum trackVisibility vis)
 /* Do color representation packed closely */
 {
 boolean showLabels = cartUsualBoolean(cart, BIGRMSK_SHOW_LABELS, BIGRMSK_SHOW_LABELS_DEFAULT);
+if ( vis == tvSquish ) 
+  showLabels = FALSE;
 int y = yOff;
 Color col;
 Color black = hvGfxFindColorIx(hvg, 0, 0, 0);
 int x1, x2, w;
 int baseWidth = seqEnd - seqStart;
 int heightPer = tg->heightPer;
 struct bigRmskRecord *cr;
 struct classRecord *ri;
 int fontHeight = mgFontLineHeight(font);
+// Added per ticket #29469
+char idStr[80];
+char statusLine[128];
+
+// bugfix ticket #28644
+int trackHeight = bigRmskTotalHeight(tg, vis);
+hvGfxSetClip(hvg, xOff, yOff, width, trackHeight);
+
 for (cr = tg->items; cr != NULL; cr = cr->next)
     {
     // Break apart the name and get the class of the
     // repeat.
     char family[256];
     char class[256];
     // Simplify repClass for lookup: strip trailing '?',
     // simplify *RNA to RNA:
     char *poundPtr = index(cr->name, '#');
     if (poundPtr)
         {
         safecpy(family, sizeof(family), cr->name);
         poundPtr = index(family, '#');
         *poundPtr = '\0';
         safecpy(class, sizeof(class), poundPtr + 1);
@@ -802,59 +911,78 @@
         *p = '\0';
     if (endsWith(class, "RNA"))
         safecpy(class, sizeof(class), "RNA");
 
     // Lookup the class to get the color scheme
     ri = hashFindVal(classHashBR, class);
     if (ri == NULL)
         ri = hashFindVal(classHashBR, "Other");
     col = ri->color;
 
     if ( showLabels ) 
         {
         int stringWidth = mgFontStringWidth(font, family) + LABEL_PADDING;
 
         x1 = roundingScale(cr->alignStart - winStart, width,
-                               baseWidth) + xOff;
-        x1 = max(x1, 0);
+                               baseWidth); 
+        // Remove labels that overlap the window and use leftLabel instead
+        if ( x1-stringWidth > 0 ) 
+            {
             y = yOff + (cr->layoutLevel * tg->lineHeight);
-        hvGfxTextCentered(hvg, x1 - stringWidth,
+            hvGfxTextCentered(hvg, x1 + xOff - stringWidth,
                       heightPer - fontHeight + y,
                       stringWidth, fontHeight, MG_BLACK, font,
                       family);
+            cr->leftLabel = 0;
+            }else
+            { 
+            x1 = roundingScale(cr->alignEnd - winStart, width,
+                               baseWidth);
+            if ( x1 > 0 ) 
+                cr->leftLabel = 1;
             }
+        }
+
+    // Get id for click handler
+    sprintf(idStr,"%d",cr->id);
 
     int idx = 0;
     int prevBlockEnd = -1;
     for (idx = 0; idx < cr->blockCount; idx++)
         {
         if (cr->blockRelStarts[idx] >= 0)
             {
             int blockStart = cr->chromStart + cr->blockRelStarts[idx];
             int blockEnd = cr->chromStart + cr->blockRelStarts[idx] +
                        cr->blockSizes[idx];
 
             x1 = roundingScale(blockStart - winStart, width,
                            baseWidth) + xOff;
             x1 = max(x1, 0);
             x2 = roundingScale(blockEnd - winStart, width,
                            baseWidth) + xOff;
             y = yOff + (cr->layoutLevel * tg->lineHeight);
             w = x2 - x1;
             if (w <= 0)
                 w = 1;
             hvGfxBox(hvg, x1, y, w, heightPer, col);
+
+            // feature change ticket #29469
+            safef(statusLine, sizeof(statusLine), "%s", cr->name);
+            mapBoxHc(hvg, cr->alignStart, cr->alignEnd, x1, y,
+                w, heightPer, tg->track, idStr, statusLine);
+
             int midY = y + (heightPer>>1);
             int dir = 0;
             if (cr->strand[0] == '+')
                 dir = 1;
             else if (cr->strand[0] == '-')
                 dir = -1;
             Color textColor = hvGfxContrastingColor(hvg, col);
             clippedBarbs(hvg, x1, midY, w, tl.barbHeight, tl.barbSpacing,
                          dir, textColor, TRUE);
             if ( prevBlockEnd > 0 )
                 {
                 int px1 = roundingScale(prevBlockEnd - winStart, width,
                            baseWidth) + xOff;
                 px1 = max(px1, 0);
                 hvGfxLine(hvg, px1, midY, x1, midY, black);
@@ -867,88 +995,30 @@
  
 
 static void bigRmskFreeItems(struct track *tg)
 /* Free up rmskJoinedMasker items. */
 {
 slFreeList(&tg->items);
 }
 
 static char * bigRmskItemName(struct track *tg, void *item)
 // This is really not necessary...but placed it here anyway...
 {
   return "";
 }
 
 
-int bigRmskItemHeight(struct track *tg, void *item)
-{
-if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
-    {
-    return max(tg->heightPer, MINHEIGHT);
-    }
-else
-    {
-      return tgFixedItemHeight(tg, item);
-    }
-}
-
-
-int bigRmskTotalHeight(struct track *tg, enum trackVisibility vis)
-{
-boolean origPackViz = cartUsualBoolean(cart, BIGRMSK_ORIG_PACKVIZ, BIGRMSK_ORIG_PACKVIZ_DEFAULT);
-
-int count = slCount(tg->items);
-if ( count > MAX_PACK_ITEMS || vis == tvDense ) 
-    {
-    tg->height = tgFixedTotalHeightNoOverflow(tg, tvDense );
-    return tgFixedTotalHeightNoOverflow(tg, tvDense );
-    }
-else
-    {
-    // Count the layout depth 
-    struct bigRmskRecord *cr;
-    int numLevels = 0;
-    for ( cr = tg->items; cr != NULL; cr = cr->next )
-        {
-        if ( cr->layoutLevel > numLevels )
-            numLevels = cr->layoutLevel;
-        }
-    numLevels++;
-
-    if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
-        {
-        tg->height = numLevels * max(tg->heightPer, MINHEIGHT);
-        }
-    else 
-        {
-        if ( origPackViz ) 
-            {
-            // Original class-per-line track
-            int numClasses = ArraySize(rptClasses);
-            tg->height = numClasses * tg->lineHeight;
-            }
-        else
-            {
-            // Color pack track
-            tg->height = numLevels * tg->lineHeight;
-            }
-        }
-    return(tg->height);
-    }
-}
-
-
 static void drawDashedHorizLine(struct hvGfx *hvg, int x1, int x2,
              int y, int dashLen, int gapLen, Color lineColor)
 // ie.    - - - - - - - - - - - - - - - -
 {
 int cx1 = x1;
 int cx2;
 while (1)
     {
     cx2 = cx1 + dashLen;
     if (cx2 > x2)
         cx2 = x2;
     hvGfxLine(hvg, cx1, y, cx2, y, lineColor);
     cx1 += (dashLen + gapLen);
     if (cx1 > x2)
         break;
@@ -1308,56 +1378,86 @@
                                              5, 5, black, rm->blockSizes[idx]);
                     // Line down to box
                     hvGfxLine(hvg, lx2, y + alignedBlockOffset, lx2,
                               y + unalignedBlockOffset, black);
                     // extension cap "|---"
                     hvGfxLine(hvg, lx1, y + unalignedBlockOffset - 3, lx1,
                               y + unalignedBlockOffset + 3, black);
      
                     }
                 else
                     {
                     lx1 = roundingScale(rm->chromStart - winStart,
                                         width, baseWidth) + xOff;
                     // Only display extension if the resolution is high enough to
                     // fully see the extension line and cap distinctly: eg. "|--" 
-                    // TODO: Make this is constant
                     if ( lx2-lx1 >= MIN_VISIBLE_EXT_PIXELS)
                         {
                         // Line Across
                         drawDashedHorizLine(hvg, lx1, lx2,
                                             y + unalignedBlockOffset, 5, 5, black);
                         // Line down to box
                         hvGfxLine(hvg, lx2, y + alignedBlockOffset, lx2,
                                   y + unalignedBlockOffset, black);
                         // extension cap "|---"
                         hvGfxLine(hvg, lx1, y + unalignedBlockOffset - 3, lx1,
                                   y + unalignedBlockOffset + 3, black);
                         }
                     }
                }
 
             if ( showLabels ) 
                 {
                 MgFont *font = tl.font;
                 int fontHeight = tl.fontHeight;
                 int stringWidth =
                      mgFontStringWidth(font, rm->name) + LABEL_PADDING;
+
+                if ( lx1-stringWidth-xOff > 0 ) 
+                    {
                     hvGfxTextCentered(hvg, lx1 - stringWidth,
                               heightPer - fontHeight + y,
                               stringWidth, fontHeight, MG_BLACK, font,
                               rm->name);
+                    rm->leftLabel = 0;
+                    }
+                else
+                    { 
+                    int end = rm->alignEnd;
+
+                    if ( showUnalignedExtents )
+                        {
+                        if ((rm->blockSizes[rm->blockCount - 1] * pixelsPerBase) > MAX_UNALIGNED_PIXEL_LEN)
+                            {
+                            char lenLabel[20];
+                            safef(lenLabel, sizeof(lenLabel), "%d",
+                                  rm->blockSizes[rm->blockCount - 1]);
+                            end += (int) (MAX_UNALIGNED_PIXEL_LEN / pixelsPerBase) +
+                                   (int) ((mgFontStringWidth(tl.font, lenLabel) +
+                                   LABEL_PADDING) / pixelsPerBase);
+                            }
+                        else
+                            {
+                            end += rm->blockSizes[rm->blockCount - 1];
+                            }
+                        }
+
+                    int endx = roundingScale(rm->chromEnd - winStart,
+                                        width, baseWidth);
+                    if ( endx > 0 ) 
+                        rm->leftLabel = 1;
+                    }
                 }
             }
         else if (idx == (rm->blockCount - 1))
             {
             if ( showUnalignedExtents )
                 {
                 /*
                  * Unaligned sequence at the end of an annotation
                  * Draw as:
                  *       -------------|   or        ------//------|
                  *       |                          |
                  *  >>>>>                    >>>>>>>
                  */
                 lx1 = roundingScale(rm->chromStart +
                          rm->blockRelStarts[idx - 1] +
@@ -1389,31 +1489,30 @@
                                         rm->blockRelStarts[idx - 1] +
                                         rm->blockSizes[idx - 1] +
                                         rm->blockSizes[idx] -
                                         winStart, width, baseWidth) + xOff;
                     if ( lx2-lx1 >= MIN_VISIBLE_EXT_PIXELS)
                         {
                         // Line Across
                         drawDashedHorizLine(hvg, lx1, lx2,
                                             y + unalignedBlockOffset, 5, 5, black);
                         // Line down to box
                         hvGfxLine(hvg, lx1, y + alignedBlockOffset, lx1,
                                   y + unalignedBlockOffset, black);
                         // Cap "--|"
                         hvGfxLine(hvg, lx2, y + unalignedBlockOffset - 3, lx2,
                                   y + unalignedBlockOffset + 3, black);
-     
                         }
                     }
                 }
             }
         else
             {
             /*
              * Middle Unaligned
              * Draw unaligned sequence between aligned fragments
              * as:       .........
              *          /         \
              *     >>>>>           >>>>>>
              *
              * or:
              *         .............
@@ -1532,36 +1631,36 @@
                              y + unalignedBlockOffset, 5, 5, black);
                 // Line Down
                 cx2 = roundingScale(rm->chromStart +
                              rm->blockRelStarts[idx +
                                     1] -
                              winStart, width, baseWidth) + xOff;
                 hvGfxLine(hvg, cx2, y + alignedBlockOffset, lx2,
                        y + unalignedBlockOffset, black);
                 }
             }
         } // if ( fragStart .. else {
     } // for(idx=..
 }
 
 
-/* Main callback for displaying this track in the viewport
- * of the browser.
- */
 static void bigRmskDrawItems(struct track *tg, int seqStart, int seqEnd,
          struct hvGfx *hvg, int xOff, int yOff, int width,
          MgFont * font, Color color, enum trackVisibility vis)
+/* Main callback for displaying this track in the viewport
+ * of the browser.
+ */
 {
 int baseWidth = seqEnd - seqStart;
 /*
  * Its unclear to me why heightPer is not updated to the
  * value set in bigRmskItemHeight() at the time this callback
  * is invoked.  Getting the correct value myself.
  * was:
  * int heightPer = tg->heightPer;
  */
 int heightPer = bigRmskItemHeight(tg, NULL);
 struct bigRmskRecord *rm;
 boolean showUnalignedExtents = cartUsualBoolean(cart, BIGRMSK_SHOW_UNALIGNED_EXTENTS, BIGRMSK_SHOW_UNALIGNED_EXTENTS_DEFAULT);
 boolean showLabels = cartUsualBoolean(cart, BIGRMSK_SHOW_LABELS, BIGRMSK_SHOW_LABELS_DEFAULT);
 boolean origPackViz = cartUsualBoolean(cart, BIGRMSK_ORIG_PACKVIZ, BIGRMSK_ORIG_PACKVIZ_DEFAULT);
 
@@ -1644,33 +1743,54 @@
                    heightPer, labelColor, font, "Repeats");
     }
 else if ( vis == tvPack && origPackViz ) 
     {
     int i;
     int numClasses = ArraySize(rptClasses);
     for (i = 0; i < numClasses; ++i)
         {
         hvGfxTextRight(hvg, xOff, y, width - 1,
                        heightPer, labelColor, font, rptClassNames[i]);
         y += heightPer;
         }
      }
 else 
     {
-    y += (tg->height-fontHeight)/2;
-    hvGfxTextRight(hvg, xOff, y, width - 1,
-                   heightPer, labelColor, font, "Repeats");
+    struct bigRmskRecord *cr;
+    for (cr = tg->items; cr != NULL; cr = cr->next)
+        {
+        if ( cr->leftLabel ) 
+            {
+            // Break apart the name and get the class of the
+            // repeat.
+            char family[256];
+            // Simplify repClass for lookup: strip trailing '?',
+            // simplify *RNA to RNA:
+            char *poundPtr = index(cr->name, '#');
+            safecpy(family, sizeof(family), cr->name);
+            if (poundPtr)
+                {
+                poundPtr = index(family, '#');
+                *poundPtr = '\0';
+                }
+            y = yOff + ((cr->layoutLevel+1) * tg->lineHeight);
+            hvGfxSetClip(hvgSide, leftLabelX, y, insideWidth, tg->height);
+            hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->lineHeight,
+                           color, font, family);
+            hvGfxUnclip(hvgSide);
+            }
+        }
     }
 }
 
 
 void bigRmskMethods(struct track *track, struct trackDb *tdb,
                   int wordCount, char *words[])
 {
 bigBedMethods(track, tdb, wordCount, words);
 track->loadItems = bigRmskLoadItems;
 track->totalHeight = bigRmskTotalHeight;
 track->drawItems = bigRmskDrawItems;
 track->drawLeftLabels = bigRmskLeftLabels;
 track->freeItems = bigRmskFreeItems;
 track->colorShades = shadesOfGray;
 track->itemName = bigRmskItemName;