b038f3dafce99493b0ca00305538c6a3dd6f041a jcasper Tue Sep 26 15:58:44 2023 -0700 Initial commit of track decorators, refs #30237 diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c index 686dd57..baa6a87 100644 --- src/hg/hgTracks/simpleTracks.c +++ src/hg/hgTracks/simpleTracks.c @@ -26,30 +26,32 @@ #include "wiggle.h" #include "lfs.h" #include "grp.h" #include "chromColors.h" #include "hgTracks.h" #include "subText.h" #include "cds.h" #include "mafTrack.h" #include "wigCommon.h" #include "hui.h" #include "imageV2.h" #include "bigBed.h" #include "htmshell.h" #include "kxTok.h" #include "hash.h" +#include "decorator.h" +#include "decoratorUi.h" #ifndef GBROWSE #include "encode.h" #include "expRatioTracks.h" #include "hapmapTrack.h" #include "retroGene.h" #include "switchGear.h" #include "variation.h" #include "wiki.h" #include "wormdna.h" #include "aliType.h" #include "agpGap.h" #include "cgh.h" #include "bactigPos.h" #include "genePred.h" @@ -456,30 +458,37 @@ } struct spaceSaver *ss = findSpaceSaver(tg, vis); if (ss) return ss->rowCount; // Falls thru here if a new visibility is needed, such as full changing to pack after limitVisibility. // This will usually be when it is the first window and it is requesting a new vis. } if (currentWindow != windows) // if not first window { errAbort("unexpected current window %lu, expected first window %lu", (unsigned long) currentWindow, (unsigned long) windows); } // If we get here, currentWindow should be first window i.e. windows var. +if (hasDecorators(tg)) +{ + packDecorators(tg); // be nice to avoid doing this for every pass through packCountRowsOverflow + // reason being we only ever pack decorations one way, which only depends on the decoration style + // (and user selections), but not the vis mode - vis just changes height. +} + struct slList *item; MgFont *font = tl.font; int extraWidth = tl.mWidth * 2; long long start, end; // TODO usually screen pixels, so plain long should work. struct window *w; struct track *tgSave = tg; // find out which items are the same, // and belong on the same line with (probably one) label: struct hash *sameItem = newHash(10); // TODO estimate this as log base 2 # items in all windows //struct hash *itemDone = newHash(10); // TODO I do not need this yet in this version if no dupes. for(w=windows,tg=tgSave; w; w=w->next,tg=tg->nextWindow) { // Have to init these here @@ -614,56 +623,123 @@ sin->done = TRUE; foundWork = TRUE; lastWin = sin->window; int winOffset = sin->window->insideX - fullInsideX; struct slList *item = sin->item; int baseStart = tg->itemStart(tg, item); int baseEnd = tg->itemEnd(tg, item); struct window *w = sin->window; // convert bases to pixels if (baseStart <= w->winStart) start = 0; else start = round((double)(baseStart - w->winStart)*scale); + +int expandPack = 0; // deactivating item boundary extention for now - working on that next + if (hasDecorators(tg) && expandPack) + // extend item start with overlays to put the label in the right place + { + int overlayStart; + char decoratorKey[2048]; + safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName); + if (decoratorGroupGetOverlayExtent(tg, decoratorKey, &overlayStart, NULL)) + { + if (overlayStart < start) + start = overlayStart; + } + } + if (!tg->drawLabelInBox && !tg->drawName && withLabels && (!noLabel)) + // add item label { leftLabelSize = mgFontStringWidth(font, tg->itemName(tg, item)) + extraWidth; if (start - leftLabelSize + winOffset < 0) leftLabelSize = start + winOffset; start -= leftLabelSize; } + if (hasDecorators(tg) && expandPack) + // expand item start for packing if other decorations push it past the left label edge, for packing + { + int decoratedStart; + char decoratorKey[2048]; + safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName); + if (decoratorGroupGetExtent(tg, decoratorKey, &decoratedStart, NULL)) + { + if (decoratedStart < start) + start = decoratedStart; + } + } + if (baseEnd >= w->winEnd) end = w->insideWidth; else end = round((baseEnd - w->winStart)*scale); + + if (hasDecorators(tg) && expandPack) + // extend item ends via overlay here + { + int overlayEnd; + char decoratorKey[2048]; + safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName); + if (decoratorGroupGetOverlayExtent(tg, decoratorKey, NULL, &overlayEnd)) + { + if (overlayEnd > end) + end = overlayEnd; + } + } + if (tg->itemRightPixels && withLabels) + // draw anything that's supposed to be off the right edge (like right labels) { end += tg->itemRightPixels(tg, item); if (end > w->insideWidth) end = w->insideWidth; } + if (hasDecorators(tg) && expandPack) + // and extend item end via general decoration extension here, for packing + { + int decoratedEnd; + char decoratorKey[2048]; + safef(decoratorKey, sizeof decoratorKey, "%s:%d-%d:%s", chrom, baseStart, baseEnd, mapItemName); + if (decoratorGroupGetExtent(tg, decoratorKey, NULL, &decoratedEnd)) + { + if (decoratedEnd > end) + end = decoratedEnd; + } + } + + AllocVar(range); range->start = start + winOffset; range->end = end + winOffset; slAddHead(&rangeList, range); rangeWidth += (range->end - range->start); + range->height = 1; + if (hasDecorators(tg)) + { + char itemString[2048]; + struct linkedFeatures *lf = (struct linkedFeatures*) item; + safef(itemString, sizeof(itemString), "%s:%d-%d:%s", chrom, baseStart, + baseEnd, lf->name); + range->height += decoratorGroupHeight(tg, itemString); + } AllocVar(node); node->val = item; node->parentSs = sin->ss; node->noLabel = noLabel; slAddHead(&nodeList, node); noLabel = TRUE; // turns off labels for all following windows - for now. } if (!foundWork) continue; @@ -4330,153 +4406,207 @@ hvGfxSetClip(hvgSide, leftLabelX, yOff, insideWidth, tg->height); char nameBuff[SMALLBUF]; safef(nameBuff, sizeof(nameBuff), "Last Row: %d", overflowCount); mgFontStringWidth(font, nameBuff); hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->lineHeight, color, font, nameBuff); hvGfxUnclip(hvgSide); hvGfxSetClip(hvgSide, insideX, yOff, insideWidth, tg->height); } /* restore state */ tg->limitedVis = origVis; tg->heightPer = origHeightPer; tg->lineHeight = origLineHeight; } -static void genericDrawItem(struct track *tg, struct spaceNode *sn, - struct hvGfx *hvg, int xOff, int yOff, int width, + +static void genericDrawItemLabel(struct track *tg, struct spaceNode *sn, + struct hvGfx *hvg, int xOff, int y, int width, MgFont *font, Color color, Color labelColor, enum trackVisibility vis, double scale, boolean withLeftLabels) -/* draw one non-overflow item */ { struct slList *item = sn->val; int s = tg->itemStart(tg, item); -int e = tg->itemEnd(tg, item); +//int e = tg->itemEnd(tg, item); int sClp = (s < winStart) ? winStart : s; -int eClp = (e > winEnd) ? winEnd : e; +//int eClp = (e > winEnd) ? winEnd : e; int x1 = round((sClp - winStart)*scale) + xOff; -int x2 = round((eClp - winStart)*scale) + xOff; +//int x2 = round((eClp - winStart)*scale) + xOff; int textX = x1; if (tg->drawLabelInBox) withLeftLabels = FALSE; if (tg->itemNameColor != NULL) { color = tg->itemNameColor(tg, item, hvg); labelColor = color; if (withLeftLabels && isTooLightForTextOnWhite(hvg, color)) labelColor = somewhatDarkerColor(hvg, color); } -// get y offset for item in pack mode -int yRow = 0; -if (tg->ss && tg->ss->rowSizes != NULL) - { - int i; - for (i=0; i < sn->row; i++) - yRow += tg->ss->rowSizes[i]; - int itemHeight = tg->itemHeight(tg, item); - yRow += (tg->ss->rowSizes[sn->row] - itemHeight + 1); - tg->heightPer = itemHeight; - } -else - yRow = tg->lineHeight * sn->row; -int y = yOff + yRow; - -tg->drawItemAt(tg, item, hvg, xOff, y, scale, font, color, vis); - -// GALT non-proportional track like gtexGene -handleNonPropDrawItemAt(tg, sn, item, hvg, xOff, y, scale, font, color, vis); - /* pgSnpDrawAt may change withIndividualLabels between items */ boolean withLabels = (withLeftLabels && withIndividualLabels && ((vis == tvPack) || (vis == tvFull && isTypeBedLike(tg))) && (!sn->noLabel) && !tg->drawName); if (withLabels) { char *name = tg->itemName(tg, item); int nameWidth = mgFontStringWidth(font, name); int dotWidth = tl.nWidth/2; boolean snapLeft = FALSE; boolean drawNameInverted = highlightItem(tg, item); textX -= nameWidth + dotWidth; - snapLeft = (textX < fullInsideX); snapLeft |= (vis == tvFull && isTypeBedLike(tg)); /* Special tweak for expRatio in pack mode: force all labels * left to prevent only a subset from being placed right: */ snapLeft |= (startsWith("expRatio", tg->tdb->type)); #ifdef IMAGEv2_NO_LEFTLABEL_ON_FULL if (theImgBox == NULL && snapLeft) #else///ifndef IMAGEv2_NO_LEFTLABEL_ON_FULL if (snapLeft) /* Snap label to the left. */ #endif ///ndef IMAGEv2_NO_LEFTLABEL_ON_FULL { textX = leftLabelX; assert(hvgSide != NULL); + int prevX, prevY, prevWidth, prevHeight; + hvGfxGetClip(hvgSide, &prevX, &prevY, &prevWidth, &prevHeight); hvGfxUnclip(hvgSide); - hvGfxSetClip(hvgSide, leftLabelX, yOff, fullInsideX - leftLabelX, tg->height); + hvGfxSetClip(hvgSide, leftLabelX, y, fullInsideX - leftLabelX, tg->lineHeight); if(drawNameInverted) { int boxStart = leftLabelX + leftLabelWidth - 2 - nameWidth; hvGfxBox(hvgSide, boxStart, y, nameWidth+1, tg->heightPer - 1, color); hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->heightPer, MG_WHITE, font, name); } else hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->heightPer, labelColor, font, name); hvGfxUnclip(hvgSide); - hvGfxSetClip(hvgSide, insideX, yOff, insideWidth, tg->height); + hvGfxSetClip(hvgSide, prevX, prevY, prevWidth, prevHeight); } else { // shift clipping to allow drawing label to left of currentWindow int pdfSlop=nameWidth/5; + int prevX, prevY, prevWidth, prevHeight; + hvGfxGetClip(hvgSide, &prevX, &prevY, &prevWidth, &prevHeight); hvGfxUnclip(hvg); hvGfxSetClip(hvg, textX-1-pdfSlop, y, nameWidth+1+pdfSlop, tg->heightPer); if(drawNameInverted) { hvGfxBox(hvg, textX - 1, y, nameWidth+1, tg->heightPer-1, color); hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, MG_WHITE, font, name); } else // usual labeling hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, labelColor, font, name); hvGfxUnclip(hvg); - hvGfxSetClip(hvg, insideX, yOff, insideWidth, tg->height); + hvGfxSetClip(hvgSide, prevX, prevY, prevWidth, prevHeight); } } +withIndividualLabels = TRUE; /* reset in case done with pgSnp */ +} + +static void genericItemMapAndArrows(struct track *tg, struct spaceNode *sn, + struct hvGfx *hvg, int xOff, int y, int width, + MgFont *font, Color color, Color labelColor, enum trackVisibility vis, + double scale, boolean withLeftLabels) +{ +struct slList *item = sn->val; +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; +int textX = x1; + + if (!tg->mapsSelf) { int w = x2-textX; /* Arrows? */ if (w > 0) { if (nextItemCompatible(tg)) 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 void genericDrawItem(struct track *tg, struct spaceNode *sn, + struct hvGfx *hvg, int xOff, int yOff, int width, + MgFont *font, Color color, Color labelColor, enum trackVisibility vis, + double scale, boolean withLeftLabels) +/* draw one non-overflow item */ +{ +struct slList *item = sn->val; +//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; +//int textX = x1; + +if (tg->drawLabelInBox) + withLeftLabels = FALSE; + +if (tg->itemNameColor != NULL) + { + color = tg->itemNameColor(tg, item, hvg); + labelColor = color; + if (withLeftLabels && isTooLightForTextOnWhite(hvg, color)) + labelColor = somewhatDarkerColor(hvg, color); + } + +// get y offset for item in pack mode +int yRow = 0; +if (tg->ss && tg->ss->rowSizes != NULL) + { + int i; + for (i=0; i < sn->row; i++) + yRow += tg->ss->rowSizes[i]; + int itemHeight = tg->itemHeight(tg, item); + yRow += (tg->ss->rowSizes[sn->row] - itemHeight + 1); + tg->heightPer = itemHeight; + } +else + yRow = tg->lineHeight * sn->row; +int y = yOff + yRow; + +tg->drawItemAt(tg, item, hvg, xOff, y, scale, font, color, vis); + +// GALT non-proportional track like gtexGene +handleNonPropDrawItemAt(tg, sn, item, hvg, xOff, y, scale, font, color, vis); + +tg->drawItemLabel(tg, sn, hvg, xOff, y, width, font, color, labelColor, vis, scale, withLeftLabels); + +// do mapping and arrows + +tg->doItemMapAndArrows(tg, sn, hvg, xOff, y, width, font, color, labelColor, vis, scale, withLeftLabels); } 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. */ { bits32 validCount = ceil(countFactor); double normFactor = (double)validCount/countFactor; el->count = validCount; el->min = minVal; el->max = maxVal; el->sumData = sumData * normFactor; el->sumSquares = sumSquares * normFactor; @@ -5074,40 +5204,47 @@ return; struct linkedFeatures *lf = item; char *newItemName = (isEmpty(lf->mouseOver)) ? itemName: lf->mouseOver; // copied from genericMapItem char *directUrl = trackDbSetting(tg->tdb, "directUrl"); boolean withHgsid = (trackDbSetting(tg->tdb, "hgsid") != NULL); char *trackName = tg->track; if (tg->originalTrack != NULL) trackName = tg->originalTrack; mapBoxHgcOrHgGene(hvg, start, end, x, y, width, height, trackName, mapItemName, newItemName, directUrl, withHgsid, NULL); } +int linkedFeaturesFixedTotalHeightNoOverflow(struct track *tg, enum trackVisibility vis) +{ +return tgFixedTotalHeightOptionalOverflow(tg,vis, tl.fontHeight+1, tl.fontHeight, FALSE); +} + + void linkedFeaturesMethods(struct track *tg) /* Fill in track methods for linked features. */ { tg->freeItems = linkedFeaturesFreeItems; tg->drawItems = linkedFeaturesDraw; tg->drawItemAt = linkedFeaturesDrawAt; tg->itemName = linkedFeaturesName; tg->mapItemName = linkedFeaturesName; tg->mapItem = linkedFeaturesMapItem; -tg->totalHeight = tgFixedTotalHeightNoOverflow; +//tg->totalHeight = tgFixedTotalHeightNoOverflow; +tg->totalHeight = linkedFeaturesFixedTotalHeightNoOverflow; tg->itemHeight = tgFixedItemHeight; tg->itemStart = linkedFeaturesItemStart; tg->itemEnd = linkedFeaturesItemEnd; tg->itemNameColor = linkedFeaturesNameColor; tg->nextPrevExon = linkedFeaturesNextPrevItem; tg->nextPrevItem = linkedFeaturesLabelNextPrevItem; if (trackDbSettingClosestToHomeOn(tg->tdb, "linkIdInName")) { tg->mapItemName = linkedFeaturesNameField1; tg->itemName = linkedFeaturesNameNotField1; } } int linkedFeaturesSeriesItemStart(struct track *tg, void *item) @@ -14099,30 +14236,33 @@ track->nextPrevExon = NULL; } void fillInFromType(struct track *track, struct trackDb *tdb) /* Fill in various function pointers in track from type field of tdb. */ { char *typeLine = tdb->type, *words[8], *type; int wordCount; if (typeLine == NULL) return; wordCount = chopLine(cloneString(typeLine), words); if (wordCount <= 0) return; type = words[0]; +track->drawItemLabel = genericDrawItemLabel; +track->doItemMapAndArrows = genericItemMapAndArrows; + #ifndef GBROWSE if (sameWord(type, "bed")) { complexBedMethods(track, tdb, FALSE, wordCount, words); /* bed.h includes genePred.h so should be able to use these trackDb settings. */ if (trackDbSetting(track->tdb, GENEPRED_CLASS_TBL) !=NULL) track->itemColor = genePredItemClassColor; // FIXME: as long as registerTrackHandler doesn't accept wildcards, // this probably needs to stay here (it's in the wrong function) if (startsWith("pubs", track->track) && stringIn("Marker", track->track)) pubsMarkerMethods(track); if (startsWith("pubs", track->track) && stringIn("Blat", track->track)) pubsBlatMethods(track);