7eb7071e960db1722c1597e2d2f7b113868efbb2 braney Mon Sep 26 14:37:45 2022 -0700 in intersection graphs in pack mode, don't descedn into the linkFeatures draw code if there are no items because thar be dragons diff --git src/hg/hgTracks/interactTrack.c src/hg/hgTracks/interactTrack.c index d701be3..a0b9dd8 100644 --- src/hg/hgTracks/interactTrack.c +++ src/hg/hgTracks/interactTrack.c @@ -1,908 +1,909 @@ /* interactTrack -- draw interaction between two genomic regions */ /* Copyright (C) 2018 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "obscure.h" #include "hgTracks.h" #include "bedCart.h" #include "bigWarn.h" #include "interact.h" #include "interactUi.h" static int interactTotalHeight(struct track *tg, enum trackVisibility vis) /* calculate height of all the interactions being displayed */ { if ( tg->visibility == tvDense) return tl.fontHeight; int min, max, deflt, current; cartTdbFetchMinMaxPixels(cart, tg->tdb, INTERACT_MINHEIGHT, INTERACT_MAXHEIGHT, atoi(INTERACT_DEFHEIGHT), &min, &max, &deflt, ¤t); return tg->height = current; } static Color interactItemColor(struct track *tg, void *item, struct hvGfx *hvg, int scoreMin, int scoreMax) /* Return color to draw an interaction */ { struct interact *inter = item; if (tg->colorShades) { struct bed *bed = (struct bed *)inter; adjustBedScoreGrayLevel(tg->tdb, bed, scoreMin, scoreMax); return tg->colorShades[grayInRange(inter->score, 0, 1000)]; } /* There must be a better way..., e.g.: unsigned red = COLOR_32_RED(inter->color); unsigned green = COLOR_32_GREEN(inter->color); unsigned blue = COLOR_32_BLUE(inter->color); */ unsigned red = (inter->color & 0xff0000) >> 16; unsigned green = (inter->color & 0xff00) >> 8; unsigned blue = inter->color & 0xff; return hvGfxFindColorIx(hvg, red, green, blue); } boolean interactSourceInWindow(struct interact *inter) /* True if midpoint of source is on screen */ { unsigned s = interactRegionCenter(inter->sourceStart, inter->sourceEnd); return (s >= winStart) && (s < winEnd); } boolean interactTargetInWindow(struct interact *inter) /* True if midpoint of target is on screen */ { unsigned t = interactRegionCenter(inter->targetStart, inter->targetEnd); return (t >= winStart) && (t < winEnd); } static void loadAndFilterItems(struct track *tg) /* Load all interact items in region */ { loadSimpleBedWithLoader(tg, (bedItemLoader)interactLoadAndValidate); // if the summary is filled in then the number of items in the region is greater than maxItems. if (tg->summary != NULL) { // too many items to display tg->drawItems = bigDrawWarning; tg->networkErrMsg = "Too many items in display (zoom in)"; tg->totalHeight = bigWarnTotalHeight; return; } // filters struct interact *inter, *next, *filteredItems = NULL; int count = slCount(tg->items); // exclude if missing endpoint(s) in window char *endsVisible = cartUsualStringClosestToHome(cart, tg->tdb, FALSE, INTERACT_ENDS_VISIBLE, INTERACT_ENDS_VISIBLE_DEFAULT); char *scoreFilter = cartOrTdbString(cart, tg->tdb, "scoreFilter", NULL); int minScore = 0; if (scoreFilter) minScore = atoi(scoreFilter); for (inter = tg->items; inter; inter = next) { next = inter->next; if (inter->score < minScore) continue; if (differentString(endsVisible, INTERACT_ENDS_VISIBLE_ANY)) { boolean sOnScreen = interactSourceInWindow(inter); boolean tOnScreen = interactTargetInWindow(inter); if (sameString(endsVisible, INTERACT_ENDS_VISIBLE_TWO)) { if (!(sOnScreen && tOnScreen)) continue; } if (sameString(endsVisible, INTERACT_ENDS_VISIBLE_ONE)) { if (!(sOnScreen || tOnScreen)) continue; } } slAddHead(&filteredItems, inter); } slReverse(&filteredItems); // consider sorting by score/value so highest scored items draw last (on top) if (slCount(filteredItems) != count) labelTrackAsFiltered(tg); tg->items = filteredItems; } void interactNoLeftLabels(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) /* Override default */ { } /* Interaction displayed as a linked feature */ struct interactLfEndNames /* Stash source and target names when converted to linked feature */ { char *source; char *target; }; static void setInteractLfEndNames(struct linkedFeatures *lf, char *sourceName, char *targetName) /* Stash endpoint names in linked feature */ { struct interactLfEndNames *ends = NULL; AllocVar(ends); ends->source = sourceName; ends->target = targetName; lf->original = ends; // coopt this void * field (would have preferred to use extra) } static struct interactLfEndNames *getInteractLfEndNames(struct linkedFeatures *lf) /* Retrieve endpoint names from linked feature */ { return (struct interactLfEndNames *)lf->original; } void interactFreeItems(struct track *tg) /* Free up interact items track */ { interactFreeList((struct interact **)(&tg->items)); } void interactLfSortAndBound(struct linkedFeatures *lf) /* Sort simpleFeatures in the linkedFeature and set start and end based on simpleFetaures */ // TODO: dedupe the simpleFeatures ? { struct simpleFeature *sfLast, *sf, *sfs = lf->components; slSort(&sfs, simpleFeatureCmp); lf->components = sfs; sfLast = (struct simpleFeature *)slLastEl(sfs); int start = sfs->start; int end = sfLast->end; for (sf = sfs; sf != NULL; sf = sf->next) { if (sf->start < start) start = sf->start; if (sf->end > end) end = sf->end; } lf->start = start; lf->end = end; } static struct linkedFeatures *interactToLf(struct interact *inter, boolean doColor) /* Convert interact BED to linkedFeatures */ { struct bed *bed = interactToBed(inter); struct linkedFeatures *lf = lfFromBed(bed); // save source and target names to extra field of linked feature, so we can display them // in pack mode // TODO: code to free setInteractLfEndNames(lf, cloneString(inter->sourceName), cloneString(inter->targetName)); // not sure why this is needed -- lfFromBed seems to reorder blocks, sometimes ? interactLfSortAndBound(lf); if (doColor) { lf->useItemRgb = TRUE; lf->filterColor = bed->itemRgb; } bedFree(&bed); return lf; } static boolean isLinkedFeaturesMode(struct track *tg) /* Determine if linked feature display will be used */ { return (tg->visibility != tvFull); } int interactSize(struct interact *inter) /* Compute length of interaction (distance between middle of each region) in bp */ { int sourceCenter = 0, targetCenter = 0; interactRegionCenters(inter, &sourceCenter, &targetCenter); return abs(targetCenter - sourceCenter); } int getX(int pos, int seqStart, double scale, int xOff) /* Get x coordinate of a genomic location. Return -1 if off-screen */ { if (pos < seqStart) return -1; return ((double)(pos - seqStart + .5) * scale) + xOff; } static void setYOff(struct track *tg, int yOff) /* Stash y offset for this track */ { tg->customInt = yOff; } static int getYOff(struct track *tg) /* Get y offset for this track (stashed by DrawItems) */ { return tg->customInt; } static int flipY(struct track *tg, int y) /* Invert y coordinate if flipped display is requested */ { int yOff = getYOff(tg); int flipped = yOff + tg->height + yOff - y; return flipped; } struct interactTrackInfo { boolean isDirectional; // source and target are distinct item types char *offset; // which end to draw offset (source or target) char *clusterMode; // cluster by source or target (or no cluster if NULL) boolean drawUp; // draw interactions with peak up (hill) boolean doOtherLabels; // true to suppress labels on other chrom items (prevent overlap) int maxSize; // longest interaction (midpoint to midpoint) in bp int fontHeight; int sameCount; // number of same chromosome interactions in window int sameHeight; // vertical space for same chromosome interactions int otherCount; // number of other chromosome interactions in window int otherHeight; // vertical space for other chromosome interactions } interactTrackInfo; static void interactGetLayoutInfo(struct track *tg, int seqStart, struct hvGfx *hvg, int xOff, MgFont *font, double scale) /* Get layout info from interact items in window */ { struct interactTrackInfo *tInfo = tg->customPt; tInfo->doOtherLabels = TRUE; char *otherChrom = NULL; int prevLabelEnd = 0, prevLabelStart = 0; char *prevLabel = 0; struct interact *inter; for (inter = (struct interact *)tg->items; inter; inter = inter->next) { otherChrom = interactOtherChrom(inter); if (otherChrom == NULL) { tInfo->sameCount++; // determine maximum interaction size, for later use laying out 'peaks' int size = interactSize(inter); if (size > tInfo->maxSize) tInfo->maxSize = size; } else { tInfo->otherCount++; // suppress interchromosomal labels if they overlap if (!tInfo->doOtherLabels) continue; int labelWidth = vgGetFontStringWidth(hvg->vg, font, otherChrom); int x = getX(inter->chromStart, seqStart, scale, xOff); int labelStart = round((double)(x - labelWidth)/2); int labelEnd = labelStart + labelWidth - 1; if (labelStart <= prevLabelEnd && !(labelStart == prevLabelStart && labelEnd == prevLabelEnd && sameString(otherChrom, prevLabel))) tInfo->doOtherLabels = FALSE; prevLabelStart = labelStart; prevLabelEnd = labelEnd; prevLabel = otherChrom; } } tInfo->fontHeight = vgGetFontPixelHeight(hvg->vg, font); tInfo->otherHeight = (tInfo->otherCount) ? 3 * tInfo->fontHeight : 0; tInfo->sameHeight = (tInfo->sameCount) ? tg->height - tInfo->otherHeight : 0; } static int interactRightPixels(struct track *tg, void *item) /* Return number of pixels we need to the right, in linked features mode. */ { struct interactTrackInfo *tInfo = tg->customPt; if (tInfo->clusterMode || !(tg->visibility == tvPack || tg->visibility == tvFull)) return 0; struct linkedFeatures *lf = (struct linkedFeatures *)item; struct interactLfEndNames *ends = getInteractLfEndNames(lf); char *rightLabel = ends->target; if (lf->orientation != 0) rightLabel = (lf->orientation < 0 ? ends->source : ends->target); return mgFontStringWidth(tl.font, rightLabel); } static char *interactLfLeftEndName(struct track *tg, void *item) /* Return name of left end in single interaction linked feature, for left label */ { struct linkedFeatures *lf = (struct linkedFeatures *)item; struct interactLfEndNames *ends = getInteractLfEndNames(lf); return (lf->orientation < 0 ? ends->target : ends->source); } static void interactLfMapItem(struct track *tg, struct hvGfx *hvg, void *item, char *itemName, char *mapItemName, int start, int end, int x, int y, int width, int height) /* Draw mapbox on interact item when displayed as linked feature */ { struct linkedFeatures *lf = (struct linkedFeatures *)item; // restore full item name (using end name for label) itemName = mapItemName = lf->name; linkedFeaturesMapItem(tg, hvg, item, itemName, mapItemName, start, end, x, y, width, height); } void interactLoadItems(struct track *tg) /* Load interact items in interact format */ { loadAndFilterItems(tg); struct interactTrackInfo *tInfo = NULL; AllocVar(tInfo); tg->customPt = tInfo; tInfo->isDirectional = interactUiDirectional(tg->tdb); tInfo->offset = interactUiOffset(tg->tdb); tInfo->drawUp = trackDbSettingClosestToHomeOn(tg->tdb, INTERACT_UP); tInfo->clusterMode = interactUiClusterMode(cart, tg->track, tg->tdb); // conditional for now -- pending QA/JK weigh in #define MR_FORCE_PACK 1 #ifdef MR_FORCE_PACK if (!tInfo->clusterMode && !isLinkedFeaturesMode(tg) && sameString(virtModeType, "default")) #else if (!tInfo->clusterMode && !isLinkedFeaturesMode(tg)) #endif { // draw curve display tg->mapsSelf = TRUE; tg->totalHeight = interactTotalHeight; tg->drawLeftLabels = interactNoLeftLabels; tg->freeItems = interactFreeItems; return; } // convert to BEDs for linked feature display struct interact *inters = tg->items, *inter; struct linkedFeatures *lfs = NULL, *lf; struct hash *intersCluster = hashNew(0); boolean doColor = !tg->colorShades; if (!tInfo->clusterMode) { tg->itemName = interactLfLeftEndName; tg->mapItem = interactLfMapItem; } for (inter = inters; inter; inter = inter->next) { if (tInfo->clusterMode) { boolean byTarget = sameString(tInfo->clusterMode, INTERACT_CLUSTER_TARGET); // hash by source or target name char *name = (byTarget ? inter->targetName : inter->sourceName); lf = (struct linkedFeatures *) hashFindVal(intersCluster, name); if (lf) { // add a simple feature for the other end (source or target) to the linked feature struct simpleFeature *sf = NULL; AllocVar(sf); // tweak interact struct for intrachromsomal item to ease next steps if (differentString(inter->targetChrom, inter->sourceChrom)) { inter->sourceStart = inter->targetStart = inter->chromStart; inter->sourceEnd = inter->targetEnd = inter->chromEnd; } sf->start = (byTarget ? inter->sourceStart : inter->targetStart); sf->end = (byTarget ? inter->sourceEnd : inter->targetEnd); struct simpleFeature *sfs = lf->components; slAddHead(&sfs, sf); lf->components = sfs; if (lf->filterColor != inter->color) lf->filterColor = MG_GRAY; } else { // create a linked feature for this cluster lf = interactToLf(inter, doColor); lf->orientation = 0; lf->name = (byTarget ? inter->targetName : inter->sourceName); lf->tallStart = (byTarget ? inter->targetStart : inter->sourceStart); lf->tallEnd = (byTarget ? inter->targetEnd : inter->sourceEnd); hashAdd(intersCluster, lf->name, lf); } } else { // packed or squish mode view of single interaction (not cluster) lf = interactToLf(inter, doColor); if (tInfo->isDirectional) { lf->tallStart = inter->targetStart; lf->tallEnd = inter->targetEnd; } else { lf->orientation = 0; } slAddHead(&lfs, lf); } } if (tInfo->clusterMode) { // sort simplefeatures and adjust bounds of clustered features struct hashEl *el, *els = hashElListHash(intersCluster); for (el = els; el; el = el->next) { lf = (struct linkedFeatures *)el->val; interactLfSortAndBound(lf); slAddHead(&lfs, lf); } slSort(&lfs, linkedFeaturesCmp); } else { slReverse(&lfs); } tg->items = lfs; // TODO: consider freeing interact items } char *interactMouseover(struct interact *inter, char *otherChrom) /* Make mouseover text for an interaction */ { struct dyString *ds = dyStringNew(0); if (isEmptyTextField(inter->name)) { if (!isEmptyTextField(inter->exp)) dyStringPrintf(ds, "%s ", inter->exp); if (otherChrom) dyStringPrintf(ds, "%s", otherChrom); else { char buf[4096]; sprintLongWithCommas(buf, inter->chromEnd - inter->chromStart); dyStringPrintf(ds, "%s bp", buf); } } else dyStringPrintf(ds, "%s", inter->name); if (inter->score) dyStringPrintf(ds, " %d", inter->score); if (inter->value != 0.0) dyStringPrintf(ds, " %0.2f", inter->value); return dyStringCannibalize(&ds); } int regionFootWidth(int start, int end, double scale) /* Return half foot width in pixels */ { unsigned size = end - start; int width = scale * (double)size / 2; if (width == 0) width = 1; return width; } /* Draw helper routines */ #define DRAW_LINE 0 #define DRAW_CURVE 1 #define DRAW_ELLIPSE 2 static void drawFoot(struct track *tg, struct hvGfx *hvg, char *seq, int seqStart, int seqEnd, int x, int y, int width, Color color, boolean drawUp, struct hash *footHash) /* Draw interaction end, 2 pixels high. Force to black if it exactly overlaps another */ { char buf[256]; safef(buf, sizeof(buf), "%s:%d-%d", seq, seqStart, seqEnd); char *pos = cloneString(buf); Color footColor = color; if (hashLookup(footHash, pos)) footColor = MG_BLACK; else hashStore(footHash, pos); if (drawUp) y = flipY(tg, y) - 2; hvGfxBox(hvg, x, y, width, 2, footColor); } static void drawLine(struct track *tg, struct hvGfx *hvg, int x1, int y1, int x2, int y2, Color color, boolean isDashed, boolean drawUp) /* Draw vertical or horizontal */ { if (drawUp) { y1 = flipY(tg, y1); y2 = flipY(tg, y2); } if (isDashed) hvGfxDottedLine(hvg, x1, y1, x2, y2, color, TRUE); else hvGfxLine(hvg, x1, y1, x2, y2, color); } static void drawFootMapbox(struct track *tg, struct hvGfx *hvg, int start, int end, char *item, char *status, int x, int y, int width, Color peakColor, Color highlightColor, boolean drawUp) /* Draw grab box and add map box */ { // Add var to identify endpoint ('foot'), or NULL if no name for endpoint */ char *clickArg = NULL; if (!isEmptyTextField(item)) { char buf[256]; safef(buf, sizeof(buf),"foot=%s", cgiEncode(item)); clickArg = cloneString(buf); } char *itemBuf = isEmptyTextField(item) ? status : item; if (drawUp) y = flipY(tg, y) - 3; hvGfxBox(hvg, x-1, y, 3, 2, peakColor); hvGfxBox(hvg, x, y, 1, 1, highlightColor); mapBoxHgcOrHgGene(hvg, start, end, x - width, y, width * 2, 4, tg->track, item, itemBuf, NULL, TRUE, clickArg); } void drawPeakMapbox(struct track *tg, struct hvGfx *hvg, int seqStart, int seqEnd, char *item, char *status, int x, int y, Color peakColor, Color highlightColor, boolean drawUp) /* Draw grab box and add map box */ { if (drawUp) y = flipY(tg, y); hvGfxBox(hvg, x-1, y-1, 3, 3, peakColor); hvGfxBox(hvg, x, y, 1, 1, highlightColor); mapBoxHgcOrHgGene(hvg, seqStart, seqEnd, x-1, y-1, 3, 3, tg->track, item, status, NULL, TRUE, NULL); } static void drawInteractItems(struct track *tg, int seqStart, int seqEnd, struct hvGfx *hvg, int xOff, int yOff, int width, MgFont *font, Color color, enum trackVisibility vis) /* Draw a list of interact items with connectors (e.g. curves) */ { // Determine drawing mode int draw = DRAW_LINE; boolean doDashes = FALSE; if (vis != tvDense) { char *drawMode = cartUsualStringClosestToHome(cart, tg->tdb, FALSE, INTERACT_DRAW, INTERACT_DRAW_DEFAULT); if (sameString(drawMode, INTERACT_DRAW_CURVE)) draw = DRAW_CURVE; else if (sameString(drawMode, INTERACT_DRAW_ELLIPSE)) draw = DRAW_ELLIPSE; doDashes = cartUsualBooleanClosestToHome(cart, tg->tdb, FALSE, INTERACT_DIRECTION_DASHES, INTERACT_DIRECTION_DASHES_DEFAULT); } double scale = scaleForWindow(width, seqStart, seqEnd); struct interact *inter = NULL; char buffer[1024]; char itemBuf[2048]; // Gather info for layout interactGetLayoutInfo(tg, seqStart, hvg, xOff, font, scale); struct interactTrackInfo *tInfo = (struct interactTrackInfo *)tg->customPt; setYOff(tg, yOff); // TODO: better to stash this in tInfo, and save that in track struct */ int highlightColor = MG_WHITE; boolean drawUp = trackDbSettingClosestToHomeOn(tg->tdb, INTERACT_UP) && vis == tvFull; // Get spectrum range int scoreMin = atoi(trackDbSettingClosestToHomeOrDefault(tg->tdb, "scoreMin", "0")); int scoreMax = atoi(trackDbSettingClosestToHomeOrDefault(tg->tdb, "scoreMax", "1000")); // Draw items struct hash *footHash = hashNew(0); // track feet so we can override color to black struct hash *footHashOther = hashNew(0); // has for items on other chrom for (inter = (struct interact *)tg->items; inter; inter = inter->next) { char *otherChrom = interactOtherChrom(inter); safef(itemBuf, sizeof itemBuf, "%s", inter->name); char *statusBuf = interactMouseover(inter, otherChrom); // Pick colors #define MG_LIGHT_MAGENTA 0xffffbbff #define MG_LIGHT_GRAY 0xff909090 color = interactItemColor(tg, inter, hvg, scoreMin, scoreMax); if (vis == tvDense && otherChrom && color == MG_BLACK) // use highlight color for other chrom items in dense mode color = MG_LIGHT_MAGENTA; int peakColor = (color == MG_BLACK || tg->colorShades) ? MG_LIGHT_MAGENTA : MG_LIGHT_GRAY; if (otherChrom) { // different chromosomes // draw below same chrom items, if any int height = 0; int yOffOther = yOff; if (vis == tvDense) { height = tg->height; } else { height = tInfo->otherHeight/2; yOffOther = yOff + tInfo->sameHeight; } unsigned r = interactRegionCenter(inter->chromStart, inter->chromEnd); int x = getX(r, seqStart, scale, xOff); int footWidth = regionFootWidth(inter->chromStart, inter->chromEnd, scale); unsigned yPos = yOffOther + height; // draw the foot (2 pixels high) drawFoot(tg, hvg, inter->chrom, inter->chromStart, inter->chromEnd, x - footWidth, yOffOther, footWidth + footWidth + 1, color, drawUp, footHashOther); // draw the vertical boolean isReversed = tInfo->isDirectional && differentString(inter->chrom, inter->sourceChrom); drawLine(tg, hvg, x, yOffOther, x, yPos, color, isReversed && doDashes, drawUp); if (vis == tvDense) continue; // add map box to foot char *nameBuf = (inter->chromStart == inter->sourceStart ? inter->sourceName : inter->targetName); drawFootMapbox(tg, hvg, inter->chromStart, inter->chromEnd, nameBuf, statusBuf, x - footWidth, yOffOther, footWidth, peakColor, highlightColor, drawUp); // add map box to vertical mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, x - 2, yOffOther, 4, height, tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); if (tInfo->doOtherLabels) { // draw label safef(buffer, sizeof buffer, "%s", sameString(inter->chrom, inter->sourceChrom) ? inter->targetChrom : inter->sourceChrom); yPos += 3; if (drawUp) yPos = flipY(tg, yPos + 6); hvGfxTextCentered(hvg, x, yPos, 4, 4, MG_BLUE, font, buffer); int labelWidth = vgGetFontStringWidth(hvg->vg, font, buffer); // add map box to label mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, x - labelWidth/2, yPos, labelWidth, tInfo->fontHeight, tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); } continue; } // Draw same chromosome interaction // source region unsigned s = interactRegionCenter(inter->sourceStart, inter->sourceEnd); int sX = getX(s, seqStart, scale, xOff); int sWidth = regionFootWidth(inter->sourceStart, inter->sourceEnd, scale); boolean sOnScreen = (s >= seqStart) && (s< seqEnd); // target region unsigned t = interactRegionCenter(inter->targetStart, inter->targetEnd); int tX = getX(t, seqStart, scale, xOff); int tWidth = regionFootWidth(inter->targetStart,inter->targetEnd, scale); boolean tOnScreen = (t >= seqStart) && (t< seqEnd); boolean isReversed = (tInfo->isDirectional && t < s); int interSize = abs(t - s); int peakHeight = (tInfo->sameHeight - 15) * ((double)interSize / tInfo->maxSize) + 10; int peak = yOff + peakHeight; if (vis == tvDense) peak = yOff + tg->height; // NOTE: until time permits, force to rectangle when in reversed strand mode. int yTarget = yOff; int ySource = yOff; if (tInfo->offset && draw != DRAW_ELLIPSE) // ellipse code doesn't support assymetrical ends { int yOffset = tg->height/10 + 1; if (sameString(tInfo->offset, INTERACT_OFFSET_TARGET)) yTarget = yOff + yOffset; else if (sameString(tInfo->offset, INTERACT_OFFSET_SOURCE)) ySource = yOff + yOffset; } unsigned footColor = color; if (sOnScreen) { drawFoot(tg, hvg, inter->sourceChrom, inter->sourceStart, inter->sourceEnd, sX - sWidth, ySource, sWidth + sWidth + 1, footColor, drawUp, footHash); if (vis == tvDense || !tOnScreen || draw == DRAW_LINE || hvg->rc) { // draw vertical from foot to peak drawLine(tg, hvg, sX, ySource, sX, peak, color, isReversed && doDashes, drawUp); } } if (tOnScreen) { drawFoot(tg, hvg, inter->targetChrom, inter->targetStart, inter->targetEnd, tX - tWidth, yTarget, tWidth + tWidth + 1, footColor, drawUp, footHash); if (vis == tvDense || !sOnScreen || draw == DRAW_LINE || hvg->rc) { // draw vertical from foot to peak drawLine(tg, hvg, tX, yTarget, tX, peak, color, isReversed && doDashes, drawUp); } } if (vis == tvDense) continue; // Full mode: add map boxes and draw interaction if (sOnScreen) { // draw grab box and map box to source region drawFootMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->sourceName, statusBuf, sX, ySource, sWidth, peakColor, highlightColor, drawUp); } if (tOnScreen) { // draw grab box and add map box to target region drawFootMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->targetName, statusBuf, tX, yTarget, tWidth, tInfo->isDirectional ? MG_MAGENTA : peakColor, highlightColor, drawUp); } if ((s < seqStart && t < seqStart) || (s > seqEnd && t > seqEnd)) continue; // Draw interaction and map boxes int lowerX = 0, upperX = 0; if (s < t) { lowerX = sOnScreen ? sX : xOff; upperX = tOnScreen ? tX : xOff + width; } else { lowerX = tOnScreen ? tX : xOff; upperX = sOnScreen ? sX : xOff + width; } if (draw == DRAW_LINE || !sOnScreen || !tOnScreen || hvg->rc) { // draw horizontal line between region centers at 'peak' height drawLine(tg, hvg, lowerX, peak, upperX, peak, color, isReversed && doDashes, drawUp); // draw grab box and map box on mid-point of horizontal line int xMap = lowerX + (double)(upperX-lowerX)/2; drawPeakMapbox(tg, hvg, inter->chromStart, inter->chromEnd, itemBuf, statusBuf, xMap, peak, peakColor, highlightColor, drawUp); continue; } // Draw curves if (draw == DRAW_CURVE) { int peakX = ((upperX - lowerX + 1) / 2) + lowerX; int fudge = 30; int peakY = peak + fudge; // admittedly a hack (obscure how to define ypeak of curve) int y1 = isReversed ? yTarget : ySource; int y2 = isReversed ? ySource : yTarget; if (drawUp) { y1 = flipY(tg, y1); y2 = flipY(tg, y2); peakY = flipY(tg, peakY); } int maxY = hvGfxCurve(hvg, lowerX, y1, peakX, peakY, upperX, y2, color, isReversed && doDashes); // curve drawer does not use peakY as expected, so it returns actual max Y used // draw grab box and map box on peak if (drawUp) { maxY = (maxY - peakY + 1)/2 + tg->customInt; if (tInfo->offset) maxY += abs(yTarget - ySource)/4; } drawPeakMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->name, statusBuf, peakX, maxY, peakColor, highlightColor, drawUp); } else if (draw == DRAW_ELLIPSE) { // can not support offsets int yLeft = yOff + peakHeight; int yTop = yOff - peakHeight; int ellipseOrient = ELLIPSE_BOTTOM; if (drawUp) { ellipseOrient = ELLIPSE_TOP; yLeft = yOff + tg->height - peakHeight; yTop = yOff + tg->height + peakHeight; } hvGfxEllipseDraw(hvg, lowerX, yLeft, upperX, yTop, color, ellipseOrient, isReversed && doDashes); // draw grab box and map box on peak int maxY = peakHeight + yOff; int peakX = ((upperX - lowerX + 1) / 2) + lowerX; drawPeakMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->name, statusBuf, peakX, maxY, peakColor, highlightColor, drawUp); } } } void interactLinkedFeaturesDrawAt(struct track *tg, void *item, struct hvGfx *hvg, int xOff, int y, double scale, MgFont *font, Color color, enum trackVisibility vis) /* Draw an item with target in contrasting color */ { struct linkedFeatures *lf = item; if (vis == tvDense) { lf->filterColor = slightlyDarkerColor(hvg, MG_GRAY); // can't distinguish overlapping colors, so force to gray } struct interactTrackInfo *tInfo = tg->customPt; linkedFeaturesDrawAt(tg, item, hvg, xOff, y, scale, font, color, vis); if (tInfo->clusterMode) { struct simpleFeature *sf; int shortHeight = tg->heightPer/2; for (sf = lf->components; sf; sf = sf->next) { if (sf->start > lf->tallStart && sf->end < lf->tallEnd) { drawScaledBox(hvg, sf->start, sf->end, scale, xOff, y + shortHeight/2, shortHeight, MG_WHITE); } } } else { struct simpleFeature *sf1 = lf->components, *sf2 = sf1->next; if (sf2 && sf2->start > lf->tallStart && sf2->end < lf->tallEnd) drawScaledBox(hvg, sf2->start, sf2->end, scale, xOff, y, tg->heightPer, MG_WHITE); if (vis == tvPack || vis == tvFull) { // add right label int x2 = round((double)((int)lf->end - winStart) * scale) + xOff; int x = x2 + tl.mWidth/2; struct interactLfEndNames *ends = getInteractLfEndNames(lf); char *rightLabel = (lf->orientation < 0 ? ends->source : ends->target); int w = mgFontStringWidth(font, rightLabel); hvGfxTextCentered(hvg, x, y, w, tg->heightPer, color, font, rightLabel); } } } void interactDrawItems(struct track *tg, int seqStart, int seqEnd, struct hvGfx *hvg, int xOff, int yOff, int width, MgFont *font, Color color, enum trackVisibility vis) /* Draw a list of interact structures. */ { struct interactTrackInfo *tInfo = (struct interactTrackInfo *)tg->customPt; #ifdef MR_FORCE_PACK if (tInfo->clusterMode || isLinkedFeaturesMode(tg) || differentString(virtModeType, "default")) #else if (tInfo->clusterMode || isLinkedFeaturesMode(tg)) #endif { tg->drawItemAt = interactLinkedFeaturesDrawAt; + if (tg->items != NULL) linkedFeaturesDraw(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis); } else // curve, etc. connector display drawInteractItems(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis); } void interactMethods(struct track *tg) /* Interact track type methods */ { tg->bedSize = 12; linkedFeaturesMethods(tg); // for most vis and mode settings tg->loadItems = interactLoadItems; tg->itemRightPixels = interactRightPixels; tg->drawItems = interactDrawItems; } void interactCtMethods(struct track *tg) /* Interact track methods for custom track */ { interactMethods(tg); }