45c9532b9711e24298ca3900a6d4b45e4b51983c galt Tue Jan 27 14:43:53 2015 -0800 Fixes #7474 - mouseover now shows exon and intron numbers for linkedFeatures tracks. diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c index 9cb31ff..d55397c 100644 --- src/hg/hgTracks/simpleTracks.c +++ src/hg/hgTracks/simpleTracks.c @@ -187,30 +187,31 @@ int leftLabelWidthChars = 17; /* number of characters allowed for left label */ int insideX; /* Start of area to draw track in in pixels. */ int insideWidth; /* Width of area to draw tracks in in pixels. */ int leftLabelX; /* Start of area to draw left labels on. */ int leftLabelWidth; /* Width of area to draw left labels on. */ float basesPerPixel = 0; /* bases covered by a pixel; a measure of zoom */ boolean zoomedToBaseLevel; /* TRUE if zoomed so we can draw bases. */ boolean zoomedToCodonLevel; /* TRUE if zoomed so we can print codons text in genePreds*/ boolean zoomedToCdsColorLevel; /* TRUE if zoomed so we can color each codon*/ boolean withLeftLabels = TRUE; /* Display left labels? */ boolean withIndividualLabels = TRUE; /* print labels on item-by-item basis (false to skip) */ boolean withCenterLabels = TRUE; /* Display center labels? */ boolean withGuidelines = TRUE; /* Display guidelines? */ boolean withNextExonArrows = FALSE; /* Display next exon navigation buttons near center labels? */ +boolean withExonNumbers = FALSE; /* Display exon and intron numbers in mouseOver instead of item name */ boolean revCmplDisp = FALSE; /* reverse-complement display */ boolean measureTiming = FALSE; /* DON'T EDIT THIS -- use CGI param "&measureTiming=." . */ struct track *trackList = NULL; /* List of all tracks. */ struct cart *cart; /* The cart where we keep persistent variables. */ int seqBaseCount; /* Number of bases in sequence. */ int winBaseCount; /* Number of bases in window. */ int maxShade = 9; /* Highest shade in a color gradient. */ Color shadesOfGray[10+1]; /* 10 shades of gray from white to black * Red is put at end to alert overflow. */ Color shadesOfBrown[10+1]; /* 10 shades of brown from tan to tar. */ struct rgbColor brownColor = {100, 50, 0}; struct rgbColor tanColor = {255, 240, 200}; @@ -1907,30 +1908,157 @@ else if (bigExon) linkedFeaturesMoveWinEnd(exon->end, bufferToEdge, newWinSize, &newWinStart, &newWinEnd); else linkedFeaturesMoveWinStart(exon->start, 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, chromName, newWinStart, newWinEnd, mouseOverText); break; } } slFreeList(&exonList); } +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) +/* Draw mapBoxes over exons and introns labeled with exon/intron numbers */ +{ +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 + { + 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; +char mouseOverText[256]; +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; + + int exonIntronNumber; + char *exonIntronText; + int numExonIntrons = numExons; + if (isExon) + { + exonIntronText = exonText; + } + else + { + exonIntronText = intronText; + --numExonIntrons; // introns are one fewer than exons + } + + if (!revStrand) + exonIntronNumber = exonIx; + else + exonIntronNumber = numExonIntrons-exonIx+1; + + safef(mouseOverText, sizeof(mouseOverText), "%s (%d/%d)", exonIntronText, exonIntronNumber, numExonIntrons); + + if (w > 0) // draw exon or intron if width is greater than 0 + { + tg->mapItem(tg, hvg, item, mouseOverText, tg->mapItemName(tg, item), + sItem, eItem, sx, y, w, heightPer); + picStart = ex; // prevent pileups. is this right? add 1? does it work? + } + } + } + + 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); +} + void linkedFeaturesLabelNextPrevItem(struct track *tg, boolean next) /* Default next-gene function for linkedFeatures. Changes winStart/winEnd * and updates position in cart. */ { int start = winStart; int end = winEnd; int size = winBaseCount; int sizeWanted = size; int bufferToEdge; struct bed *items = NULL; /* If there's stuff on the screen, skip past it. */ /* If not, skip to the edge of the window. */ if (next) { @@ -2978,140 +3106,184 @@ return highlight; } double scaleForWindow(double width, int seqStart, int seqEnd) /* Return the scale for the window. */ { return width / (seqEnd - seqStart); } boolean nextItemCompatible(struct track *tg) /* Check to see if we draw nextPrev item buttons on a track. */ { return (withNextExonArrows && tg->nextExonButtonable && tg->nextPrevExon); } +boolean exonNumberMapsCompatible(struct track *tg, enum trackVisibility vis) +/* Check to see if we draw exon and intron maps labeling their number. */ +{ +boolean exonNumbers = sameString(trackDbSettingOrDefault(tg->tdb, "exonNumbers", "on"), "on"); +return (withExonNumbers && exonNumbers && (vis==tvFull || vis==tvPack) && (winEnd - winStart < 400000) + && (tg->nextPrevExon==linkedFeaturesNextPrevItem)); +} + void genericMapItem(struct track *tg, struct hvGfx *hvg, void *item, char *itemName, char *mapItemName, int start, int end, int x, int y, int width, int height) /* This is meant to be used by genericDrawItems to set to tg->mapItem in */ /* case tg->mapItem isn't set to anything already. */ { // Don't bother if we are imageV2 and a dense child. if (!theImgBox || tg->limitedVis != tvDense || !tdbIsCompositeChild(tg->tdb)) { char *directUrl = trackDbSetting(tg->tdb, "directUrl"); boolean withHgsid = (trackDbSetting(tg->tdb, "hgsid") != NULL); mapBoxHgcOrHgGene(hvg, start, end, x, y, width, height, tg->track, mapItemName, itemName, directUrl, withHgsid, NULL); } } void genericDrawNextItemStuff(struct track *tg, struct hvGfx *hvg, enum trackVisibility vis, - struct slList *item, int x2, int textX, int y, int heightPer, - Color color) + struct slList *item, double scale, int x2, int x1, int textX, int y, int heightPer, + Color color, boolean doButtons) /* After the item is drawn in genericDrawItems, draw next/prev item related */ /* buttons and the corresponding mapboxes. */ { int buttonW = heightPer-1 + 2*NEXT_ITEM_ARROW_BUFFER; int s = tg->itemStart(tg, item); int e = tg->itemEnd(tg, item); boolean rButton = FALSE; boolean lButton = FALSE; /* Draw the actual triangles. These are always at the edge of the window. */ -if (s < winStart) +if (doButtons && (s < winStart)) { lButton = TRUE; hvGfxNextItemButton(hvg, insideX + NEXT_ITEM_ARROW_BUFFER, y, heightPer-1, heightPer-1, color, MG_WHITE, FALSE); } -if (e > winEnd) +if (doButtons && (e > winEnd)) { rButton = TRUE; hvGfxNextItemButton(hvg, insideX + insideWidth - NEXT_ITEM_ARROW_BUFFER - heightPer, y, heightPer-1, heightPer-1, color, MG_WHITE, TRUE); } + +if ((vis == tvFull) || (vis == tvPack)) + { + /* Make the button mapboxes. */ + if (lButton) + tg->nextPrevExon(tg, hvg, item, insideX, y, buttonW, heightPer, FALSE); + if (rButton) + tg->nextPrevExon(tg, hvg, item, insideX + insideWidth - buttonW, y, buttonW, heightPer, TRUE); + } + +boolean compat = exonNumberMapsCompatible(tg, vis); if (vis == tvPack) { 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); - tg->nextPrevExon(tg, hvg, item, insideX, y, buttonW, heightPer, FALSE); textX = insideX + buttonW; // continue on the right side of the left exon button w = x2-textX; } if (rButton) { - tg->nextPrevExon(tg, hvg, item, x2-buttonW, y, buttonW, heightPer, TRUE); 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); + 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 == tvFull) { int geneMapBoxX = insideX; int geneMapBoxW = insideWidth; - /* Draw the first gene mapbox, in the left margin. */ + /* Draw the first gene label mapbox, in the left margin. */ #ifndef IMAGEv2_NO_LEFTLABEL_ON_FULL int trackPastTabX = (withLeftLabels ? trackTabWidth : 0); #ifdef IMAGEv2_SHORT_MAPITEMS char *name = tg->itemName(tg, item); if (*name != '\0') tg->mapItem(tg, hvg, item, name, tg->mapItemName(tg, item), s, e, trackPastTabX, y, insideX - trackPastTabX, heightPer); #else///ndef IMAGEv2_SHORT_MAPITEMS tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, trackPastTabX, y, insideX - trackPastTabX, heightPer); #endif///ndef IMAGEv2_SHORT_MAPITEMS #endif///ndef IMAGEv2_NO_LEFTLABEL_ON_FULL - /* Make the button mapboxes. */ - if (lButton) - tg->nextPrevExon(tg, hvg, item, insideX, y, buttonW, heightPer, FALSE); - if (rButton) - tg->nextPrevExon(tg, hvg, item, insideX + insideWidth - buttonW, y, buttonW, heightPer, TRUE); /* Depending on which button mapboxes we drew, draw the remaining mapbox. */ - if (lButton && rButton) + if (lButton) { geneMapBoxX += buttonW; - geneMapBoxW -= 2 * buttonW; + geneMapBoxW -= buttonW; } - else if (lButton) + if (rButton) { - geneMapBoxX += buttonW; geneMapBoxW -= buttonW; } - else if (rButton) - geneMapBoxW -= buttonW; #ifdef IMAGEv2_SHORT_MAPITEMS if (x2 > 0) { geneMapBoxX = textX; geneMapBoxW = x2-geneMapBoxX; if (geneMapBoxW < 5) // Full with short labels but don't make tiny map items { geneMapBoxX -= (5 - geneMapBoxW)/2; geneMapBoxW = 5; } } #endif//def IMAGEv2_SHORT_MAPITEMS + if (compat) + { // draw labeled exon/intron maps with exon/intron numbers + linkedFeaturesItemExonMaps(tg, hvg, item, scale, y, heightPer, s, e, lButton, rButton, buttonW); + 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); + } + } + else + { tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, geneMapBoxX, y, geneMapBoxW, heightPer); } } +} + + static void genericDrawOverflowItem(struct track *tg, struct spaceNode *sn, struct hvGfx *hvg, int xOff, int yOff, int width, MgFont *font, Color color, double scale, int overflowRow, boolean firstOverflow) /* genericDrawItems logic for drawing an overflow row */ { /* change some state to paint it in the "overflow" row. */ enum trackVisibility origVis = tg->limitedVis; int origLineHeight = tg->lineHeight; int origHeightPer = tg->heightPer; tg->limitedVis = tvDense; tg->heightPer = tl.fontHeight; tg->lineHeight = tl.fontHeight+1; struct slList *item = sn->val; @@ -3218,31 +3390,33 @@ { hvGfxBox(hvg, textX - 1, y, nameWidth+1, tg->heightPer-1, color); hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, MG_WHITE, font, name); } else hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, labelColor, font, name); } } if (!tg->mapsSelf) { int w = x2-textX; /* Arrows? */ if (w > 0) { if (nextItemCompatible(tg)) - genericDrawNextItemStuff(tg, hvg, vis, item, x2, textX, y, tg->heightPer, color); + genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, textX, y, tg->heightPer, color, TRUE); + else if (exonNumberMapsCompatible(tg, vis)) + genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, textX, y, tg->heightPer, color, FALSE); else { tg->mapItem(tg, hvg, item, tg->itemName(tg, item), tg->mapItemName(tg, item), s, e, textX, y, w, tg->heightPer); } } } withIndividualLabels = TRUE; /* reset in case done with pgSnp */ } static int normalizeCount(struct preDrawElement *el, double countFactor, double minVal, double maxVal, double sumData, double sumSquares) /* Normalize statistics to be based on an integer number of valid bases. * Integer value is the smallest integer not less than countFactor. */ { @@ -3459,34 +3633,54 @@ #ifdef IMAGEv2_NO_LEFTLABEL_ON_FULL if (theImgBox != NULL && vis == tvFull) // dragScroll >1x has no bed full leftlabels, { // but in image labels like pack. char *name = tg->itemName(tg, item); int nameWidth = mgFontStringWidth(font, name); int textX = round((s - winStart)*scale) + xOff; textX -= (nameWidth + 2); if (textX >= insideX && nameWidth > 0) { x1 = textX; // extends the map item to cover this label Color itemNameColor = tg->itemNameColor(tg, item, hvg); hvGfxTextRight(hvg,textX,y,nameWidth,tg->heightPer,itemNameColor,font,name); } } #endif ///def IMAGEv2_NO_LEFTLABEL_ON_FULL - genericDrawNextItemStuff(tg, hvg, vis, item, x2, x1, y, tg->heightPer, color); + genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, x1, y, tg->heightPer, color); } #else//ifndef IMAGEv2_SHORT_MAPITEMS - genericDrawNextItemStuff(tg, hvg, vis, item, -1, -1, y, tg->heightPer, color); + { + // Convert start/end coordinates to pix + int s = tg->itemStart(tg, item); + int e = tg->itemEnd(tg, item); + int sClp = (s < winStart) ? winStart : s; + int eClp = (e > winEnd) ? winEnd : e; + int x1 = round((sClp - winStart)*scale) + xOff; + int x2 = round((eClp - winStart)*scale) + xOff; + genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, -1, y, tg->heightPer, color, TRUE); // was -1, -1, -1 + } + else if (exonNumberMapsCompatible(tg, vis)) + { + // Convert start/end coordinates to pix + int s = tg->itemStart(tg, item); + int e = tg->itemEnd(tg, item); + int sClp = (s < winStart) ? winStart : s; + int eClp = (e > winEnd) ? winEnd : e; + int x1 = round((sClp - winStart)*scale) + xOff; + int x2 = round((eClp - winStart)*scale) + xOff; + genericDrawNextItemStuff(tg, hvg, vis, item, scale, x2, x1, -1, y, tg->heightPer, color, FALSE); // was -1, -1, -1 + } #endif//ndef IMAGEv2_SHORT_MAPITEMS y += tg->lineHeight; } } } void genericDrawItems(struct track *tg, int seqStart, int seqEnd, struct hvGfx *hvg, int xOff, int yOff, int width, MgFont *font, Color color, enum trackVisibility vis) /* Draw generic item list. Features must be fixed height * and tg->drawItemAt has to be filled in. */ { if (tg->mapItem == NULL) tg->mapItem = genericMapItem; if (vis != tvDense && (! bedItemRgb(tg->tdb)) && baseColorCanDraw(tg)) @@ -12793,41 +12987,41 @@ else track->colorShades = shadesOfGray; } track->tdb = tdb; /* Handle remote database settings - just a JK experiment at the moment. */ track->remoteSqlHost = trackDbSetting(tdb, "sqlHost"); track->remoteSqlUser = trackDbSetting(tdb, "sqlUser"); track->remoteSqlPassword = trackDbSetting(tdb, "sqlPassword"); track->remoteSqlDatabase = trackDbSetting(tdb, "sqlDatabase"); track->remoteSqlTable = trackDbSetting(tdb, "sqlTable"); track->isRemoteSql = (track->remoteSqlHost != NULL && track->remoteSqlUser != NULL && track->remoteSqlDatabase != NULL && track->remoteSqlTable !=NULL); exonArrows = trackDbSetting(tdb, "exonArrows"); -nextItem = trackDbSetting(tdb, "nextItemButton"); /* default exonArrows to on, except for tracks in regulation/expression group */ if (exonArrows == NULL) { if (sameString(tdb->grp, "regulation")) exonArrows = "off"; else exonArrows = "on"; } track->exonArrows = sameString(exonArrows, "on"); track->nextExonButtonable = TRUE; +nextItem = trackDbSetting(tdb, "nextItemButton"); if (nextItem && sameString(nextItem, "off")) track->nextExonButtonable = FALSE; track->nextItemButtonable = track->nextExonButtonable; #ifndef GBROWSE char *iatName = trackDbSetting(tdb, "itemAttrTbl"); if (iatName != NULL) track->itemAttrTbl = itemAttrTblNew(iatName); #endif /* GBROWSE */ fillInFromType(track, tdb); return track; } struct hash *handlerHash; void registerTrackHandler(char *name, TrackHandler handler)