15973bdabacf5ddf5dd9289d05e83a7cd4080dbb braney Tue Nov 9 09:12:48 2021 -0800 support for bigRmsk from Robert Hubley robert.hubley@isbscience.org diff --git src/hg/hgTracks/bigRmskTrack.c src/hg/hgTracks/bigRmskTrack.c new file mode 100644 index 0000000..ba0ce55 --- /dev/null +++ src/hg/hgTracks/bigRmskTrack.c @@ -0,0 +1,1681 @@ +/* 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: + * + */ + +/* 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 "bigRmskUi.h" + +/* Track Development Notes: + * + * Summary of concerns: + * 1. tg->loadItems: + * Should be responsible for loading data "items". + * In our case they will be bigRmskRecords. + * 2. tg->totalHeight: + * Should be responsible for layout calculations + * that determine the totalHeight of the track at the + * current scale and/or visual style ( 'pack', 'full' etc. ) + * 3. tg->preDrawItems: + * Not sure the use case for this. + * 4. tg->drawItems: + * Pretty self explanatory. + * 5. tg->drawLeftLabels: + * Draw labels to left of track (if desired). + * + * Detailed track flow: + * mainMain: + * cartHtmlShell ... doMiddle() + * + * hgTrack: + * doMiddle() ### called by mainMain ### + * tracksDisplay() + * doTrackForm() + * remoteParallelLoad() + * Call track->loadItems() + * -------------or-------------- + * Call track->loadItems() + * + * printTrackInitJavascript() + * makeActiveImage() + * doLeftLabels() + * trackPlusLabelHeight() + * Call subtrack->totalHeight() + * doCenterLabels() + * Call track->totalHeight() + * doPreDrawItems() + * doDrawitems() + * Call track->drawItems() + * Call track->totalHeight() + * Call track->drawLeftLabels() + * doTrackMap() + * Call subtrack->totalHeight() + */ + + +/* Structure to hold repeat class names/colors */ +struct classRecord + { + struct classes *next; // just in case we want to use it as a slList + char *className; + int layoutLevel; + Color color; + }; + +/* The primary "item" record used by this track */ +struct bigRmskRecord + { + struct bigRmskRecord *next; /* Next in singly linked list. */ + char *chrom; /* Reference sequence chromosome or scaffold */ + unsigned chromStart; /* Start position of feature in chromosome */ + 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 */ + }; + +/* 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 + * detailed view of repeats is turned allowed, otherwise it reverts + * back to the original UCSC glyph. I chose not to make this a function of + * data density so that switching between views could be more predictable. + */ +#define DETAIL_VIEW_MAX_SCALE 45000 + +/* 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 + +/* LABEL_PADDING: The amount of space to reserve to the left + * of the label (in px). + */ +#define LABEL_PADDING 20 + +/* MINHEIGHT: The minimum height for a track row. + */ +#define MINHEIGHT 24 + +/* MAX_PACK_ITEMS: The maximum number of items to show in pack mode. Over + * this value and the track switches to dense mode. + */ +#define MAX_PACK_ITEMS 500 + +/* MIN_VISIBLE_EXT_PIXELS: Minimum unextended region to show (in pixels). If + * under this value the vertical and horizontal arms are completely omitted. + */ +#define MIN_VISIBLE_EXT_PIXELS 4 + +/* 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. + */ +static struct hash *classHashBR = NULL; + +// 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. + * - 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 +}; + + + + + + +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); +} + + +static void getVisualExtentsInBP(struct bigRmskRecord *rm, float pixelsPerBase, unsigned *pStart, unsigned *pEnd, bool showUnalignedExtents, bool showLabels) +/* Calculate the glyph visual extents in genome coordinate + * space ( ie. bp ) + * + * It is not straightforward to determine the extent + * of this track's glyph due to the mixture of graphic + * 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); + } +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) + + (int) ((mgFontStringWidth(tl.font, lenLabel) + + LABEL_PADDING) / pixelsPerBase); + } + else + { + start -= rm->blockSizes[0]; + } + } + +end = rm->alignEnd; + +if ( showUnalignedExtents ) + { + if ((rm->blockSizes[rm->blockCount - 1] * pixelsPerBase) > MAX_UNALIGNED_PIXEL_LEN) + { + 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]; + } + } + +*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; + } + } + +// all vs all comparison of all nodes to check for overlap +for (int idxA = 0; idxA < n; idxA++) + { + struct bigRmskRecord annA = *recs[idxA]; + 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) +{ +for (int i = idx; i < nElements - 1; i++) + { + arr[i] = arr[i + 1]; + } +arr[nElements - 1] = -1; +} + +void swap(int *xp, int *yp) +{ +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. + * + * This layout routine was written by Jack Roddy + */ +int nRecs = slCount(firstRec); +struct bigRmskRecord **recs = malloc(nRecs * sizeof(struct bigRmskRecord *)); +struct bigRmskRecord *rec = firstRec; + +boolean showUnalignedExtents = cartUsualBoolean(cart, BIGRMSK_SHOW_UNALIGNED_EXTENTS, + BIGRMSK_SHOW_UNALIGNED_EXTENTS_DEFAULT); + +boolean showLabels = cartUsualBoolean(cart, BIGRMSK_SHOW_LABELS, BIGRMSK_SHOW_LABELS_DEFAULT); + +int n = 0; +while (rec != NULL) + { + getVisualExtentsInBP(rec, pixelsPerBase, &(rec->visualStart),&(rec->visualEnd), + showUnalignedExtents, showLabels); + recs[n++] = rec; + rec = rec->next; + } + +struct Graph *g = newGraph(recs, n); + +int nextColor = 0; +int nRemaining = n; +int *remaining = malloc(sizeof(int) * n); +bool *available = malloc(sizeof(bool) * n); + +for (int i = 0; i < n; i++) + { + remaining[i] = i; + available[i] = TRUE; + } + +sortRemainingByWidth(remaining, recs, n); +while (nRemaining > 0) + { + // at the start of every iteration, set everything remaining to available + for (int i = 0; i < nRemaining; i++) + { + available[remaining[i]] = TRUE; + } + // for every remaining node + for (int i = 0; i < nRemaining; i++) + { + // find the index in the rec array + int uIdx = remaining[i]; + if (available[uIdx]) + { + // if node u is available, assign a color/row + recs[uIdx]->layoutLevel = nextColor; + int *uEdges = &(g->edges[uIdx * n]); + int uDegree = g->degrees[uIdx]; + // iterate across u's edges and set them to unavailable for this iteration + for (int j = 0; j < uDegree; j++) + { + int vIdx = uEdges[j]; + available[vIdx] = FALSE; + } + // u is unavailable since we just placed it + available[uIdx] = FALSE; + // remove uIdx from remaining + removeIdx(i, remaining, nRemaining); + nRemaining--; + i--; + } + } + nextColor++; + } + +free(remaining); +free(available); +free(recs); +} + + +void bigRmskLayoutItems(struct track *tg) +{ +/* 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 == 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. + while (items) + { + struct bigRmskRecord *rr = items; + items = items->next; + + // Extract the class from the annotation name + // - Simplify repClass for lookup: strip trailing '?', + // simplify *RNA to RNA: + char *poundPtr = index(rr->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"); + cr = hashFindVal(classHashBR, class); + if (cr == NULL) + cr = hashFindVal(classHashBR, "Other"); + rr->layoutLevel = cr->layoutLevel; + } + } + else + { + // New "pack" visualization are joined features with labels. + slSort(&items, cmpStartEnd); + // If you slSort you reset the linked list and it's head. + tg->items = items; + + int inLevel = 0; + while (items) + { + struct bigRmskRecord *rr = items; + items = items->next; + + if ( rr->layoutLevel < 0 ) + { + unsigned rrChromStart, rrChromEnd; + getVisualExtentsInBP(rr, pixelsPerBase, &rrChromStart, &rrChromEnd, 0, showLabels); + rr->layoutLevel = inLevel; + + struct bigRmskRecord *rm = NULL; + unsigned rmChromStart, rmChromEnd; + rm = items; + while (rm) + { + if ( rm->layoutLevel < 0 ) + { + getVisualExtentsInBP(rm, pixelsPerBase, &rmChromStart, &rmChromEnd, 0, showLabels); + if (rmChromStart > rrChromEnd) + { + rm->layoutLevel = inLevel; + rrChromEnd = rmChromEnd; + } + } + rm = rm->next; + } // while ( rm ) + inLevel++; + } // if ( rr->layoutLevel... + } // while ( items ) + } // if(... + } +} + + +static void bigRmskLoadItems(struct track *tg) +/* + * Loading is done first given the region the display is currently centered on. + * This function is responsible for fetching the data *and* defining the number + * of "items" that will be displayed. An item represents a row of the track. + * Therefore, while this is a "loading" function it also needs to perform enough + * 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; + cr->color = rmskJoinedClassColors[i]; + 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]); + rm->score = sqlUnsigned(bedRow[4]); + safef(rm->strand, sizeof(rm->strand), "%s", bedRow[5]); + rm->alignStart = sqlUnsigned(bedRow[6]); + rm->alignEnd = sqlUnsigned(bedRow[7]); + // 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. + 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); +} + + +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 + // repeat. + char class[256]; + // Simplify repClass for lookup: strip trailing '?', + // simplify *RNA to RNA: + char *poundPtr = index(cr->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(classHashBR, class); + if (ri == NULL) + ri = hashFindVal(classHashBR, "Other"); + col = ri->color; + + 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; + w = x2 - x1; + if (w <= 0) + w = 1; + hvGfxBox(hvg, x1, yOff, w, heightPer, col); + } + } + } +} + + +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; + +struct bigRmskRecord *cr; +for (cr = tg->items; cr != NULL; cr = cr->next) + { + percId = 1000 - cr->score; + grayLevel = grayInRange(percId, 500, 1000); + col = shadesOfGray[grayLevel]; + + 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); + } + } + }//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); +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); +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); + char *slashPtr = index(class, '/'); + if (slashPtr) + *slashPtr = '\0'; + } + else + { + safecpy(family, sizeof(family), 0); + 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(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); + y = yOff + (cr->layoutLevel * tg->lineHeight); + hvGfxTextCentered(hvg, x1 - stringWidth, + heightPer - fontHeight + y, + stringWidth, fontHeight, MG_BLACK, font, + family); + } + + 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); + 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); + } + prevBlockEnd = blockEnd; + } + } + }//for (cr = ri->itemData... +} + + +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; + } +} + + +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 - (fontHeight/2), 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 bigRmskRecord *rm, + enum trackVisibility vis, boolean showUnalignedExtents, boolean showLabels) +/* + * Draw a detailed RepeatMasker annotation glyph given + * a single bigRmskRecord structure. + * + * A couple of caveats about the use of the bigRmskRecord + * 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 classRecord *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; +float pixelsPerBase = (float)width / (float)baseWidth; + +Color black = hvGfxFindColorIx(hvg, 0, 0, 0); +Color fillColor = shadesOfGray[5]; +Color outlineColor = rmskJoinedClassColors[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(classHashBR, class); +if (ri == NULL) + ri = hashFindVal(classHashBR, "Other"); +// Pick the fill color based on the divergence +int percId = 1000 - rm->score; +int grayLevel = grayInRange(percId, 500, 1000); +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 consensus 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) + { + // Start with the assumption that we don't show + // unaligned extentions. + lx1 = roundingScale(rm->chromStart + + rm->blockRelStarts[idx + 1] - + winStart, width, baseWidth) + xOff; + + if ( showUnalignedExtents ) + { + /* + * 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]); + // 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; + hvGfxTextCentered(hvg, lx1 - stringWidth, + heightPer - fontHeight + y, + stringWidth, fontHeight, MG_BLACK, font, + rm->name); + } + } + 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] + + 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]); + // 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 + { + lx2 = roundingScale(rm->chromStart + + 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: + * ............. + * \ / + * >>>>> >>>>>> + * + * 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); + } + } + } // 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) +{ +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); + +// If we are in full view mode and the scale is sufficient, +// display the detailed visualization. +if (vis == tvFull && baseWidth <= DETAIL_VIEW_MAX_SCALE) + { + int level; + for (rm = tg->items; rm != NULL; rm = rm->next) + { + level = yOff + (rm->layoutLevel * heightPer); + drawRMGlyph(hvg, level, heightPer, width, baseWidth, + xOff, rm, vis, showUnalignedExtents, showLabels ); + 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; + + char idStr[80]; + sprintf(idStr,"%d",rm->id); + mapBoxHc(hvg, rm->alignStart, rm->alignEnd, ss1, level, + w, heightPer, tg->track, idStr, statusLine); + } + } +else + { + // Experiment with further context sensitivity + int count = slCount(tg->items); + if ( count > MAX_PACK_ITEMS || vis == tvDense ) + { + // Draw the single line view + drawDenseGlyphs(tg, seqStart, seqEnd, + hvg, xOff, yOff, width, + font, color, vis); + } + else + { + if ( origPackViz ) + // Draw the stereotypical class view + drawOrigPackGlyphs(tg, seqStart, seqEnd, + hvg, xOff, yOff, width, + font, color, vis); + else + // Draw new pack view + drawPackGlyphs(tg, seqStart, seqEnd, + hvg, xOff, yOff, width, + font, color, vis); + } + } +} + + +void bigRmskLeftLabels(struct track *tg, int seqStart, int seqEnd, + struct hvGfx *hvg, int xOff, int yOff, int width, int height, + boolean withCenterLabels, MgFont *font, Color color, + enum trackVisibility vis) +/* draw the labels on the left margin */ +{ +boolean origPackViz = cartUsualBoolean(cart, BIGRMSK_ORIG_PACKVIZ, BIGRMSK_ORIG_PACKVIZ_DEFAULT); +int y; +Color labelColor = tg->labelColor; +labelColor = maybeDarkerLabels(tg, hvg, labelColor); +int fontHeight = tl.fontHeight+1; +int heightPer = bigRmskItemHeight(tg, NULL); +y = yOff + fontHeight; + +if (vis == tvDense) + { + hvGfxTextRight(hvg, xOff, y, width - 1, + 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"); + } +} + + +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; +track->mapItemName = bigRmskItemName; +track->itemHeight = bigRmskItemHeight; +track->itemStart = tgItemNoStart; +track->itemEnd = tgItemNoEnd; +track->mapsSelf = TRUE; +track->canPack = TRUE; +}