69ed2f332551b505c78b081306d834224e70e5da kate Tue Mar 20 23:24:08 2018 -0700 Code cleanup. Fix mouseover on curves. refs #17512 diff --git src/hg/hgTracks/interactTrack.c src/hg/hgTracks/interactTrack.c index 45b7026..7f952bd 100644 --- src/hg/hgTracks/interactTrack.c +++ src/hg/hgTracks/interactTrack.c @@ -69,341 +69,390 @@ continue; if (tg->colorShades) { struct bed *bed = (struct bed *)inter; adjustBedScoreGrayLevel(tg->tdb, bed, scoreMin, scoreMax); } 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; } -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. */ +char *interactMouseover(struct interact *inter, char *otherChrom) +/* Make mouseover text for an interaction */ { -#define DRAW_LINE 0 -#define DRAW_CURVE 1 -#define DRAW_ELLIPSE 2 +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 draw = DRAW_LINE; -if (vis != tvDense) +int regionCenter(int start, int end) +/* Return center of genomic region */ { - 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; +return round((double)(end - start + .5) / 2) + start; } -boolean isDirectional = interactUiDirectional(tg->tdb); -double scale = scaleForWindow(width, seqStart, seqEnd); -struct interact *inters = tg->items; -unsigned int maxWidth = 0; -struct interact *inter; -char buffer[1024]; -char itemBuf[2048]; +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; +} -// Determine if there are mixed inter and intra-chromosomal -// Suppress interchromosomal labels if they overlap -int nSame = 0, nOther = 0; -int prevLabelEnd = 0, prevLabelStart = 0; -char *prevLabel = 0; -boolean doOtherLabels = TRUE; -char *otherChrom = NULL; -for (inter=inters; inter; inter=inter->next) +void interactRegionCenters(struct interact *inter, int *sourceCenter, int *targetCenter) +/* Return genomic position of endpoint centers */ +{ +assert(sourceCenter); +assert(targetCenter); +*sourceCenter = regionCenter(inter->sourceStart, inter->sourceEnd); +*targetCenter = regionCenter(inter->targetStart, inter->targetEnd); +} + +int interactSize(struct interact *inter) +/* Compute length of interaction (distance between middle of each region) in bp */ { - int width = inter->chromEnd - inter->chromStart; - if (width > maxWidth) - maxWidth = width; +int sourceCenter = 0, targetCenter = 0; +interactRegionCenters(inter, &sourceCenter, &targetCenter); +return abs(targetCenter - sourceCenter); } -for (inter=inters; inter; inter=inter->next) + +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 round((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) - nSame++; + { + tInfo->sameCount++; + // determine maximum interaction size, for later use laying out 'peaks' + int size = interactSize(inter); + if (size > tInfo->maxSize) + tInfo->maxSize = size; + } else { - nOther++; - if (!doOtherLabels) + tInfo->otherCount++; + // suppress interchromosomal labels if they overlap + if (!tInfo->doOtherLabels) continue; int labelWidth = vgGetFontStringWidth(hvg->vg, font, otherChrom); - // TODO: simplify now that center approach is abandoned - int sx = ((inter->chromStart - seqStart) + .5) * scale + xOff; // x coord of center - int labelStart = (double)sx - labelWidth/2; + int x = getX(inter->chromStart, seqStart, scale, xOff); + assert(x > 0); + int labelStart = round((double)(x - labelWidth)/2); int labelEnd = labelStart + labelWidth - 1; if (labelStart <= prevLabelEnd && !(labelStart == prevLabelStart && labelEnd == prevLabelEnd && sameString(otherChrom, prevLabel))) - doOtherLabels = FALSE; + tInfo->doOtherLabels = FALSE; prevLabelStart = labelStart; prevLabelEnd = labelEnd; prevLabel = otherChrom; } } -int fontHeight = vgGetFontPixelHeight(hvg->vg, font); -int otherHeight = (nOther) ? 3 * fontHeight : 0; -int sameHeight = (nSame) ? tg->height - otherHeight: 0; +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); // Draw items -for (inter=inters; inter; inter=inter->next) +for (inter = (struct interact *)tg->items; inter; inter = inter->next) { char *otherChrom = interactOtherChrom(inter); safef(itemBuf, sizeof itemBuf, "%s", inter->name); - 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); - char *statusBuf = dyStringCannibalize(&ds); + char *statusBuf = interactMouseover(inter, otherChrom); + // Pick colors color = interactItemColor(tg, inter, hvg); - if (vis == tvDense && interactOtherChrom(inter) && color == MG_BLACK) + if (vis == tvDense && otherChrom && color == MG_BLACK) + // use highlight color for other chrom items in dense mode color = MG_MAGENTA; int peakColor = (color == MG_BLACK || tg->colorShades) ? MG_MAGENTA : MG_GRAY; - // TODO: simplify by using start/end instead of center and width - // This is a holdover from longRange track implementation - unsigned lowStart, lowEnd, highStart, highEnd; - if (otherChrom) - { - lowStart = highStart = inter->chromStart; - lowEnd = highEnd = inter->chromEnd; - } - else if (inter->sourceStart < inter->targetStart) - { - lowStart = inter->sourceStart; - lowEnd = inter->sourceEnd; - highStart = inter->targetStart; - highEnd = inter->targetEnd; - } - else - { - lowStart = inter->targetStart; - lowEnd = inter->targetEnd; - highStart = inter->sourceStart; - highEnd = inter->sourceEnd; - } - unsigned s = lowStart + ((double)(lowEnd - lowStart + .5) / 2); - int sx = ((s - seqStart) + .5) * scale + xOff; // x coord of center (lower region) - unsigned sw = lowEnd - lowStart; - int sFootWidth = scale * (double)sw / 2; // width in pixels of half foot (lower) - if (sFootWidth == 0) - sFootWidth = 1; - - unsigned e = highStart + (double)(highEnd - highStart + .5) / 2; - int ex = ((e - seqStart) + .5) * scale + xOff; - unsigned ew = highEnd - highStart; - int eFootWidth = scale * (double)ew / 2; - if (eFootWidth == 0) - eFootWidth = 1; - if (otherChrom) { // different chromosomes // draw below same chrom items, if any - unsigned yPos = 0; int height = 0; int yOffOther = yOff; - if (tg->visibility == tvDense) + if (vis == tvDense) { height = tg->height; } else { - height = otherHeight/2; - yOffOther = yOff + sameHeight; + height = tInfo->otherHeight/2; + yOffOther = yOff + tInfo->sameHeight; } - yPos = yOffOther + height; + unsigned r = regionCenter(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, sx - sFootWidth, yOffOther, sx + sFootWidth, yOffOther, color); + hvGfxLine(hvg, x - footWidth, yOffOther, x + footWidth, yOffOther, color); // draw the vertical - // TODO: modularize directional/non-directional draws - if (differentString(inter->chrom, inter->sourceChrom) && isDirectional) - hvGfxDottedLine(hvg, sx, yOffOther, sx, yPos, color, TRUE); + if (tInfo->isDirectional && differentString(inter->chrom, inter->sourceChrom)) + hvGfxDottedLine(hvg, x, yOffOther, x, yPos, color, TRUE); else - hvGfxLine(hvg, sx, yOffOther, sx, yPos, color); + hvGfxLine(hvg, x, yOffOther, x, yPos, color); + + if (vis == tvDense) + continue; - if (tg->visibility == tvFull) - { // add map box to foot char *nameBuf = (inter->chromStart == inter->sourceStart ? inter->sourceName : inter->targetName); if (isEmptyTextField(nameBuf)) nameBuf = statusBuf; mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, - sx - sFootWidth, yOffOther, sFootWidth * 2, 4, + x - footWidth, yOffOther, footWidth * 2, 4, tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); // add map box to vertical - mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, sx - 2, yOffOther, 4, height, - tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); - if (doOtherLabels) + 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); - hvGfxTextCentered(hvg, sx, yPos + 2, 4, 4, MG_BLUE, font, buffer); - int width = vgGetFontStringWidth(hvg->vg, font, buffer); + hvGfxTextCentered(hvg, x, yPos + 2, 4, 4, MG_BLUE, font, buffer); + int labelWidth = vgGetFontStringWidth(hvg->vg, font, buffer); - // add mapBox to label - mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, sx - width/2, yPos, - width, fontHeight, tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); - } + // 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 + // Draw same chromosome interaction + + // source region + unsigned s = regionCenter(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); - boolean eOnScreen = (e >= seqStart) && (e < seqEnd); - double interWidth = e - s; - int peakHeight = (sameHeight - 15) * ((double)interWidth / maxWidth) + 10; + // target region + unsigned t = regionCenter(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 (tg->visibility == tvDense) + if (vis == tvDense) peak = yOff + tg->height; if (sOnScreen) { - // draw foot of lower region - hvGfxLine(hvg, sx - sFootWidth, yOff, sx + sFootWidth, yOff, color); - // draw vertical - if (!eOnScreen || draw == DRAW_LINE) + // draw foot of source region + hvGfxLine(hvg, sX - sWidth, yOff, sX + sWidth, yOff, color); + if (vis == tvDense || !tOnScreen || draw == DRAW_LINE) { - if (inter->chromStart == inter->targetStart && isDirectional) - hvGfxDottedLine(hvg, sx, yOff, sx, peak, color, TRUE); + // draw vertical + if (isReversed) + hvGfxDottedLine(hvg, sX, yOff, sX, peak, color, TRUE); else - hvGfxLine(hvg, sx, yOff, sx, peak, color); + hvGfxLine(hvg, sX, yOff, sX, peak, color); } } - if (eOnScreen) + if (tOnScreen) { - // draw foot of upper region - hvGfxLine(hvg, ex - eFootWidth, yOff, ex + eFootWidth, yOff, color); - - // draw vertical - if (!sOnScreen || draw == DRAW_LINE) + // draw foot of target region + hvGfxLine(hvg, tX - tWidth, yOff, tX + tWidth, yOff, color); + if (vis == tvDense || !sOnScreen || draw == DRAW_LINE) { - if (inter->chromStart == inter->targetStart && isDirectional) - hvGfxDottedLine(hvg, ex, yOff, ex, peak, color, TRUE); + // draw vertical + if (isReversed) + hvGfxDottedLine(hvg, tX, yOff, tX, peak, color, TRUE); else - hvGfxLine(hvg, ex, yOff, ex, peak, color); + hvGfxLine(hvg, tX, yOff, tX, peak, color); } } - if (tg->visibility == tvFull) - { + if (vis == tvDense) + continue; + + // Full mode: add map boxes and draw interaction char *nameBuf = NULL; if (sOnScreen) { - /* add mapbox to lower region */ - nameBuf = (inter->chromStart == inter->sourceStart ? - inter->sourceName : inter->targetName); - if (isEmptyTextField(nameBuf)) - nameBuf = statusBuf; - hvGfxBox(hvg, sx-1, yOff, 3, 1, peakColor); - hvGfxBox(hvg, sx, yOff, 1, 1, MG_WHITE); + // add map box to source region + nameBuf = isEmptyTextField(inter->sourceName) ? statusBuf : inter->sourceName; + hvGfxBox(hvg, sX-1, yOff, 3, 1, peakColor); + hvGfxBox(hvg, sX, yOff, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, - sx - sFootWidth, yOff, sFootWidth * 2, 3, + sX - sWidth, yOff, sWidth * 2, 3, tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); - } - if (eOnScreen) + if (tOnScreen) { - /* add mapbox to upper region */ - nameBuf = (inter->chromEnd == inter->targetEnd ? - inter->targetName : inter->sourceName); - if (isEmptyTextField(nameBuf)) - nameBuf = statusBuf; - hvGfxBox(hvg, ex-1, yOff, 3, 1, peakColor); - hvGfxBox(hvg, ex, yOff, 1, 1, MG_WHITE); + // add map box to target region + nameBuf = isEmptyTextField(inter->targetName) ? statusBuf : inter->targetName; + hvGfxBox(hvg, tX-1, yOff, 3, 1, peakColor); + hvGfxBox(hvg, tX, yOff, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, - ex - eFootWidth, yOff, eFootWidth * 2, 3, + tX - tWidth, yOff, tWidth * 2, 3, tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); } - if (sOnScreen && eOnScreen && draw != DRAW_LINE) + // 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) { - boolean isDashed = (inter->sourceStart > inter->targetStart); + // 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, peak-1, 1, 3, peakColor); + hvGfxBox(hvg, xMap, peak, 1, 1, MG_WHITE); + mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, xMap-1, yMap, 3, 3, + tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); + continue; + } + // Draw curves if (draw == DRAW_CURVE) { - int peakX = ((ex - sx + 1) / 2) + sx; + 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, sx, yOff, peakX, peakY, ex, yOff, color, isDashed); + 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, 3, 1, peakColor); hvGfxBox(hvg, peakX, maxY, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, peakX, maxY, 3, 1, - tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); + tg->track, itemBuf, statusBuf, NULL, TRUE, NULL); } else if (draw == DRAW_ELLIPSE) { int yLeft = yOff + peakHeight; int yTop = yOff - peakHeight; - hvGfxEllipseDraw(hvg, sx, yLeft, ex, yTop, color, ELLIPSE_BOTTOM, isDashed); + hvGfxEllipseDraw(hvg, lowerX, yLeft, upperX, yTop, color, ELLIPSE_BOTTOM, isReversed); // draw map box on peak int maxY = peakHeight + yOff; - int peakX = ((ex - sx + 1) / 2) + sx; + int peakX = ((upperX - lowerX + 1) / 2) + lowerX; hvGfxBox(hvg, peakX-1, maxY, 3, 1, peakColor); hvGfxBox(hvg, peakX, maxY, 1, 1, MG_WHITE); mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, peakX, maxY, 3, 1, - tg->track, itemBuf, nameBuf, NULL, TRUE, NULL); - } - } - else - { - // draw link horizontal line between regions - unsigned ePeak = eOnScreen ? ex : xOff + width; - unsigned sPeak = sOnScreen ? sx : xOff; - if (inter->sourceStart > inter->targetStart && isDirectional) - hvGfxDottedLine(hvg, sPeak, peak, ePeak, peak, color, TRUE); - else - hvGfxLine(hvg, sPeak, peak, ePeak, peak, color); - - // map box on mid-point of horizontal line - int xMap = sPeak + (double)(ePeak-sPeak)/2; - int yMap = peak-1; - hvGfxBox(hvg, xMap, peak-1, 1, 3, peakColor); - hvGfxBox(hvg, xMap, peak, 1, 1, MG_WHITE); - mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, xMap-1, yMap, 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;