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 ∅ + } +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; +}