e37f19d638c858f249b6b3e84586b6a2a3ca4487 braney Thu Sep 17 09:53:04 2026 -0700 hgTracks: color a lifted chain track by normScore, the way a native one is lfFromLiftedChain set grayIx to maxShade and took the raw chain score, so a quickLifted chain track asked to color by score drew every chain in the darkest shade, and the dense mode sort by score ordered them differently from the native track. chainLoadItems reads normScore out of the chain table and uses it for both. normScore is a column of the SQL chain table and is in no bigChain file, so only the SQL side can do this, which is also the only side the native loaders do it on. chainLoadRange throws the column away and struct chain has nowhere to keep it, so chainNormScoreHash reads it separately, once per source range, and only when the track is coloring by score. chainDbNormScoreAvailable only reads a trackDb setting, so the column count is checked before the row is indexed. Verified with a trackDb stanza lifting hg19 chainSelf onto hg38: the same 1846 items drew in 7 distinct colors before and 30 after. Note a hub cannot reach this path, because chain is not a hub track type, so it takes a stanza of ours carrying quickLiftUrl. Found in the v504 code review, refs #38349. refs #38249 diff --git src/hg/hgTracks/chainTrack.c src/hg/hgTracks/chainTrack.c index 5da706078b1..dd3ad391498 100644 --- src/hg/hgTracks/chainTrack.c +++ src/hg/hgTracks/chainTrack.c @@ -352,44 +352,55 @@ MgFont *font, Color color, enum trackVisibility vis) /* Draw chained features. This loads up the simple features from * the chainLink table, calls linkedFeaturesDraw, and then * frees the simple features again. */ { if (tg->items == NULL) /*Exit Early if nothing to do */ return; linkedFeaturesDraw(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis); } static struct linkedFeatures *lfFromLiftedChain(struct chain *chain, struct chain *lifted, - int qs, boolean doSnake) + int qs, boolean doSnake, double normScore) /* Build the drawing item for one chain that has been mapped onto the reference. The * blocks come from the lifted alignment, so unlike the native loaders this one does not - * leave the components for loadLinks to fetch. */ + * leave the components for loadLinks to fetch. normScore is the chain's normalized score + * when the track is coloring by score, or a negative number when it is not; the native + * loader uses that number for both the shade and the dense-mode sort, so this one has to + * as well. */ { struct linkedFeatures *lf; char buf[16]; AllocVar(lf); lf->start = lf->tallStart = lifted->tStart; lf->end = lf->tallEnd = lifted->tEnd; lf->qSize = lifted->qSize; +if (normScore >= 0) + { + lf->grayIx = hGrayInRange(normScore, 0, 100, maxShade+1); + lf->score = normScore; + } +else + { lf->grayIx = maxShade; lf->score = chain->score; + } lf->filterColor = -1; lf->orientation = (lifted->qStrand == '-') ? -1 : 1; int len = strlen(chain->qName) + 32; lf->name = needMem(len); if (!doSnake) // qs was worked out from the source chain, so print its strand, not the lifted one safef(lf->name, len, "%s %c %dk", chain->qName, chain->qStrand, qs/1000); else safef(lf->name, len, "%s", chain->qName); safef(buf, sizeof(buf), "%d", chain->id); lf->extra = cloneString(buf); struct simpleFeature *sfList = NULL, *sf; @@ -407,89 +418,132 @@ sf->qStart = lifted->qSize - b->qEnd; sf->qEnd = lifted->qSize - b->qStart; } else { sf->qStart = b->qStart; sf->qEnd = b->qEnd; } slAddHead(&sfList, sf); } slReverse(&sfList); lf->components = sfList; return lf; } +static struct hash *chainNormScoreHash(char *db, char *table, struct quickLiftRange *range) +/* chain id -> normScore, as a string, for the chains in one source range. chainLoadRange + * throws the column away and struct chain has nowhere to keep it, so read it separately + * rather than color a lifted chain track differently from a native one. Returns NULL + * when the table has no normScore column, which the trackDb setting only claims. */ +{ +struct hash *hash = NULL; +struct sqlConnection *conn = hAllocConn(db); +int rowOffset; +struct sqlResult *sr = hRangeQuery(conn, table, range->chrom, range->start, range->end, + NULL, &rowOffset); +if (sqlCountColumns(sr) > rowOffset + 11) + { + hash = newHash(8); + char **row; + while ((row = sqlNextRow(sr)) != NULL) + hashAdd(hash, row[rowOffset + 10], lmCloneString(hash->lm, row[rowOffset + 11])); + } +sqlFreeResult(&sr); +hFreeConn(&conn); +return hash; +} + static void quickLiftChainLoadItems(struct track *tg) /* Load chains out of the assembly this track came from and map them onto the reference. * A chain is an alignment between that assembly and some other species, so lifting one * composes two alignments and leaves the user with chains between the assembly on screen * and that species. */ { boolean doSnake = cartOrTdbBoolean(cart, tg->tdb, "doSnake" , FALSE); struct cartOptions *chainCart = (struct cartOptions *) tg->extraUiData; char *optionChrStr = cartOptionalStringClosestToHome(cart, tg->tdb, FALSE, "chromFilter"); if (isNotEmpty(optionChrStr) && sameWord("All", skipLeadingSpaces(optionChrStr))) optionChrStr = NULL; char *liftDb = trackDbSetting(tg->tdb, "quickLiftDb"); char *table = NULL; quickLiftResolveTable(tg->tdb, tg->table, &table, &liftDb); char *quickLiftFile = trackDbSetting(tg->tdb, "quickLiftUrl"); char *chainFile = NULL, *linkFile = NULL; if (tg->isBigBed) { chainFile = trackDbSetting(tg->tdb, "bigDataUrl"); // linkDataUrl when the track names its link file, which not every track does by the // name bigChainGetLinkFile would guess. This is the choice loadLinks makes. linkFile = trackDbSetting(tg->tdb, "linkDataUrl"); if (linkFile == NULL) linkFile = bigChainGetLinkFile(chainFile); } +// normScore lives in a column of the SQL chain table and nowhere in a bigChain file, so +// only the SQL side can color by it, which is also the only side the native loaders do. +boolean useNormScore = !tg->isBigBed && (chainCart->chainColor == chainColorScoreColors) && + chainDbNormScoreAvailable(tg->tdb); + struct hash *chainHash = newHash(8); struct hash *mapPsls = NULL; struct quickLiftRange *range, *rangeList = quickLiftSourceRanges(quickLiftFile, chromName, winStart, winEnd, chainHash); struct linkedFeatures *list = NULL; for (range = rangeList; range != NULL; range = range->next) { struct chain *chain, *chainList; + struct hash *normScores = NULL; if (tg->isBigBed) chainList = chainLoadIdRangeHub(NULL, chainFile, linkFile, range->chrom, range->start, range->end, -1); else + { chainList = chainLoadRange(liftDb, table, range->chrom, range->start, range->end); + if (useNormScore) + normScores = chainNormScoreHash(liftDb, table, range); + } for (chain = chainList; chain != NULL; chain = chain->next) { if (chain->blockList == NULL) continue; if ((optionChrStr != NULL) && !startsWith(optionChrStr, chain->qName)) continue; if ((chainCart->scoreFilter > 0) && (chain->score < chainCart->scoreFilter)) continue; int qs = (chain->qStrand == '-') ? chain->qSize - chain->qEnd : chain->qStart; struct chain *lifted = quickLiftChain(chainHash, &mapPsls, chain); if (lifted == NULL) continue; - slAddHead(&list, lfFromLiftedChain(chain, lifted, qs, doSnake)); + double normScore = -1; + if (normScores != NULL) + { + char id[32]; + safef(id, sizeof id, "%d", chain->id); + char *val = hashFindVal(normScores, id); + if (val != NULL) + normScore = sqlFloat(val); + } + slAddHead(&list, lfFromLiftedChain(chain, lifted, qs, doSnake, normScore)); } + hashFree(&normScores); } // put the list back into the order the chains were loaded in, which is what decides // between items the sort below sees as equal slReverse(&list); if (tg->visibility != tvDense) slSort(&list, linkedFeaturesCmpStart); else if (chainCart->chainColor == chainColorScoreColors) slSort(&list, chainCmpScore); tg->items = list; maybeLoadSnake(tg); } void bigChainLoadItems(struct track *tg)