ef7e53bd098a62ded05b8c3a08afff08ff939063 kate Wed Aug 8 16:26:40 2018 -0700 Minor changes to make it easier to see/mouseover/click on the interactions: 1) Darken color of grab box. 2) Make endpoint horizontals 2 pixels high (JK suggestion). refs #21109 diff --git src/hg/hgTracks/interactTrack.c src/hg/hgTracks/interactTrack.c index a6b1a28..f770776 100644 --- src/hg/hgTracks/interactTrack.c +++ src/hg/hgTracks/interactTrack.c @@ -1,480 +1,480 @@ /* interactTrack -- draw interaction between two genomic regions */ /* Copyright (C) 2018 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include "common.h" #include "obscure.h" #include "hgTracks.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); } void interactLoadItems(struct track *tg) /* Load all interact items in region */ { loadSimpleBedWithLoader(tg, (bedItemLoader)interactLoadAndValidate); if (slCount(tg->items) == 0 && tg->limitedVisSet) { // too many items to display // borrowed behaviors in bamTrack and vcfTrack // TODO BRANEY: make this behavior generic for bigBeds // (bigBedSelectRange) tg->drawItems = bigDrawWarning; tg->networkErrMsg = "Too many items in display (zoom in)"; tg->totalHeight = bigWarnTotalHeight; return; } // filter on score char buf[1024]; safef(buf, sizeof buf, "%s.%s", tg->tdb->track, INTERACT_MINSCORE); int minScore = cartUsualInt(cart, buf, 0); struct interact *inter, *next, *filteredItems = NULL; int count = slCount(tg->items); for (inter = tg->items; inter; inter = next) { next = inter->next; if (inter->score < minScore) 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; } 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; } void interactRegionCenters(struct interact *inter, int *sourceCenter, int *targetCenter) /* Return genomic position of endpoint centers */ { assert(sourceCenter); assert(targetCenter); *sourceCenter = interactRegionCenter(inter->sourceStart, inter->sourceEnd); *targetCenter = interactRegionCenter(inter->targetStart, inter->targetEnd); } 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; } struct interactTrackInfo { boolean isDirectional; 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; struct interactTrackInfo *interactGetTrackInfo(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 = NULL; AllocVar(tInfo); tInfo->doOtherLabels = TRUE; tInfo->isDirectional = interactUiDirectional(tg->tdb); 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; return tInfo; } static 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. */ { #define DRAW_LINE 0 #define DRAW_CURVE 1 #define DRAW_ELLIPSE 2 // Determine drawing mode int draw = DRAW_LINE; 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; } double scale = scaleForWindow(width, seqStart, seqEnd); struct interact *inter = NULL; char buffer[1024]; char itemBuf[2048]; // Gather info for layout struct interactTrackInfo *tInfo = interactGetTrackInfo(tg, seqStart, hvg, xOff, font, scale); // Get spectrum range int scoreMin = atoi(trackDbSettingClosestToHomeOrDefault(tg->tdb, "scoreMin", "0")); int scoreMax = atoi(trackDbSettingClosestToHomeOrDefault(tg->tdb, "scoreMax", "1000")); // Draw items 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 0xffbbbbbb + #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 - hvGfxLine(hvg, x - footWidth, yOffOther, x + footWidth, yOffOther, color); + // draw the foot (2 pixels high) + hvGfxBox(hvg, x - footWidth, yOffOther, footWidth + footWidth + 1, 2, color); // draw the vertical if (tInfo->isDirectional && differentString(inter->chrom, inter->sourceChrom)) hvGfxDottedLine(hvg, x, yOffOther, x, yPos, color, TRUE); else hvGfxLine(hvg, x, yOffOther, x, yPos, color); if (vis == tvDense) continue; // add map box to foot char *nameBuf = (inter->chromStart == inter->sourceStart ? inter->sourceName : inter->targetName); if (isEmptyTextField(nameBuf)) nameBuf = statusBuf; int chromStart = inter->chromStart; int chromEnd = inter->chromEnd; mapBoxHgcOrHgGene(hvg, chromStart, chromEnd, x - footWidth, yOffOther, footWidth * 2, 4, tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); // add map box to vertical mapBoxHgcOrHgGene(hvg, chromStart, 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); hvGfxTextCentered(hvg, x, yPos + 2, 4, 4, MG_BLUE, font, buffer); int labelWidth = vgGetFontStringWidth(hvg->vg, font, buffer); // add map box to label mapBoxHgcOrHgGene(hvg, chromStart, 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. if (sOnScreen) { - // draw foot of source region - hvGfxLine(hvg, sX - sWidth, yOff, sX + sWidth, yOff, color); + // draw foot of source region (2 pixels high) + hvGfxBox(hvg, sX - sWidth, yOff, sWidth + sWidth + 1, 2, color); if (vis == tvDense || !tOnScreen || draw == DRAW_LINE || hvg->rc) { // draw vertical if (isReversed) hvGfxDottedLine(hvg, sX, yOff, sX, peak, color, TRUE); else hvGfxLine(hvg, sX, yOff, sX, peak, color); } } if (tOnScreen) { - // draw foot of target region - hvGfxLine(hvg, tX - tWidth, yOff, tX + tWidth, yOff, color); + // draw foot of target region (2 pixels high) + hvGfxBox(hvg, tX - tWidth, yOff, tWidth + tWidth + 1, 2, color); if (vis == tvDense || !sOnScreen || draw == DRAW_LINE || hvg->rc) { // draw vertical if (isReversed) hvGfxDottedLine(hvg, tX, yOff, tX, peak, color, TRUE); else hvGfxLine(hvg, tX, yOff, tX, peak, color); } } if (vis == tvDense) continue; // Full mode: add map boxes and draw interaction int chromStart = inter->chromStart; int chromEnd = inter->chromEnd; char *nameBuf = NULL; if (sOnScreen) { // add map box to source region nameBuf = isEmptyTextField(inter->sourceName) ? statusBuf : inter->sourceName; hvGfxBox(hvg, sX-1, yOff, 3, 2, peakColor); hvGfxBox(hvg, sX, yOff, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, chromStart, chromEnd, sX - sWidth, yOff, sWidth * 2, 4, tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); } if (tOnScreen) { // add map box to target region nameBuf = isEmptyTextField(inter->targetName) ? statusBuf : inter->targetName; hvGfxBox(hvg, tX-1, yOff, 3, 2, peakColor); hvGfxBox(hvg, tX, yOff, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, chromStart, chromEnd, tX - tWidth, yOff, tWidth * 2, 4, tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); } 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 if (isReversed) hvGfxDottedLine(hvg, lowerX, peak, upperX, peak, color, TRUE); else hvGfxLine(hvg, lowerX, peak, upperX, peak, color); // map box on mid-point of horizontal line int xMap = lowerX + (double)(upperX-lowerX)/2; int yMap = peak-1; hvGfxBox(hvg, xMap-1, peak-1, 3, 3, peakColor); hvGfxBox(hvg, xMap, peak, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, chromStart, chromEnd, xMap-1, yMap-1, 3, 3, tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); continue; } // Draw curves if (draw == DRAW_CURVE) { int peakX = ((upperX - lowerX + 1) / 2) + lowerX; int peakY = peak + 30; // admittedly a hack (obscure how to define ypeak of curve) int maxY = hvGfxCurve(hvg, lowerX, yOff, peakX, peakY, upperX, yOff, color, isReversed); // curve drawer does not use peakY as expected, so it returns actual max Y used // draw map box on peak hvGfxBox(hvg, peakX-1, maxY-1, 3, 3, peakColor); hvGfxBox(hvg, peakX, maxY, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, chromStart, chromEnd, peakX-1, maxY-1, 3, 3, tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); } else if (draw == DRAW_ELLIPSE) { int yLeft = yOff + peakHeight; int yTop = yOff - peakHeight; hvGfxEllipseDraw(hvg, lowerX, yLeft, upperX, yTop, color, ELLIPSE_BOTTOM, isReversed); // draw map box on peak int maxY = peakHeight + yOff; int peakX = ((upperX - lowerX + 1) / 2) + lowerX; hvGfxBox(hvg, peakX-1, maxY-1, 3, 3, peakColor); hvGfxBox(hvg, peakX, maxY, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, chromStart, chromEnd, peakX-1, maxY-1, 3, 3, tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); } } } void interactDrawLeftLabels(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 */ { } void interactMethods(struct track *tg) /* Interact track type methods */ { tg->loadItems = interactLoadItems; tg->drawItems = interactDrawItems; tg->drawLeftLabels = interactDrawLeftLabels; tg->totalHeight = interactTotalHeight; tg->mapsSelf = TRUE; } void interactCtMethods(struct track *tg) /* Interact track methods for custom track */ { interactMethods(tg); }