432c144417eb7a077b01ad04999942ec6b8bb3c1 hiram Wed Jan 7 14:47:39 2015 -0800 bring up to date with Robert's latest code to help fix bugs refs #9741 diff --git src/hg/hgTracks/rmskJoinedTrack.c src/hg/hgTracks/rmskJoinedTrack.c index 8642be6..de7af0a 100644 --- src/hg/hgTracks/rmskJoinedTrack.c +++ src/hg/hgTracks/rmskJoinedTrack.c @@ -1,23 +1,26 @@ /* 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 + * Written by Robert Hubley 10/2012-11/2014 * * Modifications: * + * 11/2014 + * Request for traditional pack/squish modes. + * * 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" @@ -52,118 +55,101 @@ struct hash *classHash = NULL; static struct repeatItem *otherRepeatItem = NULL; /* Hash of subtracks * The joinedRmskTrack is designed to be used as a composite track. * When rmskJoinedLoadItems 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]; +struct rmskJoined **levels; }; // 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. +/* 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 */ -// trying colors from snakePalette 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 }; -/* -static Color rmskJoinedClassColors[] = { -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 - - +// Start position is anchored by the alignment start +// coordinates. Then we subtract from that space for +// the label. +ex.start = rm->alignStart - (int) ((mgFontStringWidth(tl.font, rm->name) + LABEL_PADDING) / pixelsPerBase); - +// Now subtract out space for the unaligned sequence +// bar starting off the graphics portion of the glyph. 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]); 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) { @@ -172,233 +158,276 @@ 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; } // A better way to organize the display static int cmpRepeatDiv(const void *va, const void *vb) -/* Sort repeats by divergence. +/* Sort repeats by divergence. TODO: Another way to do this + would be to sort by containment. How many annotations + are contained within the repeat. This would require + a "contained_count" be precalculated for each annotation. */ { struct rmskJoined *a = *((struct rmskJoined **) va); struct rmskJoined *b = *((struct rmskJoined **) vb); return (b->score - a->score); } -//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); -} -*/ +struct repeatItem *classRIList = NULL; +struct repeatItem *fullRIList = NULL; static struct repeatItem * makeJRepeatItems() /* Initialize the track */ { -classHash = newHash(6); -struct repeatItem *ri, *riList = NULL; int i; +struct repeatItem *ri = NULL; + +// Build a permanent hash for mapping +// class names to colors. Used by glyph +// drawing routine. Also build the +// classRIList primarily for use in tvDense +// mode. +if (!classHash) + { + classHash = newHash(6); 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 = rmskJoinedClassColors[i]; - slAddHead(&riList, ri); + slAddHead(&classRIList, ri); // Hash now prebuilt to hold color attributes hashAdd(classHash, ri->class, ri); if (sameString(rptClassNames[i], "Other")) otherRepeatItem = ri; } -slReverse(&riList); -return riList; + slReverse(&classRIList); + } +return classRIList; } + + static void rmskJoinedLoadItems(struct track *tg) /* We do the query(ies) here so we can report how deep the track(s) * will be when rmskJoinedTotalHeight() 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(); +//tg->items = makeJRepeatItems(); + makeJRepeatItems(); int baseWidth = winEnd - winStart; pixelsPerBase = (float) insideWidth / (float) baseWidth; -if (tg->visibility == tvFull && baseWidth <= DETAIL_VIEW_MAX_SCALE) +if ((tg->visibility == tvFull || tg->visibility == tvSquish || + tg->visibility == tvPack) && baseWidth <= DETAIL_VIEW_MAX_SCALE) { + struct repeatItem *ri = NULL; struct subTrack *st = NULL; AllocVar(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); slSort(&detailList, cmpRepeatDiv); sqlFreeResult(&sr); hFreeConn(&conn); + AllocArray( st->levels, slCount(&detailList)); + 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; + + if ( tg->visibility == tvFull ) + { + AllocVar(ri); + ri->className = cr->name; + slAddHead(&fullRIList, ri); + } + + // tvFull is one-per-line -- no need to group items in levels + if ( tg->visibility == tvSquish || tg->visibility == tvPack ) + { 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 ) + } // else if ( tg->visibility == tvSquish ... } // while ( detailList ) // Create Hash Entry hashReplace(subTracksHash, tg->table, st); - } // if ( tg->visibility == tvFull + slReverse(&fullRIList); + tg->items = fullRIList; + } // if ((tg->visibility == tvFull || ... + else + tg->items = classRIList; } static void rmskJoinedFreeItems(struct track *tg) /* Free up rmskJoinedMasker items. */ { slFreeList(&tg->items); } static char * rmskJoinedName(struct track *tg, void *item) /* Return name of rmskJoined 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 ((tg->visibility == tvSquish || + tg->visibility == tvPack) && winBaseCount <= DETAIL_VIEW_MAX_SCALE) { if (strcmp(ri->className, "SINE") == 0) return("Repeats"); else return ∅ } return ri->className; } int rmskJoinedItemHeight(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) +if (tg->limitedVis == tvSquish && winBaseCount <= DETAIL_VIEW_MAX_SCALE ) + { + if ( tg->heightPer < (MINHEIGHT/2) ) + return (MINHEIGHT/2); + else + return tg->heightPer; + } +else if ((tg->limitedVis == tvFull || tg->limitedVis == tvPack) && winBaseCount <= DETAIL_VIEW_MAX_SCALE) { if ( tg->heightPer < MINHEIGHT ) return MINHEIGHT; else return tg->heightPer; } else { return tgFixedItemHeight(tg, item); } } int rmskJoinedTotalHeight(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) +if ((tg->limitedVis == tvFull || tg->limitedVis == tvSquish || + tg->limitedVis == tvPack) && winBaseCount <= DETAIL_VIEW_MAX_SCALE) { // Lookup the depth of this subTrack and report it struct subTrack *st = hashFindVal(subTracksHash, tg->table); if (st) + { + tg->height = ((st->levelCount + 1) * rmskJoinedItemHeight(tg, NULL) ); return ((st->levelCount + 1) * rmskJoinedItemHeight(tg, NULL) ); + } else + { + tg->height = rmskJoinedItemHeight(tg, NULL); return (rmskJoinedItemHeight(tg, NULL)); // Just display one line } + } else -return tgFixedTotalHeightNoOverflow(tg, vis); + { + if ( vis == tvDense ) + { + tg->height = tgFixedTotalHeightNoOverflow(tg, tvDense ); + return tgFixedTotalHeightNoOverflow(tg, tvDense ); + } + else + { + tg->height = tgFixedTotalHeightNoOverflow(tg, tvFull ); + return tgFixedTotalHeightNoOverflow(tg, tvFull ); + } + } } 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); @@ -525,31 +554,32 @@ } 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) + int width, int baseWidth, int xOff, struct rmskJoined *rm, + enum trackVisibility vis) /* * 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 @@ -759,38 +789,43 @@ 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 + if ( vis != tvSquish && vis != tvFull ) + { MgFont *font = tl.font; int fontHeight = tl.fontHeight; int stringWidth = mgFontStringWidth(font, rm->name) + LABEL_PADDING; hvGfxTextCentered(hvg, lx1 - stringWidth, heightPer - fontHeight + y, 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, @@ -955,55 +990,53 @@ 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) +if (vis == tvFull || vis == tvSquish || vis == tvPack) { /* * Do grayscale 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); } @@ -1135,82 +1168,84 @@ 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 rmskJoinedItemHeight() at the time this callback * is invoked. Getting the correct value myself. * was: * int heightPer = tg->heightPer; */ int heightPer = rmskJoinedItemHeight(tg, NULL); -boolean isFull = (vis == tvFull); +//boolean isFull = (vis == tvFull); struct rmskJoined *rm; +// DEBUG REMOVE // If we are in full view mode and the scale is sufficient, // display the new visualization. -if (isFull && baseWidth <= DETAIL_VIEW_MAX_SCALE) +if ((vis == tvFull || vis == tvSquish || vis == tvPack) && 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); + drawRMGlyph(hvg, level, heightPer, width, baseWidth, xOff, rm, vis ); 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 rmskJoinedMethods(struct track *tg) { tg->loadItems = rmskJoinedLoadItems; tg->freeItems = rmskJoinedFreeItems; tg->drawItems = rmskJoinedDrawItems; tg->colorShades = shadesOfGray; tg->itemName = rmskJoinedName; tg->mapItemName = rmskJoinedName; tg->totalHeight = rmskJoinedTotalHeight; tg->itemHeight = rmskJoinedItemHeight; tg->itemStart = tgItemNoStart; tg->itemEnd = tgItemNoEnd; tg->mapsSelf = TRUE; +tg->canPack = TRUE; }