abd7278ac7167ede325d8c144a35ea59a0798766 chmalee Wed Jun 17 03:55:47 2026 -0700 Add a right click option to change color or background highlight color of individual track items. Only works for bed like items, refs #37778 diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c index 3203fa63434..6cfd1da99a2 100644 --- src/hg/hgTracks/simpleTracks.c +++ src/hg/hgTracks/simpleTracks.c @@ -3,30 +3,31 @@ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ /* NOTE: This code was imported from hgTracks.c 1.1469, May 19 2008, * so a lot of revision history has been obscured. To see code history * from before this file was created, run this: * cvs ann -r 1.1469 hgTracks.c | less +Gp */ #include "common.h" #include "spaceSaver.h" #include "portable.h" #include "bed.h" #include "basicBed.h" +#include "htmlColor.h" #include "psl.h" #include "web.h" #include "hdb.h" #include "hgFind.h" #include "hCommon.h" #include "hgColors.h" #include "trackDb.h" #include "bedCart.h" #include "wiggle.h" #include "lfs.h" #include "grp.h" #include "chromColors.h" #include "hgTracks.h" #include "subText.h" #include "cds.h" @@ -275,30 +276,32 @@ * 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, 255}; struct rgbColor tanColor = {255, 240, 200, 255}; struct rgbColor guidelineColor = {220, 220, 255, 255}; struct rgbColor multiRegionAltColor = {235, 235, 255, 255}; struct rgbColor undefinedYellowColor = {240,240,180, 255}; Color shadesOfSea[10+1]; /* Ten sea shades. */ struct rgbColor darkSeaColor = {0, 60, 120, 255}; struct rgbColor lightSeaColor = {200, 220, 255, 255}; struct hash *hgFindMatches; /* The matches found by hgFind that should be highlighted. */ boolean hgFindMatchesShowHighlight; /* For use with pdf mode which suppresses label highlight */ +struct hash *itemColorHash; /* Per-item background highlight colors keyed by "track\titemName". */ + struct trackLayout tl; void initTl() /* Initialize layout around small font and a picture about 600 pixels * wide. */ { trackLayoutInit(&tl, cart); } static boolean isTooLightForTextOnWhite(struct hvGfx *hvg, Color color) /* Return TRUE if text in this color would probably be invisible on a white background. */ { struct rgbColor rgbColor = hvGfxColorIxToRgb(hvg, color); int colorTotal = rgbColor.r + 2*rgbColor.g + rgbColor.b; @@ -4131,30 +4134,80 @@ } } if ((vis == tvFull || vis == tvPack) && (intronGap && (qGap == 0) && (tGap >= intronGap))) { clippedBarbs(hvg, x1, midY, w, tl.barbHeight, tl.barbSpacing, lf->orientation, bColor, FALSE); } } } } /* Rule of thumb for displaying chain gaps: consider a valid double-sided * gap if target side is at most 5 times greater than query side. */ #define CHAIN_GAP_FACTOR 5 +struct itemColorSpec +/* A user-chosen color for a single item, set via the right-click "Color this item" menu. */ + { + Color color; /* The chosen color. */ + boolean wholeItem; /* TRUE to recolor the item glyph, FALSE for a background highlight. */ + }; + +static struct itemColorSpec *itemColorLookup(struct track *tg, void *item) +/* Return the user-chosen color spec for this item, or NULL. Matches on mapItemName, itemName, or + * genomic position ("pos:chrom:start-end"), the same identities the JS uses to build the record. + * Nameless items (e.g. bed3) have no usable name, so the position key identifies them. */ +{ +if (itemColorHash == NULL) + return NULL; +char key[2048]; +struct itemColorSpec *spec = NULL; +if (tg->mapItemName != NULL) + { + safef(key, sizeof key, "%s\t%s", tg->track, tg->mapItemName(tg, item)); + spec = hashFindVal(itemColorHash, key); + } +if (spec == NULL && tg->itemName != NULL) + { + safef(key, sizeof key, "%s\t%s", tg->track, tg->itemName(tg, item)); + spec = hashFindVal(itemColorHash, key); + } +if (spec == NULL && tg->itemStart != NULL && tg->itemEnd != NULL) + { + safef(key, sizeof key, "%s\tpos:%s:%d-%d", tg->track, chromName, + tg->itemStart(tg, item), tg->itemEnd(tg, item)); + spec = hashFindVal(itemColorHash, key); + } +return spec; +} + +boolean itemColorOverride(struct track *tg, void *item, Color *retColor, boolean *retWholeItem) +/* If the user set a per-item color for this item (via right-click), return TRUE and fill in the + * color and whether it recolors the whole item; otherwise return FALSE. Lets non-linkedFeatures + * draw routines (e.g. bedDrawSimpleAt) honor right-click item colors. */ +{ +struct itemColorSpec *spec = itemColorLookup(tg, item); +if (spec == NULL) + return FALSE; +if (retColor != NULL) + *retColor = spec->color; +if (retWholeItem != NULL) + *retWholeItem = spec->wholeItem; +return TRUE; +} + void linkedFeaturesDrawAt(struct track *tg, void *item, struct hvGfx *hvg, int xOff, int y, double scale, MgFont *font, Color color, enum trackVisibility vis) /* Draw a single simple bed item at position. */ { struct linkedFeatures *lf = item; struct simpleFeature *sf, *components; int heightPer = tg->heightPer; int x1,x2; int shortOff = heightPer/4; int shortHeight = heightPer - 2*shortOff; int tallStart, tallEnd, s, e, e2, s2; Color bColor; int intronGap = 0; boolean chainLines = ((vis != tvDense)&&(tg->subType == lfSubChain)); @@ -4199,30 +4252,44 @@ { drawOpt = baseColorDrawSetup(hvg, tg, lf, &qSeq, &qOffset, &psl); if (drawOpt > baseColorDrawOff) exonArrows = FALSE; } if ((tg->tdb != NULL) && (vis != tvDense)) intronGap = atoi(trackDbSettingOrDefault(tg->tdb, "intronGap", "0")); lfColors(tg, lf, hvg, &color, &bColor); if (vis == tvDense && trackDbSetting(tg->tdb, EXP_COLOR_DENSE)) color = saveColor; color = colorFromCart(tg, color); bColor = colorFromCart(tg, bColor); +// user-chosen per-item color (right-click "Color this item"): recolor the whole glyph or +// fall back to a background highlight, unless the item is already highlighted. +struct itemColorSpec *userColorSpec = itemColorLookup(tg, lf); +if (userColorSpec != NULL) + { + if (userColorSpec->wholeItem) + color = bColor = userColorSpec->color; + else if (lf->highlightColor == 0) + { + lf->highlightColor = userColorSpec->color; + lf->highlightMode = highlightBackground; + } + } + struct genePred *gp = NULL; if (startsWith("genePred", tg->tdb->type) || startsWith("bigGenePred", tg->tdb->type)) gp = (struct genePred *)(lf->original); boolean baseColorNeedsCodons = (drawOpt == baseColorDrawItemCodons || drawOpt == baseColorDrawDiffCodons || drawOpt == baseColorDrawGenomicCodons); if (psl && baseColorNeedsCodons) { boolean isXeno = ((tg->subType == lfSubXeno) || (tg->subType == lfSubChain) || startsWith("mrnaBla", tg->table)); int sizeMul = pslIsProtein(psl) ? 3 : 1; lf->codons = baseColorCodonsFromPsl(lf, psl, sizeMul, isXeno, maxShade, drawOpt, tg); } else if (drawOpt > baseColorDrawOff) @@ -4762,30 +4829,39 @@ int sClp = (s < winStart) ? winStart : s; int x1 = round((sClp - 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); } +// user-chosen per-item color (right-click "Color this item"): color the label to match the glyph +struct itemColorSpec *userColorSpec = itemColorLookup(tg, item); +if (userColorSpec != NULL && userColorSpec->wholeItem) + { + color = labelColor = userColorSpec->color; + if (withLeftLabels && isTooLightForTextOnWhite(hvg, color)) + labelColor = somewhatDarkerColor(hvg, color); + } + /* 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: */ @@ -16023,15 +16099,63 @@ char *matchLine = NULL; struct slName *nameList = NULL, *name = NULL; matchLine = cartOptionalString(cart, "hgFind.matches"); if(matchLine == NULL) return; nameList = slNameListFromString(matchLine,','); hgFindMatches = newHash(5); for(name = nameList; name != NULL; name = name->next) { hashAddInt(hgFindMatches, name->name, 1); } slFreeList(&nameList); hgFindMatchesShowHighlight = TRUE; // default to showing the highlight searched item label. } +void createItemColorHash() +/* Read the itemColors cart variable into a hash of per-item colors keyed by "track\titemName", + * keeping only records for the current database. The cart format is db#track#mode#itemName#hexColor + * records joined by '|', where mode is "item" (recolor the glyph) or "bg" (background highlight). + * The color is the last '#' field so that item names containing '#' are tolerated; item names + * containing '|' are not supported. The cart value is user-editable, so malformed records (bad + * color, missing fields) are skipped rather than aborting the image. */ +{ +char *itemColors = cartOptionalString(cart, "itemColors"); +if (isEmpty(itemColors)) + return; +struct slName *recordList = slNameListFromString(itemColors, '|'), *record; +for (record = recordList; record != NULL; record = record->next) + { + char *p = record->name; + char *db = cloneNextWordByDelimiter(&p, '#'); + char *track = cloneNextWordByDelimiter(&p, '#'); + char *mode = cloneNextWordByDelimiter(&p, '#'); + char *lastHash = (p != NULL) ? strrchr(p, '#') : NULL; + if (!isEmpty(db) && !isEmpty(track) && !isEmpty(mode) && lastHash != NULL + && sameString(db, database)) + { + *lastHash = '\0'; + char *itemName = p; + char *hex = lastHash + 1; + char colorSpec[16]; + safef(colorSpec, sizeof colorSpec, "#%s", hex); + unsigned rgb; + if (!isEmpty(itemName) && htmlColorForCode(colorSpec, &rgb)) + { + struct itemColorSpec *spec; + AllocVar(spec); + spec->color = bedColorToGfxColor(rgb); + spec->wholeItem = sameString(mode, "item"); + char key[2048]; + safef(key, sizeof key, "%s\t%s", track, itemName); + if (itemColorHash == NULL) + itemColorHash = newHash(0); + hashAdd(itemColorHash, key, spec); + } + } + freeMem(db); + freeMem(track); + freeMem(mode); + } +slFreeList(&recordList); +} +