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(®Ex, filterString, REG_NOSUB); - for (bb = bbList; bb != NULL; bb = bb->next) { bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow)); if ( (isFilterRegexp && (regexec(®Ex,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;