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;