6768623f4a07b2b790c6c091031174d348072df0
hiram
  Fri Aug 1 12:17:30 2014 -0700
rename file to be more consistent refs #13706
diff --git src/hg/hgTracks/rmskJoinedTrack.c src/hg/hgTracks/rmskJoinedTrack.c
new file mode 100644
index 0000000..d1aead8
--- /dev/null
+++ src/hg/hgTracks/rmskJoinedTrack.c
@@ -0,0 +1,1182 @@
+/* joinedRmskTrack - A comprehensive RepeatMasker visualization track
+ *                   handler. This is an extension of the original
+ *                   rmskTrack.c written by UCSC.
+ *
+ *  Written by Robert Hubley 10/2012
+ *
+ *  Modifications:
+ *
+ *  7/2014
+ *     With the help of Jim Kent we modified the
+ *     glyph design to more closely resemble the
+ *     existing browser styles.
+ */
+
+/* Copyright (C) 2014 The Regents of the University of California 
+ * See README in this or parent directory for licensing information. */
+
+#include "common.h"
+#include "hash.h"
+#include "linefile.h"
+#include "jksql.h"
+#include "hdb.h"
+#include "hgTracks.h"
+#include "rmskJoined.h"
+
+
+/* DETAIL_VIEW_MAX_SCALE: The size ( max bp ) at which the
+ * detailed view of repeats is turned on, otherwise it reverts
+ * back to the original UCSC glyph.
+ */
+#define DETAIL_VIEW_MAX_SCALE 30000
+
+/* MAX_UNALIGNED_PIXEL_LEN: The maximum size of unaligned sequence
+ * to draw before switching to the unscaled view: ie. "----/###/---"
+ * This is measured in pixels *not* base pairs due to the
+ * mixture of scaled ( lines ) and unscaled ( text ) glyph
+ * components.
+ */
+#define MAX_UNALIGNED_PIXEL_LEN 150
+
+// TODO: Document
+#define LABEL_PADDING 5
+
+
+// TODO: Document
+static float pixelsPerBase = 1.0;
+
+/* Hash of the repeatItems ( tg->items ) for this track.
+ *   This is used to hold display names for the lines of
+ *   the track, the line heights, and class colors.
+ */
+struct hash *classHash = NULL;
+static struct repeatItem *otherRepeatItem = NULL;
+
+/* Hash of subtracks
+ *   The joinedRmskTrack is designed to be used as a composite track.
+ *   When jRepeatLoad is called this hash is populated with the
+ *   results of one or more table queries
+ */
+struct hash *subTracksHash = NULL;
+
+struct subTrack
+{
+  // The number of display levels used in levels[]
+int levelCount;
+  // The rmskJoined records from table query
+struct rmskJoined *levels[30];
+};
+
+
+// Repeat Classes: Display names
+static char *rptClassNames[] = {
+"SINE", "LINE", "LTR", "DNA", "Simple", "Low Complexity", "Satellite",
+"RNA", "Other", "Unknown",
+};
+
+// Repeat Classes: Data names
+static char *rptClasses[] = {
+"SINE", "LINE", "LTR", "DNA", "Simple_repeat", "Low_complexity",
+"Satellite", "RNA", "Other", "Unknown",
+};
+
+
+/* Repeat class to color mappings. I took these from a
+ * online color dictionary website:
+ *
+ *     http://people.csail.mit.edu/jaffer/Color/Dictionaries
+ *
+ *  I used the html4 catalog and tried to the 10 most distinct
+ *  colors.
+ *
+ *  NOTE: If these are changed, do not forget to update the
+ *        help table in joinedRmskTrack.html
+ */
+static Color jRepeatClassColors[] = {
+0xff0000ff,			// SINE - red
+0xff00ff00,			// LINE - lime
+0xff000080,			// LTR - maroon
+0xffff00ff,			// DNA - fuchsia
+0xff00ffff,			// Simple - yellow
+0xff008080,			// LowComplex - olive
+0xffff0000,			// Satellite - blue
+0xff008000,			// RNA - green
+0xff808000,			// Other - teal
+0xffffff00,			// Unknown - aqua
+};
+
+// Basic range type
+struct Extents
+{
+int start;
+int end;
+};
+
+static struct Extents * getExtents (struct rmskJoined *rm)
+/* Calculate the glyph extents in genome coordinate
+ * space ( ie. bp )
+ *
+ * It is not straightforward to determine the extent
+ * of this track's glyph.  This is due to the mixture
+ * of graphic and text elements within the glyph.
+ *
+ * TODO: Consider changing to graphics (ie. pixel) space.
+ */
+{
+static struct Extents ex;
+char lenLabel[20];
+
+if (rm == NULL)
+    return NULL;
+
+ex.start = rm->alignStart
+    -
+    (int) ((mgFontStringWidth (tl.font, rm->name) +
+	    LABEL_PADDING) / pixelsPerBase);
+
+
+if ((rm->blockSizes[0] * pixelsPerBase) > MAX_UNALIGNED_PIXEL_LEN)
+    {
+    safef (lenLabel, sizeof (lenLabel), "%d", rm->blockSizes[0]);
+    ex.start -= (int) (MAX_UNALIGNED_PIXEL_LEN / pixelsPerBase) +
+	(int) ((mgFontStringWidth (tl.font, lenLabel) +
+		LABEL_PADDING) / pixelsPerBase);
+    }
+else
+    {
+    ex.start -= rm->blockSizes[0];
+    }
+
+ex.end = rm->alignEnd;
+
+if ((rm->blockSizes[rm->blockCount - 1] * pixelsPerBase) >
+    MAX_UNALIGNED_PIXEL_LEN)
+    {
+    safef (lenLabel, sizeof (lenLabel), "%d",
+	   rm->blockSizes[rm->blockCount - 1]);
+    ex.end +=
+	(int) (MAX_UNALIGNED_PIXEL_LEN / pixelsPerBase) +
+	(int) ((mgFontStringWidth (tl.font, lenLabel) +
+		LABEL_PADDING) / pixelsPerBase);
+    }
+else
+    {
+    ex.end += rm->blockSizes[rm->blockCount - 1];
+    }
+
+return &ex;
+}
+
+static int cmpRepeatVisStart (const void *va, const void *vb)
+/* Sort repeats by display start position.  Note: We
+ * account for the fact we may not start the visual
+ * display at chromStart.  See MAX_UNALIGNED_PIXEL_LEN.
+ */
+{
+struct rmskJoined *a = *((struct rmskJoined **) va);
+struct rmskJoined *b = *((struct rmskJoined **) vb);
+
+struct Extents *ext = NULL;
+ext = getExtents (a);
+int aStart = ext->start;
+ext = getExtents (b);
+int bStart = ext->start;
+
+return (aStart - bStart);
+}
+
+static struct repeatItem * makeJRepeatItems ()
+/* Initialize the track */
+{
+classHash = newHash (6);
+struct repeatItem *ri, *riList = NULL;
+int i;
+int numClasses = ArraySize (rptClasses);
+for (i = 0; i < numClasses; ++i)
+    {
+    AllocVar (ri);
+    ri->class = rptClasses[i];
+    ri->className = rptClassNames[i];
+    // New color attribute
+    ri->color = jRepeatClassColors[i];
+    slAddHead (&riList, ri);
+    // Hash now prebuilt to hold color attributes
+    hashAdd (classHash, ri->class, ri);
+    if (sameString (rptClassNames[i], "Other"))
+        otherRepeatItem = ri;
+    }
+slReverse (&riList);
+return riList;
+}
+
+static void jRepeatLoad (struct track *tg)
+/* We do the query(ies) here so we can report how deep the track(s)
+ * will be when jRepeatTotalHeight() is called later on.
+ */
+{
+  /*
+   * Initialize the subtracks hash - This will eventually contain
+   * all the repeat data for each displayed subtrack.
+   */
+if (!subTracksHash)
+    subTracksHash = newHash (20);
+
+tg->items = makeJRepeatItems ();
+
+int baseWidth = winEnd - winStart;
+pixelsPerBase = (float) insideWidth / (float) baseWidth;
+if (tg->visibility == tvFull && baseWidth <= DETAIL_VIEW_MAX_SCALE)
+    {
+    struct subTrack *st = NULL;
+    AllocVar (st);
+    if (st)
+	{
+	st->levels[0] = NULL;
+	st->levelCount = 0;
+	struct rmskJoined *rm = NULL;
+	char **row;
+	int rowOffset;
+	struct sqlConnection *conn = hAllocConn (database);
+	struct sqlResult *sr = hRangeQuery (conn, tg->table, chromName,
+					    winStart, winEnd, NULL,
+					    &rowOffset);
+	struct rmskJoined *detailList = NULL;
+	while ((row = sqlNextRow (sr)) != NULL)
+	    {
+	    rm = rmskJoinedLoad (row + rowOffset);
+	    slAddHead (&detailList, rm);
+	    }
+	slSort (&detailList, cmpRepeatVisStart);
+
+	sqlFreeResult (&sr);
+	hFreeConn (&conn);
+
+	int crChromStart, crChromEnd;
+	while (detailList)
+	    {
+	    st->levels[st->levelCount++] = detailList;
+
+	    struct rmskJoined *cr = detailList;
+	    detailList = detailList->next;
+	    cr->next = NULL;
+	    int rmChromStart, rmChromEnd;
+	    struct rmskJoined *prev = NULL;
+	    rm = detailList;
+
+	    struct Extents *ext = NULL;
+	    ext = getExtents (cr);
+	    crChromStart = ext->start;
+	    crChromEnd = ext->end;
+
+	    while (rm)
+		{
+		ext = getExtents (rm);
+		rmChromStart = ext->start;
+		rmChromEnd = ext->end;
+
+		if (rmChromStart > crChromEnd)
+		    {
+		    cr->next = rm;
+		    cr = rm;
+		    crChromStart = rmChromStart;
+		    crChromEnd = rmChromEnd;
+		    if (prev)
+			prev->next = rm->next;
+		    else
+			detailList = rm->next;
+
+		    rm = rm->next;
+		    cr->next = NULL;
+		    }
+		else
+		    {
+		    // Save for a lower level
+		    prev = rm;
+		    rm = rm->next;
+		    }
+		}		// while ( rm )
+	    }			// while ( detailList )
+	// Create Hash Entry
+	hashReplace (subTracksHash, tg->table, st);
+	}			// if ( st )
+    }				// if ( tg->visibility == tvFull
+}
+
+static void jRepeatFree (struct track *tg)
+/* Free up jRepeatMasker items. */
+{
+slFreeList (&tg->items);
+}
+
+static char * jRepeatName (struct track *tg, void *item)
+/* Return name of jRepeat item track. */
+{
+static char empty = '\0';
+struct repeatItem *ri = item;
+  /*
+   * In detail view mode the items represent different packing
+   * levels.  No need to display a label at each level.  Instead
+   * Just return a label for the first level.
+   */
+if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
+    {
+    if (strcmp (ri->className, "SINE") == 0)
+	return ("Repeats");
+    else
+	return &empty;
+    }
+return ri->className;
+}
+
+int jRepeatTotalHeight (struct track *tg, enum trackVisibility vis)
+{
+  // Are we in full view mode and at the scale needed to display
+  // the detail view?
+if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
+    {
+    // Lookup the depth of this subTrack and report it
+    struct subTrack *st = hashFindVal (subTracksHash, tg->table);
+    if (st)
+        return ((st->levelCount + 1) * 24);
+    else
+        return (24);	// Just display one line
+    }
+else
+return tgFixedTotalHeightNoOverflow (tg, vis);
+}
+
+int jRepeatItemHeight (struct track *tg, void *item)
+{
+  // Are we in full view mode and at the scale needed to display
+  // the detail view?
+if (tg->limitedVis == tvFull && winBaseCount <= DETAIL_VIEW_MAX_SCALE)
+    return 24;
+else
+    return tgFixedItemHeight (tg, item);
+}
+
+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;
+    }
+}
+
+static void drawDashedHorizLineWHash (struct hvGfx *hvg, int x1, int x2,
+			  int y, int dashLen, int gapLen, Color lineColor,
+			  int unalignedLen)
+// ie.    - - - - - - -\234\- - - - - - - - -
+{
+int cx1 = x1;
+int cx2;
+
+char lenLabel[20];
+safef (lenLabel, sizeof (lenLabel), "%d", unalignedLen);
+MgFont *font = tl.font;
+int fontHeight = tl.fontHeight;
+int stringWidth = mgFontStringWidth (font, lenLabel) + LABEL_PADDING;
+
+int glyphWidth = x2 - x1;
+
+if (glyphWidth < stringWidth + 6 + (2 * dashLen))
+    stringWidth = 0;
+
+int midX = ((glyphWidth) / 2) + x1;
+int startHash = midX - (stringWidth * 0.5);
+int midPointDrawn = 0;
+
+  /*
+     Degrade Gracefully:
+     * Too little space to draw dashes or even
+     * hash marks, give up.
+   */
+if (glyphWidth < 6 + dashLen)
+    {
+    hvGfxLine (hvg, x1, y, x2, y, lineColor);
+    return;
+    }
+if (glyphWidth < 6 + (2 * dashLen))
+    {
+    midX -= 3;
+    hvGfxLine (hvg, x1, y, midX, y, lineColor);
+    hvGfxLine (hvg, midX, y - 3, midX + 3, y + 3, lineColor);
+    hvGfxLine (hvg, midX + 3, y - 3, midX + 6, y + 3, lineColor);
+    hvGfxLine (hvg, midX + 6, y, x2, y, lineColor);
+    return;
+    }
+
+while (1)
+    {
+    cx2 = cx1 + dashLen;
+    if (cx2 > x2)
+        cx2 = x2;
+
+    if (!midPointDrawn && cx2 > startHash)
+	{
+	// Draw double slashes "\\" instead of dash
+	hvGfxLine (hvg, cx1, y - 3, cx1 + 3, y + 3, lineColor);
+	if (stringWidth)
+	    {
+	    hvGfxTextCentered (hvg, cx1 + 3, y - 3, stringWidth,
+			       fontHeight, MG_BLACK, font, lenLabel);
+	    cx1 += stringWidth;
+	    }
+	hvGfxLine (hvg, cx1 + 3, y - 3, cx1 + 6, y + 3, lineColor);
+	cx1 += 6;
+	midPointDrawn = 1;
+	}
+    else
+	{
+	// Draw a dash
+	hvGfxLine (hvg, cx1, y, cx2, y, lineColor);
+	cx1 += dashLen;
+	}
+
+    if (!midPointDrawn && cx1 + gapLen > midX)
+	{
+	// Draw double slashes "\\" instead of gap
+	hvGfxLine (hvg, cx1, y - 3, cx1 + 3, y + 3, lineColor);
+	if (stringWidth)
+	    {
+	    hvGfxTextCentered (hvg, cx1 + 3, y - 3, stringWidth,
+			       fontHeight, MG_BLACK, font, lenLabel);
+	    cx1 += stringWidth;
+	    }
+	hvGfxLine (hvg, cx1 + 3, y - 3, cx1 + 6, y + 3, lineColor);
+	cx1 += 6;
+	midPointDrawn = 1;
+	}
+    else
+	{
+	cx1 += gapLen;
+	}
+
+    if (cx1 > x2)
+        break;
+    }
+}
+
+static void drawNormalBox (struct hvGfx *hvg, int x1, int y1,
+	       int width, int height, Color fillColor, Color outlineColor)
+{
+struct gfxPoly *poly = gfxPolyNew ();
+int y2 = y1 + height;
+int x2 = x1 + width;
+  /*
+   *     1,5--------------2
+   *       |              |
+   *       |              |
+   *       |              |
+   *       4--------------3
+   */
+gfxPolyAddPoint (poly, x1, y1);
+gfxPolyAddPoint (poly, x2, y1);
+gfxPolyAddPoint (poly, x2, y2);
+gfxPolyAddPoint (poly, x1, y2);
+gfxPolyAddPoint (poly, x1, y1);
+hvGfxDrawPoly (hvg, poly, fillColor, TRUE);
+hvGfxDrawPoly (hvg, poly, outlineColor, FALSE);
+gfxPolyFree (&poly);
+}
+
+static void drawBoxWChevrons (struct hvGfx *hvg, int x1, int y1,
+		  int width, int height, Color fillColor, Color outlineColor,
+		  char strand)
+{
+drawNormalBox (hvg, x1, y1, width, height, fillColor, outlineColor);
+static Color white = 0xffffffff;
+int dir = (strand == '+' ? 1 : -1);
+int midY = y1 + ((height) >> 1);
+clippedBarbs (hvg, x1 + 1, midY, width, ((height >> 1) - 2), 5,
+	      dir, white, TRUE);
+}
+
+static void drawRMGlyph (struct hvGfx *hvg, int y, int heightPer,
+	     int width, int baseWidth, int xOff, struct rmskJoined *rm)
+/*
+ *  Draw a detailed RepeatMasker annotation glyph given
+ *  a single rmskJoined structure.
+ *
+ *  A couple of caveats about the use of the rmskJoined
+ *  structure to hold a RepeatMasker annotation.
+ *
+ *  chromStart/chromEnd:  These represent genomic coordinates
+ *                        that mark the extents of graphics
+ *                        annotation on the genome.  I.e
+ *                        aligned blocks + unaligned consensus
+ *                        blocks.  The code below may not
+ *                        draw to these extents in some cases.
+ *
+ *  score:  This represents the average divergence of the
+ *          individual aligned blocks.  It is a percentage
+ *          * 100.  Ie. 23.11 % is encoded as the integer 2311.
+ *
+ *  blockRelStarts:  Two types of blocks are interwoven in this
+ *                format.  Aligned and unaligned blocks.  Aligned
+ *                blocks will have a value >= 0 representing the
+ *                relative position from chromStart for this
+ *                start of this block.  A value of -1 for chromStart
+ *                is indicative of unaligned block and starts from
+ *                the end of the last aligned block on the chromosome
+ *                or from chromStart if it's the first block in the list.
+ *
+ *  name:  Stores the id#class/family
+ *
+ *  rgb:  Unused at this time
+ *
+ *  description: Unused but the intention would be to store
+ *               the RepeatMasker *.out lines which make up
+ *               all the aligned sections of this joined
+ *               annotation.
+ *
+ *  blockSizes:   As you would expect.
+ *
+ *  Here is an example:
+ *                ie.
+ *                A forward strand RM annotation from chr1:186-196
+ *                which is aligned to a 100 bp repeat from 75-84
+ *                in the consensus would be represented as:
+ *
+ *                chromStart: 111
+ *                chromEnd: 212
+ *                blockRelStarts: -1, 75, -1
+ *                blockSizes: 75, 10, 16
+ *
+ */
+{
+int idx;
+int lx1, lx2;
+int cx1, cx2;
+int w;
+struct repeatItem *ri;
+  /*
+   * heightPer is the God given height of a single
+   * track item...respect your overlord.
+   */
+int alignedBlockHeight = heightPer * 0.5;
+int alignedBlockOffset = heightPer - alignedBlockHeight;
+int unalignedBlockHeight = heightPer * 0.75;
+int unalignedBlockOffset = heightPer - unalignedBlockHeight;
+
+Color black = hvGfxFindColorIx (hvg, 0, 0, 0);
+Color fillColor = shadesOfGray[5];
+Color outlineColor = jRepeatClassColors[0];
+
+  // Break apart the name and get the class of the
+  // repeat.
+char class[256];
+  // Simplify repClass for lookup: strip trailing '?',
+  // simplify *RNA to RNA:
+char *poundPtr = index (rm->name, '#');
+if (poundPtr)
+    {
+    safecpy (class, sizeof (class), poundPtr + 1);
+    char *slashPtr = index (class, '/');
+    if (slashPtr)
+        *slashPtr = '\0';
+    }
+else
+    {
+    safecpy (class, sizeof (class), "Unknown");
+    }
+char *p = &(class[strlen (class) - 1]);
+if (*p == '?')
+    *p = '\0';
+if (endsWith (class, "RNA"))
+    safecpy (class, sizeof (class), "RNA");
+
+  // Lookup the class to get the color scheme
+ri = hashFindVal (classHash, class);
+if (ri == NULL)
+    ri = otherRepeatItem;
+
+  // Pick the fill color based on the divergence
+int percId = 10000 - rm->score;
+int grayLevel = grayInRange (percId, 6000, 10000);
+fillColor = shadesOfGray[grayLevel];
+outlineColor = ri->color;
+
+  // Draw from left to right
+for (idx = 0; idx < rm->blockCount; idx++)
+    {
+    int fragGStart = rm->blockRelStarts[idx];
+
+    /*
+     *  Assumptions about blockCount
+     *   - first aligned block = 1, the block 0 is
+     *     the unaligned consnesus either 5' or 3' of
+     *     this block.
+     *   - The last aligned block is blockCount - 1;
+     */
+    if (fragGStart > -1)
+	{
+	// Aligned Block
+	int fragSize = rm->blockSizes[idx];
+	fragGStart += rm->chromStart;
+	int fragGEnd = fragGStart + fragSize;
+	lx1 = roundingScale (fragGStart - winStart, width, baseWidth) + xOff;
+	lx1 = max (lx1, 0);
+	lx2 = roundingScale (fragGEnd - winStart, width, baseWidth) + xOff;
+	w = lx2 - lx1;
+	if (w <= 0)
+	    w = 1;
+
+	if (idx == 1 && rm->blockCount == 3)
+	    {
+	    // One and only one aligned block
+	    drawBoxWChevrons (hvg, lx1, y + alignedBlockOffset, w,
+			      alignedBlockHeight, fillColor,
+			      outlineColor, rm->strand[0]);
+	    }
+	else if (idx == 1)
+	    {
+	    // First block
+	    if (rm->strand[0] == '-')
+		{
+		// First block on negative strand is the point block
+		drawBoxWChevrons (hvg, lx1,
+				  y + alignedBlockOffset, w,
+				  alignedBlockHeight, fillColor,
+				  outlineColor, rm->strand[0]);
+		}
+	    else
+		{
+		// First block on the positive strand is the tail block
+		drawBoxWChevrons (hvg, lx1,
+				  y + alignedBlockOffset, w,
+				  alignedBlockHeight, fillColor,
+				  outlineColor, rm->strand[0]);
+
+		}
+	    }
+	else if (idx == (rm->blockCount - 2))
+	    {
+	    // Last block
+	    if (rm->strand[0] == '-')
+		{
+		// Last block on the negative strand is the tail block
+		drawBoxWChevrons (hvg, lx1,
+				  y + alignedBlockOffset, w,
+				  alignedBlockHeight, fillColor,
+				  outlineColor, rm->strand[0]);
+
+		}
+	    else
+		{
+		// Last block on the positive strand is the poitn block
+		drawBoxWChevrons (hvg, lx1,
+				  y + alignedBlockOffset, w,
+				  alignedBlockHeight, fillColor,
+				  outlineColor, rm->strand[0]);
+
+		}
+	    }
+	else
+	    {
+	    // Intermediate aligned blocks are drawn as rectangles
+	    drawBoxWChevrons (hvg, lx1, y + alignedBlockOffset, w,
+			      alignedBlockHeight, fillColor,
+			      outlineColor, rm->strand[0]);
+	    }
+
+	}
+    else
+	{
+	// Unaligned Block
+	int relStart = 0;
+	if (idx == 0)
+	    {
+	    /*
+	     * Unaligned sequence at the start of an annotation
+	     * Draw as:
+	     *     |-------------     or |------//------
+	     *                  |                      |
+	     *                  >>>>>>>                >>>>>>>>
+	     */
+	    lx2 = roundingScale (rm->chromStart +
+				 rm->blockRelStarts[idx + 1] -
+				 winStart, width, baseWidth) + xOff;
+	    if ((rm->blockSizes[idx] * pixelsPerBase) >
+		MAX_UNALIGNED_PIXEL_LEN)
+		{
+		lx1 = roundingScale (rm->chromStart +
+				     (rm->blockRelStarts[idx + 1] -
+				      (int)
+				      (MAX_UNALIGNED_PIXEL_LEN /
+				       pixelsPerBase)) -
+				     winStart, width, baseWidth) + xOff;
+		// Line Across
+		drawDashedHorizLineWHash (hvg, lx1, lx2,
+					  y +
+					  unalignedBlockOffset,
+					  5, 5, black, rm->blockSizes[idx]);
+		}
+	    else
+		{
+		lx1 = roundingScale (rm->chromStart - winStart,
+				     width, baseWidth) + xOff;
+		// Line Across
+		drawDashedHorizLine (hvg, lx1, lx2,
+				     y + unalignedBlockOffset, 5, 5, black);
+		}
+	    // Line down
+	    hvGfxLine (hvg, lx2, y + alignedBlockOffset, lx2,
+		       y + unalignedBlockOffset, black);
+	    hvGfxLine (hvg, lx1, y + unalignedBlockOffset - 3, lx1,
+		       y + unalignedBlockOffset + 3, black);
+
+	    // Draw labels
+	    MgFont *font = tl.font;
+	    int fontHeight = tl.fontHeight;
+	    int stringWidth =
+		mgFontStringWidth (font, rm->name) + LABEL_PADDING;
+	    hvGfxTextCentered (hvg, lx1 - stringWidth,
+			       y + unalignedBlockOffset + fontHeight,
+			       stringWidth, fontHeight, MG_BLACK, font,
+			       rm->name);
+
+
+	    }
+	else if (idx == (rm->blockCount - 1))
+	    {
+	    /*
+	     * Unaligned sequence at the end of an annotation
+	     * Draw as:
+	     *       -------------|   or        ------//------|
+	     *       |                          |
+	     *  >>>>>                    >>>>>>>
+	     */
+	    lx1 = roundingScale (rm->chromStart +
+				 rm->blockRelStarts[idx - 1] +
+				 rm->blockSizes[idx - 1] - winStart,
+				 width, baseWidth) + xOff;
+	    if ((rm->blockSizes[idx] * pixelsPerBase) >
+		MAX_UNALIGNED_PIXEL_LEN)
+		{
+		lx2 = roundingScale (rm->chromStart +
+				     rm->blockRelStarts[idx -
+							1] +
+				     rm->blockSizes[idx - 1] +
+				     (int)
+				     (MAX_UNALIGNED_PIXEL_LEN /
+				      pixelsPerBase) - winStart,
+				     width, baseWidth) + xOff;
+		// Line Across
+		drawDashedHorizLineWHash (hvg, lx1, lx2,
+					  y +
+					  unalignedBlockOffset,
+					  5, 5, black, rm->blockSizes[idx]);
+		}
+	    else
+		{
+		lx2 = roundingScale (rm->chromStart +
+				     rm->blockRelStarts[idx -
+							1] +
+				     rm->blockSizes[idx - 1] +
+				     rm->blockSizes[idx] -
+				     winStart, width, baseWidth) + xOff;
+		// Line Across
+		drawDashedHorizLine (hvg, lx1, lx2,
+				     y + unalignedBlockOffset, 5, 5, black);
+		}
+	    // Line down
+	    hvGfxLine (hvg, lx1, y + alignedBlockOffset, lx1,
+		       y + unalignedBlockOffset, black);
+	    hvGfxLine (hvg, lx2, y + unalignedBlockOffset - 3, lx2,
+		       y + unalignedBlockOffset + 3, black);
+	    }
+	else
+	    {
+	    /*
+	     * Middle Unaligned
+	     * Draw unaligned sequence between aligned fragments
+	     * as:       .........
+	     *          /         \
+	     *     >>>>>           >>>>>>
+	     *
+	     * or:
+	     *         .............
+	     *         \           /
+	     *     >>>>>           >>>>>>
+	     *
+	     * Also use ....//.... to indicate not to scale if
+	     * necessary.
+	     *
+	     */
+	    int alignedGapSize = rm->blockRelStarts[idx + 1] -
+		(rm->blockRelStarts[idx - 1] + rm->blockSizes[idx - 1]);
+	    int unaSize = rm->blockSizes[idx];
+
+	    int alignedOverlapSize = unaSize - alignedGapSize;
+
+	    if (unaSize < 0)
+		{
+		relStart =
+		    rm->blockRelStarts[idx - 1] +
+		    rm->blockSizes[idx - 1] - (alignedOverlapSize / 2);
+
+		if (abs (unaSize) > rm->blockSizes[idx - 1])
+		    unaSize = -rm->blockSizes[idx - 1];
+
+		lx1 = roundingScale (rm->chromStart +
+				     rm->blockRelStarts[idx -
+							1] +
+				     rm->blockSizes[idx - 1] +
+				     unaSize - winStart, width,
+				     baseWidth) + xOff;
+		lx1 = max (lx1, 0);
+		lx2 = roundingScale (rm->chromStart +
+				     rm->blockRelStarts[idx +
+							1] -
+				     winStart, width, baseWidth) + xOff;
+
+		int midPoint = ((lx2 - lx1) / 2) + lx1;
+
+		// Block Intersection Line
+		hvGfxLine (hvg, lx1, y + alignedBlockOffset, lx1,
+			   y + alignedBlockOffset + alignedBlockHeight,
+			   black);
+
+		// Line Up
+		hvGfxLine (hvg, lx1, y + alignedBlockOffset,
+			   midPoint, y + unalignedBlockOffset, black);
+
+		// Line Down
+		hvGfxLine (hvg, lx2, y + alignedBlockOffset,
+			   midPoint, y + unalignedBlockOffset, black);
+
+		}
+	    else if (alignedOverlapSize > 0 &&
+		     ((alignedOverlapSize * 0.5) >
+		      (0.3 * rm->blockSizes[idx - 1])
+		      || (alignedOverlapSize * 0.5) >
+		      (0.3 * rm->blockSizes[idx + 1])))
+		{
+		// Need to shorten unaligned length
+		int smallOverlapLen = 0;
+		smallOverlapLen = (0.3 * rm->blockSizes[idx - 1]);
+		if (smallOverlapLen > (0.3 * rm->blockSizes[idx + 1]))
+		    smallOverlapLen = (0.3 * rm->blockSizes[idx + 1]);
+		unaSize = (smallOverlapLen * 2) + alignedGapSize;
+		relStart =
+		    rm->blockRelStarts[idx - 1] +
+		    rm->blockSizes[idx - 1] - smallOverlapLen;
+		lx1 = roundingScale (relStart + rm->chromStart -
+				     winStart, width, baseWidth) + xOff;
+		lx1 = max (lx1, 0);
+		lx2 = roundingScale (relStart + rm->chromStart +
+				     unaSize - winStart, width,
+				     baseWidth) + xOff;
+		// Line Up
+		cx1 = roundingScale (rm->chromStart +
+				     rm->blockRelStarts[idx -
+							1] +
+				     rm->blockSizes[idx - 1] -
+				     winStart, width, baseWidth) + xOff;
+		hvGfxLine (hvg, cx1, y + alignedBlockOffset, lx1,
+			   y + unalignedBlockOffset, black);
+
+		// Line Across
+		drawDashedHorizLineWHash (hvg, lx1, lx2,
+					  y +
+					  unalignedBlockOffset,
+					  5, 5, black, rm->blockSizes[idx]);
+		// Line Down
+		cx2 = roundingScale (rm->chromStart +
+				     rm->blockRelStarts[idx +
+							1] -
+				     winStart, width, baseWidth) + xOff;
+		hvGfxLine (hvg, cx2, y + alignedBlockOffset, lx2,
+			   y + unalignedBlockOffset, black);
+		}
+	    else
+		{
+		// Adequate to display full length
+		relStart =
+		    rm->blockRelStarts[idx - 1] +
+		    rm->blockSizes[idx - 1] - (alignedOverlapSize / 2);
+		lx1 = roundingScale (relStart + rm->chromStart -
+				     winStart, width, baseWidth) + xOff;
+		lx1 = max (lx1, 0);
+		lx2 = roundingScale (relStart + rm->chromStart +
+				     unaSize - winStart, width,
+				     baseWidth) + xOff;
+		// Line Up
+		int cx1 =
+		    roundingScale (rm->chromStart +
+				   rm->blockRelStarts[idx - 1] +
+				   rm->blockSizes[idx - 1] - winStart,
+				   width, baseWidth) + xOff;
+		hvGfxLine (hvg, cx1, y + alignedBlockOffset, lx1,
+			   y + unalignedBlockOffset, black);
+		drawDashedHorizLine (hvg, lx1, lx2,
+				     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);
+		}
+	    }
+	}
+    }
+
+}
+
+static void origRepeatDraw (struct track *tg, int seqStart, int seqEnd,
+		struct hvGfx *hvg, int xOff, int yOff, int width,
+		MgFont * font, Color color, enum trackVisibility vis)
+/* This is the original repeat drawing routine, modified
+ * to handle the new rmskJoined table structure.
+ */
+{
+int baseWidth = seqEnd - seqStart;
+struct repeatItem *ri;
+int y = yOff;
+int heightPer = tg->heightPer;
+int lineHeight = tg->lineHeight;
+int x1, x2, w;
+boolean isFull = (vis == tvFull);
+Color col;
+struct sqlConnection *conn = hAllocConn (database);
+struct sqlResult *sr = NULL;
+char **row;
+int rowOffset;
+
+
+if (isFull)
+    {
+    /*
+     * Do gray scale representation spread out among tracks.
+     */
+    struct hash *hash = newHash (6);
+    struct rmskJoined *ro;
+    int percId;
+    int grayLevel;
+
+    for (ri = tg->items; ri != NULL; ri = ri->next)
+	{
+	ri->yOffset = y;
+	y += lineHeight;
+	hashAdd (hash, ri->class, ri);
+	}
+    sr = hRangeQuery (conn, tg->table, chromName, winStart, winEnd, NULL,
+		      &rowOffset);
+    while ((row = sqlNextRow (sr)) != NULL)
+	{
+	ro = rmskJoinedLoad (row + rowOffset);
+	char class[256];
+	// Simplify repClass for lookup: strip trailing '?',
+	// simplify *RNA to RNA:
+	char *poundPtr = index (ro->name, '#');
+	if (poundPtr)
+	    {
+	    safecpy (class, sizeof (class), poundPtr + 1);
+	    char *slashPtr = index (class, '/');
+	    if (slashPtr)
+	        *slashPtr = '\0';
+	    }
+	else
+	    {
+	    safecpy (class, sizeof (class), "Unknown");
+	    }
+	char *p = &(class[strlen (class) - 1]);
+	if (*p == '?')
+	    *p = '\0';
+	if (endsWith (class, "RNA"))
+	    safecpy (class, sizeof (class), "RNA");
+	ri = hashFindVal (hash, class);
+	if (ri == NULL)
+	    ri = otherRepeatItem;
+	percId = 10000 - ro->score;
+	grayLevel = grayInRange (percId, 6000, 10000);
+	col = shadesOfGray[grayLevel];
+
+	int idx = 0;
+	for (idx = 0; idx < ro->blockCount; idx++)
+	    {
+	    if (ro->blockRelStarts[idx] > 0)
+		{
+		int blockStart = ro->chromStart + ro->blockRelStarts[idx];
+		int blockEnd =
+		    ro->chromStart + ro->blockRelStarts[idx] +
+		    ro->blockSizes[idx];
+
+		x1 = roundingScale (blockStart - winStart, width,
+				    baseWidth) + xOff;
+		x1 = max (x1, 0);
+		x2 = roundingScale (blockEnd - winStart, width,
+				    baseWidth) + xOff;
+		w = x2 - x1;
+		if (w <= 0)
+		    w = 1;
+		hvGfxBox (hvg, x1, ri->yOffset, w, heightPer, col);
+		}
+	    }
+	rmskJoinedFree (&ro);
+	}
+    freeHash (&hash);
+    }
+else
+    {
+    char table[64];
+    boolean hasBin;
+    struct dyString *query = newDyString (1024);
+    /*
+     * Do black and white on single track.  Fetch less than
+     * we need from database.
+     */
+    if (hFindSplitTable (database, chromName, tg->table, table, &hasBin))
+	{
+	sqlDyStringPrintf (query,
+			"select chromStart,blockCount,blockSizes,"
+			"blockRelStarts from %s where ", table);
+	if (hasBin)
+	    hAddBinToQuery (winStart, winEnd, query);
+	sqlDyStringPrintf (query, "chromStart<%u and chromEnd>%u ",
+			winEnd, winStart);
+	/*
+	 * if we're using a single rmsk table, add chrom to the where clause
+	 */
+	if (startsWith ("rmskJoined", table))
+	    sqlDyStringPrintf (query, " and chrom = '%s' ", chromName);
+	sr = sqlGetResult (conn, query->string);
+	while ((row = sqlNextRow (sr)) != NULL)
+	    {
+	    int idx = 0;
+	    int blockCount = sqlSigned (row[1]);
+	    int sizeOne;
+	    int *blockSizes;
+	    int *blockRelStarts;
+	    int chromStart = sqlUnsigned (row[0]);
+	    sqlSignedDynamicArray (row[2], &blockSizes, &sizeOne);
+	    assert (sizeOne == blockCount);
+	    sqlSignedDynamicArray (row[3], &blockRelStarts, &sizeOne);
+	    assert (sizeOne == blockCount);
+
+	    for (idx = 1; idx < blockCount - 1; idx++)
+		{
+		if (blockRelStarts[idx] >= 0)
+		    {
+		    int blockStart = chromStart + blockRelStarts[idx];
+		    int blockEnd =
+			chromStart + blockRelStarts[idx] + blockSizes[idx];
+
+		    x1 = roundingScale (blockStart - winStart,
+					width, baseWidth) + xOff;
+		    x1 = max (x1, 0);
+		    x2 = roundingScale (blockEnd - winStart, width,
+					baseWidth) + xOff;
+		    w = x2 - x1;
+		    if (w <= 0)
+		        w = 1;
+		    hvGfxBox (hvg, x1, yOff, w, heightPer, MG_BLACK);
+		    }
+		}
+	    }
+	}
+    dyStringFree (&query);
+    }
+sqlFreeResult (&sr);
+hFreeConn (&conn);
+}
+
+/* Main callback for displaying this track in the viewport
+ * of the browser.
+ */
+static void jRepeatDraw (struct track *tg, int seqStart, int seqEnd,
+	     struct hvGfx *hvg, int xOff, int yOff, int width,
+	     MgFont * font, Color color, enum trackVisibility vis)
+{
+int baseWidth = seqEnd - seqStart;
+
+// TODO: Document
+pixelsPerBase = (float) width / (float) baseWidth;
+  /*
+   * Its unclear to me why heightPer is not updated to the
+   * value set in jRepeatItemHeight() at the time this callback
+   * is invoked.  Getting the correct value myself.
+   * was:
+   * int heightPer = tg->heightPer;
+   */
+int heightPer = jRepeatItemHeight (tg, NULL);
+boolean isFull = (vis == tvFull);
+struct rmskJoined *rm;
+
+  // If we are in full view mode and the scale is sufficient,
+  // display the new visualization.
+if (isFull && baseWidth <= DETAIL_VIEW_MAX_SCALE)
+    {
+    int level = yOff;
+
+    struct subTrack *st = hashFindVal (subTracksHash, tg->table);
+    if (!st)
+	return;
+
+    int lidx = st->levelCount;
+    int currLevel = 0;
+    for (currLevel = 0; currLevel < lidx; currLevel++)
+	{
+	rm = st->levels[currLevel];
+	while (rm)
+	    {
+	    drawRMGlyph (hvg, level, heightPer, width, baseWidth, xOff, rm);
+
+	    char statusLine[128];
+	    int ss1 = roundingScale (rm->alignStart - winStart,
+				     width, baseWidth) + xOff;
+
+	    safef (statusLine, sizeof (statusLine), "%s", rm->name);
+
+	    int x1 = roundingScale (rm->alignStart - winStart, width,
+				    baseWidth) + xOff;
+	    x1 = max (x1, 0);
+	    int x2 = roundingScale (rm->alignEnd - winStart, width,
+				    baseWidth) + xOff;
+	    int w = x2 - x1;
+	    if (w <= 0)
+	        w = 1;
+
+	    mapBoxHc (hvg, rm->alignStart, rm->alignEnd, ss1, level,
+		      w, heightPer, tg->track, rm->id, statusLine);
+	    rm = rm->next;
+	    }
+	level += heightPer;
+	rmskJoinedFreeList (&(st->levels[currLevel]));
+	}
+    }
+else
+    {
+    // Draw the stereotypical view
+    origRepeatDraw (tg, seqStart, seqEnd,
+		    hvg, xOff, yOff, width, font, color, vis);
+    }
+}
+
+void jRepeatMethods (struct track *tg)
+{
+tg->loadItems = jRepeatLoad;
+tg->freeItems = jRepeatFree;
+tg->drawItems = jRepeatDraw;
+tg->colorShades = shadesOfGray;
+tg->itemName = jRepeatName;
+tg->mapItemName = jRepeatName;
+tg->totalHeight = jRepeatTotalHeight;
+tg->itemHeight = jRepeatItemHeight;
+tg->itemStart = tgItemNoStart;
+tg->itemEnd = tgItemNoEnd;
+tg->mapsSelf = TRUE;
+}