ded41ea1968e30e2c248f65d1c9fec9793cb82fc max Thu Jun 18 11:31:32 2026 -0700 hgTracks: strand-direction arrows on the zoomed-in codon/CDS and UTR display When a coding transcript is zoomed in far enough to color its codons, the coding boxes carried no visible strand cue: only the introns showed the barbed "fishbone" arrows, and the strand was otherwise visible only on mouseover. This adds strand-direction chevrons to the codon and UTR display so the reading direction is visible at a glance. - At the amino-acid (codon-text) level, drawScaledBoxWithText() draws a chevron at each codon's left boundary (in the whitespace between the centered letters), in the box's contrasting color (white on the dark-blue codon shades), before the letter so the letter stays crisp. Points right on + strand, left on -; reverse-complement display is handled by hvGfxLine. The base-position ruler's codon display passes strand 0 and is unaffected. - When codons are colored but too small to label, baseColorDrawCdsArrows() distributes white chevrons across the coding part of each exon, on top of the boxes, at twice the standard barb spacing. - The UTRs (which never showed arrows) now get chevrons too, in the feature's contrasting color at a wider (3x) spacing as a cue that they are non-coding. The chevron is sized to the short UTR box, with a 1px floor so it still shows in squish mode. refs #37779 diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c index 8dcbc27bb98..66eb55effac 100644 --- src/hg/hgTracks/simpleTracks.c +++ src/hg/hgTracks/simpleTracks.c @@ -864,76 +864,91 @@ int lineHeight, int heightPer, boolean allowOverflow) /* Most fixed height track groups will use this to figure out the height * they use. */ { int height; if ((height = setupForWiggle(tg, vis)) != 0) return height; int rows; double maxHeight = maximumTrackHeight(tg); int itemCount = slCount(tg->items); int maxItemsToUseOverflow = maxItemsToOverflow(tg); tg->heightPer = heightPer; tg->lineHeight = lineHeight; +boolean isCompactPack = trackDbSettingOn(tg->tdb, "compactPack"); + /* Note that the maxCount variable passed to packCountRowsOverflow() is tied to the maximum height allowed for a track and influences decisions about when to squish, dense, or overflow a track. If doing overflow try to pack all the items into the maxHeight area or put all the overflow into the last row. If not doing overflow allow the track enough rows to go over the maxHeight (thus if the spaceSaver fills up the total height will be more than maxHeight). */ switch (vis) { case tvFull: if (isTypeBedLike(tg)) { if(allowOverflow && itemCount < maxItemsToUseOverflow) rows = packCountRowsOverflow(tg, floor(maxHeight/tg->lineHeight), FALSE, allowOverflow, vis); else rows = packCountRowsOverflow(tg, floor(maxHeight/tg->lineHeight)+1, FALSE, FALSE, vis); } else { rows = slCount(tg->items); } break; case tvPack: { + if (isCompactPack) + { + tg->heightPer = heightPer / 2; + tg->lineHeight = tg->heightPer; + } if(allowOverflow && itemCount < maxItemsToUseOverflow) rows = packCountRowsOverflow(tg, floor(maxHeight/tg->lineHeight), TRUE, allowOverflow, vis); else rows = packCountRowsOverflow(tg, floor(maxHeight/tg->lineHeight)+1, TRUE, FALSE, vis); if (tdbIsCompositeChild(tg->tdb)) { boolean doHideEmpties = compositeChildHideEmptySubtracks(cart, tg->tdb, NULL, NULL); if (isCenterLabelsPackOff(tg) && !doHideEmpties) if (rows == 0) rows = 1; // compact pack mode, shows just side label } break; } case tvSquish: + { + if (isCompactPack) + { + tg->heightPer = 3; + tg->lineHeight = 3; + } + else { tg->heightPer = heightPer/2; if ((tg->heightPer & 1) == 0) tg->heightPer -= 1; tg->lineHeight = tg->heightPer + 1; + } if(allowOverflow && itemCount < maxItemsToUseOverflow) rows = packCountRowsOverflow(tg, floor(maxHeight/tg->lineHeight), FALSE, allowOverflow, vis); else rows = packCountRowsOverflow(tg, floor(maxHeight/tg->lineHeight)+1, FALSE, FALSE, vis); break; } case tvDense: default: rows = 1; break; } tg->height = rows * tg->lineHeight; return tg->height; } @@ -3103,31 +3118,31 @@ safecpy(aaAbbr, sizeof(aaAbbr), "Ter"); aaName = "termination"; } else if (aaLetter == 'X') // error/partial codon: nothing to show aaAbbr[0] = '\0'; else { aaToAbbr(aaLetter, aaAbbr, sizeof(aaAbbr)); aaName = aaToName(aaLetter); } dyStringPrintf(codonDy, "Codons: c.%d-%d (p.%d-%d)
", cStart, cEnd, pStart, pStart+1); if (!isEmpty(aaAbbr)) { if (aaName != NULL) - dyStringPrintf(codonDy, "Amino acid: %s (%s)
", aaAbbr, aaName); + dyStringPrintf(codonDy, "Amino acid: %s - %s
", aaAbbr, aaName); else dyStringPrintf(codonDy, "Amino acid: %s
", aaAbbr); } } // if you change the text below, also change hgTracks:mouseOverToExon dyStringPrintf(codonDy, "Strand: %s    Exon Length: %dbp
Exon: %s %d of %d
%s", strandStr, e - s, 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; } } } } @@ -4476,30 +4491,33 @@ } } } if ((intronGap > 0) || chainLines) lfDrawSpecialGaps(lf, intronGap, chainLines, gapFactor, tg, hvg, xOff, y, scale, color, bColor, vis); if (vis != tvDense) { /* If highlighting differences between aligned sequence and genome when * zoomed way out, this must be done in a separate pass after exons are * drawn so that exons sharing the pixel don't overdraw differences. */ baseColorOverdrawDiff(tg, lf, hvg, xOff, y, scale, heightPer, qSeq, qOffset, psl, winStart, drawOpt); + /* When codons are colored, distribute strand arrows across the exons on top + * of the boxes (coding exons when too small to label, plus the UTRs). */ + baseColorDrawCdsArrows(tg, lf, hvg, xOff, y, scale, heightPer, winStart, drawOpt, color); if (psl && (indelShowQueryInsert || indelShowPolyA)) baseColorOverdrawQInsert(tg, lf, hvg, xOff, y, scale, heightPer, qSeq, qOffset, psl, font, winStart, drawOpt, indelShowQueryInsert, indelShowPolyA); } } static void lfSeriesDrawConnecter(struct linkedFeaturesSeries *lfs, struct hvGfx *hvg, int start, int end, double scale, int xOff, int midY, Color color, Color bColor, enum trackVisibility vis) /* Draw connection between two sets of linked features. */ { if (start != -1 && !lfs->noLine) { int x1 = round((double)((int)start-winStart)*scale) + xOff;