05e67c59a20a5d00b810a981aef3b00c5bef82e1 max Fri Sep 20 06:03:18 2024 -0700 more features to hubtools: search in both parent and subdirs, better docs diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c index 39c2763..e6c9d30 100644 --- src/hg/hgTracks/simpleTracks.c +++ src/hg/hgTracks/simpleTracks.c @@ -1659,30 +1659,101 @@ return FALSE; // Ignore. if (chromStart > winEnd || winStart > chromEnd || (!isIns && chromStart == winEnd) || (!isIns && chromEnd == winStart)) // doesn't overlap window? return FALSE; // nothing to do if (chromStart < winStart) // clip to part overlapping window chromStart = winStart; if (chromEnd > winEnd) // which prevents x1,x2 from overflowing when zooming-in makes scale large. chromEnd = winEnd; *pX1 = round((double)(chromStart-winStart)*scale) + xOff; *pX2 = isIns ? (*pX1 + 1) : round((double)(chromEnd-winStart)*scale) + xOff; return TRUE; } +void drawNotches(struct hvGfx* hvg, int x1, int x2, int y, int height, Color color, int startPhase, int endPhase) +{ +// do nothing for -1 and 0. Also ignore invalid exonFrame numbers, e.g. 4. +int w = (int)(height*0.75); +int halfHeight = (int)(height/2); + +if (x2-x1 < 2*w) + w = ((x2-x1)/2); + +//if ((x2-x1) > 8*height) // TODO: this should use the letter-width somehow... + //return; + +if ((startPhase==1 || startPhase==2)) + { + struct gfxPoly *poly = gfxPolyNew(); + switch (startPhase) + { + case 1 : + // a triangle + gfxPolyAddPoint(poly, x1, y+halfHeight); + gfxPolyAddPoint(poly, x1+w, y); + gfxPolyAddPoint(poly, x1+w, y+height); + break; + + case 2 : + { + // the opposite of a triangle + int halfW = (int)(w/2); + gfxPolyAddPoint(poly, x1, y); + gfxPolyAddPoint(poly, x1, y+height); + gfxPolyAddPoint(poly, x1+w, y+height); + gfxPolyAddPoint(poly, x1+halfW, y+halfHeight); + gfxPolyAddPoint(poly, x1+w, y); + break; + } + } + + hvGfxDrawPoly(hvg,poly,color,TRUE); + gfxPolyFree(&poly); + } + +if (endPhase==1 || endPhase==2) + { + struct gfxPoly *poly = gfxPolyNew(); + switch (endPhase) + { + case 1 : + // a triangle + gfxPolyAddPoint(poly, x2-w, y); + gfxPolyAddPoint(poly, x2+w, y+halfHeight); + gfxPolyAddPoint(poly, x2-w, y+height); + break; + + case 2 : + { + // the opposite of a triangle + int halfW = (int)(w/2); + gfxPolyAddPoint(poly, x2-w, y); + gfxPolyAddPoint(poly, x2-w, y+height); + gfxPolyAddPoint(poly, x2, y+height); + gfxPolyAddPoint(poly, x2-halfW, y+halfHeight); + gfxPolyAddPoint(poly, x2, y); + break; + } + } + + hvGfxDrawPoly(hvg,poly,color,TRUE); + gfxPolyFree(&poly); + } +} + void drawScaledBox(struct hvGfx *hvg, int chromStart, int chromEnd, double scale, int xOff, int y, int height, Color color) /* Draw a box scaled from chromosome to window coordinates. * Get scale first with scaleForPixels. */ { int x1, x2; if (scaledBoxToPixelCoords(chromStart, chromEnd, scale, xOff, &x1, &x2)) { int w = x2-x1; if (w == 0) // when zoomed out, avoid shinking to nothing w = 1; hvGfxBox(hvg, x1, y, w, height, color); } } @@ -2662,51 +2733,51 @@ *pNewWinStart = *pNewWinEnd - newWinSize; } #define EXONTEXTLEN 256 static void makeExonFrameText(int exonIntronNumber, int numExons, int startPhase, int endPhase, char *buf) /* Write mouseover text that describes the exon's phase into buf[EXONTEXTLEN]. Note that start/end-phases are in the direction of transcription: if transcript is on + strand, the start phase is the exonFrame value, and the end phase is the next exonFrame (3' on DNA) value if transcript is on - strand, the start phase is the previous (=3' on DNA) exonFrame and the end phase is the exonFrame */ { if (startPhase==-1) // UTRs don't have a frame at all { - safef(buf, EXONTEXTLEN, ", untranslated region"); + safef(buf, EXONTEXTLEN, "
untranslated region"); } else { char *exonNote = ""; boolean isNotLastExon = (exonIntronNumberCodon Phase: Start %d, End %d%s", startPhase, endPhase, exonNote); } else { if (startPhase==0) - exonNote = ": in-frame exon"; + exonNote = " = in-frame exon"; else - exonNote = ": out-of-frame exon"; - safef(buf, EXONTEXTLEN, ", start codon phase %d%s", startPhase, exonNote); + exonNote = " = out-of-frame exon"; + safef(buf, EXONTEXTLEN, "
Start Codon Phase: %d%s", startPhase, exonNote); } } } boolean linkedFeaturesNextPrevItem(struct track *tg, struct hvGfx *hvg, void *item, int x, int y, int w, int h, boolean next) /* Draw a mapBox over the arrow-button on an *item already in the window*. */ /* Clicking this will do one of several things: */ { boolean result = FALSE; struct linkedFeatures *lf = item; struct simpleFeature *exons = lf->components; struct simpleFeature *exon = exons; char *nextExonText; char *prevExonText; long newWinSize = virtWinEnd - virtWinStart; @@ -2886,35 +2957,286 @@ linkedFeaturesMoveWinStart(xExonStart, bufferToEdge, newWinSize, &newWinStart, &newWinEnd); if (!revStrand) safef(mouseOverText, sizeof(mouseOverText), "%s (%d/%d)", prevExonText, numExons-exonIx, numExons); else safef(mouseOverText, sizeof(mouseOverText), "%s (%d/%d)", nextExonText, exonIx+1, numExons); mapBoxJumpTo(hvg, x, y, w, h, tg, virtChromName, newWinStart, newWinEnd, mouseOverText); result = TRUE; break; } } slFreeList(&exonList); slFreeList(&crList); return result; } +/* this is a take at refactoring linkedFeaturesItemExonMaps */ +static void drawExonMapsNotches(struct track *tg, struct hvGfx *hvg, void *item, double scale, + int y, int heightPer, int sItem, int eItem, + boolean lButton, boolean rButton, int buttonW, Color color) +/* Draw mapBoxes over exons and introns labeled with exon/intron numbers, phase information, and draw notches for phases. */ +{ +struct linkedFeatures *lf = item; +struct simpleFeature *exons = lf->components; +struct simpleFeature *exon = exons; +char *exonText, *intronText; +int numExons = 0; +int exonIx = 1; +struct slRef *exonList = NULL, *ref; +// TODO this exonText (and intronText) setting is just a made-up placeholder. +// could add a real setting name. Maybe someday extend to exon names (LRG?) instead of just exon numbers +if (startsWith("chain", tg->tdb->type) || startsWith("lrg", tg->tdb->track) || startsWith("net", tg->tdb->track)) + { + exonText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "exonText" , "Block"); + intronText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "intronText", "Gap" ); // what really goes here for chain type? + } +else + { + exonText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "exonText" , "Exon" ); + intronText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "intronText", "Intron"); + } + +while (exon != NULL) +/* Make a stupid list of exons separate from what's given. */ +/* It seems like lf->components isn't necessarily sorted. */ + { + refAdd(&exonList, exon); + exon = exon->next; + } +/* Now sort it. */ +slSort(&exonList, exonSlRefCmp); + +numExons = slCount(exonList); +boolean revStrand = (lf->orientation == -1); +int eLast = -1; +int s = -1; +int e = -1; +boolean isExon = TRUE; +int picStart = insideX; +int picEnd = picStart + insideWidth; +if (lButton) + picStart += buttonW; +if (rButton) + picEnd -= buttonW; + +for (ref = exonList; TRUE; ) + { + exon = ref->val; + if (isExon) + { + s = exon->start; + e = exon->end; + } + else + { + s = eLast; + e = exon->start; + } + // skip exons and introns that are completely outside the window + if (s <= winEnd && e >= winStart) + { + int sClp = (s < winStart) ? winStart : s; + int eClp = (e > winEnd) ? winEnd : e; + + int sx = round((sClp - winStart)*scale) + insideX; + int ex = round((eClp - winStart)*scale) + insideX; + + // skip regions entirely outside available picture + // (accounts for space taken by exon arrows buttons) + if (sx <= picEnd && ex >= picStart) + { + // clip it to avail pic + sx = (sx < picStart) ? picStart : sx; + ex = (ex > picEnd) ? picEnd : ex; + + int w = ex - sx; + + if (w > 0) // draw exon or intron if width is greater than 0 + { + int exonIntronNumber; + char *exonIntronText; + int numExonIntrons = numExons; + if (isExon) + { + exonIntronText = exonText; + } + else + { + exonIntronText = intronText; + --numExonIntrons; // introns are one fewer than exons + } + + char strandChar; + if (!revStrand) { + exonIntronNumber = exonIx; + strandChar = '+'; + } + else { + exonIntronNumber = numExonIntrons-exonIx+1; + strandChar = '-'; + } + + // we still need to show the existing mouseover text + char* existingText = lf->mouseOver; + if (isEmpty(existingText)) + existingText = lf->name; + + int startPhase = -1; + int endPhase = -1; + char phaseText[EXONTEXTLEN]; + phaseText[0] = 0; + struct genePred *gp = lf->original; + if ((gp != NULL) && gp->exonFrames && isExon) + { + startPhase = gp->exonFrames[exonIx-1]; + if (!revStrand) + endPhase = gp->exonFrames[exonIx]; + else + if (exonIx>1) + endPhase = gp->exonFrames[exonIx-2]; + makeExonFrameText(exonIntronNumber, numExons, startPhase, endPhase, phaseText); + } + + // draw mapBoxes for the codons if we are zoomed in far enough + struct simpleFeature *codon; + struct dyString *codonDy = dyStringNew(0); + int codonS, codonE; + if (isExon && lf->codons && zoomedToCdsColorLevel) + { + for (codon = lf->codons; codon != NULL; codon = codon->next) + { + codonS = codon->start; codonE = codon->end; + if (codonS <= winEnd && codonE >= winStart) + { + int codonSClp = (codonS < winStart) ? winStart : codonS; + int codonEClp = (codonE > winEnd) ? winEnd : codonE; + + int codonsx = round((codonSClp - winStart)*scale) + insideX; + int codonex = round((codonEClp - winStart)*scale) + insideX; + + // skip regions entirely outside available picture + // (accounts for space taken by exon arrows buttons) + if (codonsx <= picEnd && codonex >= picStart) + { + // clip it to avail pic + codonsx = (codonsx < picStart) ? picStart : codonsx; + codonex = (codonex > picEnd) ? picEnd : codonex; + + int w = codonex - codonsx; + if (w > 0) + { + // temporarily remove the mouseOver from the lf, since linkedFeatureMapItem will always + // prefer a lf->mouseOver over the itemName + char *oldMouseOver = lf->mouseOver; + lf->mouseOver = NULL; + dyStringClear(codonDy); + if (!isEmpty(existingText)) + dyStringPrintf(codonDy, "%s, ", existingText); + int codonHgvsIx = (codon->codonIndex - 1) * 3; + if (codonHgvsIx >= 0) + dyStringPrintf(codonDy, "cDNA Pos: c.%d-%d
", codonHgvsIx + 1, codonHgvsIx + 3); + dyStringPrintf(codonDy, "Strand: %c
%s %d of %d%s", + strandChar, exonIntronText, exonIntronNumber, numExonIntrons, phaseText); + tg->mapItem(tg, hvg, item, codonDy->string, tg->mapItemName(tg, item), + sItem, eItem, codonsx, y, w, heightPer); + // and restore the mouseOver + lf->mouseOver = oldMouseOver; + } + } + } + } + } + } + /*else + { + // temporarily remove the mouseOver from the lf, since linkedFeatureMapItem will always + // prefer a lf->mouseOver over the itemName + if (!isEmpty(existingText)) + safef(mouseOverText, sizeof(mouseOverText), "%s, W < 0 ?? strand %c, %s %d of %d%s", + existingText, strandChar, exonIntronText, exonIntronNumber, numExonIntrons, phaseText); + else + safef(mouseOverText, sizeof(mouseOverText), "W < 0 ?? strand %c, %s %d of %d%s", + strandChar, exonIntronText, exonIntronNumber, numExonIntrons, phaseText); + char *oldMouseOver = lf->mouseOver; + lf->mouseOver = NULL; + tg->mapItem(tg, hvg, item, mouseOverText, tg->mapItemName(tg, item), + sItem, eItem, sx, y, w, heightPer); + // and restore the mouseOver + lf->mouseOver = oldMouseOver; + } + */ + + /*if (w > 0) // draw exon or intron if width is greater than 0 + { + char *sep = ""; + if (!isEmpty(existingText)) + sep = ", "; + + safef(mouseOverText, sizeof(mouseOverText), "%s%sstrand %c, %s %d of %d%s", + existingText, sep, strandChar, exonIntronText, exonIntronNumber, numExonIntrons, phaseText); + + // temporarily remove the mouseOver from the lf, since linkedFeatureMapItem will always + // prefer a lf->mouseOver over the itemName + char *oldMouseOver = lf->mouseOver; + lf->mouseOver = NULL; + tg->mapItem(tg, hvg, item, mouseOverText, tg->mapItemName(tg, item), + sItem, eItem, sx, y, w, heightPer); + // and restore the old mouseOver + lf->mouseOver = oldMouseOver; + + picStart = ex; // prevent pileups. is this right? add 1? does it work? + + Color notchColor = lighterColor(hvg, color); + Color notchColor2 = lighterColor(hvg, notchColor); + struct rgbColor rgb = hvGfxColorIxToRgb(hvg, notchColor2); + Color notchColor3 = MAKECOLOR_32_A(rgb.r,rgb.g,rgb.b,0.5); + drawNotches(hvg, sx, ex, y, heightPer, notchColor3, startPhase, endPhase); + } + */ + } + } + + if (isExon) + { + eLast = e; + ref = ref->next; + if (!ref) + break; + } + else + { + exonIx++; + } + isExon = !isExon; + + if (s > winEnd) // since the rest will also be outside the window + break; + + } + +slFreeList(&exonList); +} + +/* OLD CODE - under hg.conf control - to be removed one day */ void linkedFeaturesItemExonMaps(struct track *tg, struct hvGfx *hvg, void *item, double scale, int y, int heightPer, int sItem, int eItem, - boolean lButton, boolean rButton, int buttonW) + boolean lButton, boolean rButton, int buttonW, Color color) /* Draw mapBoxes over exons and introns labeled with exon/intron numbers */ { +if (cfgOptionBooleanDefault("newExonTooltips", FALSE)) + drawExonMapsNotches(tg, hvg, item, scale, y, heightPer, sItem, eItem, lButton, rButton, buttonW, color); + struct linkedFeatures *lf = item; struct simpleFeature *exons = lf->components; struct simpleFeature *exon = exons; char *exonText, *intronText; int numExons = 0; int exonIx = 1; struct slRef *exonList = NULL, *ref; // TODO this exonText (and intronText) setting is just a made-up placeholder. // could add a real setting name. Maybe someday extend to exon names (LRG?) instead of just exon numbers if (startsWith("chain", tg->tdb->type) || startsWith("lrg", tg->tdb->track)) { exonText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "exonText" , "Block"); intronText = trackDbSettingClosestToHomeOrDefault(tg->tdb, "intronText", "Gap" ); // what really goes here for chain type? } else @@ -3115,30 +3437,31 @@ break; } else { exonIx++; } isExon = !isExon; if (s > winEnd) // since the rest will also be outside the window break; } slFreeList(&exonList); } +/* END OLD CODE */ static struct window *makeMergedWindowList(struct window *windows) /* Make a copy of the windows list, merging nearby regions on the same chrom and which are within some limit (1MB?) of each other. */ { int mergeLimit = 1024*1024; struct window *w = windows; struct window *resultList = NULL; struct window *mergedW = NULL; if (!windows) errAbort("Unexpected error: windows list NULL in makeMergedWindowList()"); boolean doNew = TRUE; while(TRUE) { if (doNew) @@ -4275,32 +4598,31 @@ color); } s = e2; } if (e > tallEnd) { s2 = s; if (s2 < tallEnd) s2 = tallEnd; if (lf->highlightColor && (lf->highlightMode == highlightOutline)) { drawScaledBox(hvg, s2, e, scale, xOff, y+shortOff, shortHeight, lf->highlightColor); drawScaledBox(hvg, s2, e, scale, xOff+1, y+shortOff+1, shortHeight-2, color); } else { - drawScaledBox(hvg, s2, e, scale, xOff, y+shortOff, shortHeight, - color); + drawScaledBox(hvg, s2, e, scale, xOff, y+shortOff, shortHeight, color); } e = s2; } /* Draw "tall" portion of exon (or codon) */ if (e > s) { if (drawOpt > baseColorDrawOff && e + 6 >= winStart && s - 6 < winEnd && (e-s <= 3 || !baseColorNeedsCodons)) baseColorDrawItem(tg, lf, sf->grayIx, hvg, xOff, y, scale, font, s, e, heightPer, zoomedToCodonLevel, qSeq, qOffset, sf, psl, drawOpt, MAXPIXELS, winStart, color); else { @@ -4565,31 +4887,32 @@ int w = x2-textX; if (lButton) { // if left-button, the label will be on far left, split out a map just for that label. tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, textX, y, insideX-textX, heightPer); textX = insideX + buttonW; // continue on the right side of the left exon button w = x2-textX; } if (rButton) { w -= buttonW; } if (compat) { // draw labeled exon/intron maps with exon/intron numbers - linkedFeaturesItemExonMaps(tg, hvg, item, scale, y, heightPer, s, e, lButton, rButton, buttonW); + linkedFeaturesItemExonMaps(tg, hvg, item, scale, y, heightPer, s, e, lButton, rButton, buttonW, color); + x2 = x1; w = x2-textX; } // if not already mapped, pick up the label if (!(lButton && compat)) { tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, textX, y, w, heightPer); } } else if (vis == tvSquish) { int w = x2-textX; tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, textX, y, w, heightPer); @@ -4602,31 +4925,31 @@ int trackPastTabX = (withLeftLabels ? trackTabWidth : 0); tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, trackPastTabX, y, insideX - trackPastTabX, heightPer); /* Depending on which button mapboxes we drew, draw the remaining mapbox. */ if (lButton) { geneMapBoxX += buttonW; geneMapBoxW -= buttonW; } if (rButton) { geneMapBoxW -= buttonW; } if (compat) { // draw labeled exon/intron maps with exon/intron numbers - linkedFeaturesItemExonMaps(tg, hvg, item, scale, y, heightPer, s, e, lButton, rButton, buttonW); + linkedFeaturesItemExonMaps(tg, hvg, item, scale, y, heightPer, s, e, lButton, rButton, buttonW, color); if (!lButton) { int w = x1 - geneMapBoxX; if (w > 0) tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, geneMapBoxX, y, w, heightPer); } if (!rButton) { int w = geneMapBoxX + geneMapBoxW - x2; if (w > 0) tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, x2, y, w, heightPer); } } @@ -13090,30 +13413,31 @@ char *table = tg->table; int bedSize = tg->bedSize; /* field count of bed part */ boolean useItemRgb = bedItemRgb(tg->tdb); struct customTrack *ct = tg->customPt; if (ct == NULL) conn = hAllocConn(database); else { conn = hAllocConn(CUSTOM_TRASH); table = ct->dbTableName; bedSize = ct->fieldCount - 2; useItemRgb = bedItemRgb(ct->tdb); } sr = hRangeQuery(conn, table, chromName, winStart, winEnd, NULL, &rowOffset); + while ((row = sqlNextRow(sr)) != NULL) { el = bedDetailLoadAsLf(row, rowOffset, bedSize, useItemRgb); slAddHead(&list, el); } slReverse(&list); sqlFreeResult(&sr); hFreeConn(&conn); tg->items = list; } void loadProtVar(struct track *tg) /* Load UniProt Variants with labels */ { struct protVarPos *list = NULL, *el;