42d91822ec103cfcc74e9f69b39d3ad8d886c278 angie Wed Mar 18 15:25:15 2020 -0700 Add a new trackDb setting hapClusterMethod and corresponding UI option for drawing haplotypes in VCF file order instead of clustering. refs #25197 Applied the new setting to mm10 strainSNPs and increased its default height so that strain labels appear in the left label area. diff --git src/hg/hgTracks/vcfTrack.c src/hg/hgTracks/vcfTrack.c index e384526..09192cd 100644 --- src/hg/hgTracks/vcfTrack.c +++ src/hg/hgTracks/vcfTrack.c @@ -1084,30 +1084,160 @@ colorMode); // Draw as much of the tree as can fit in the left label area: int extraPixel = (colorMode == altOnlyMode) ? 1 : 0; int hapHeight = tg->height- CLIP_PAD - 2*extraPixel; struct yFromNodeHelper yHelper = {0, NULL, NULL}; initYFromNodeHelper(&yHelper, yOff+extraPixel, hapHeight, gtHapCount, gtHapOrder, vcff->genotypeCount); struct titleHelper titleHelper = { NULL, 0, 0, 0, 0, NULL, NULL }; initTitleHelper(&titleHelper, tg->track, startIx, centerIx, endIx, nRecords, vcff); char *treeAngle = cartOrTdbString(cart, tg->tdb, VCF_HAP_TREEANGLE_VAR, VCF_DEFAULT_HAP_TREEANGLE); boolean drawRectangle = sameString(treeAngle, VCF_HAP_TREEANGLE_RECTANGLE); drawTreeInLabelArea(ht, hvg, yOff+extraPixel, hapHeight+CLIP_PAD, &yHelper, &titleHelper, drawRectangle); } +static void drawSampleLabels(struct vcfFile *vcff, boolean isAllDiploid, int yStart, int height, + unsigned short *gtHapOrder, int gtHapCount, MgFont *font, Color color, + char *track) +/* Draw sample names as left labels. */ +{ +if (isAllDiploid) + { + double pxPerGt = (double)height / vcff->genotypeCount; + if (pxPerGt < tl.fontHeight + 1) + warn("track %s: drawSampleLabels called with insufficient height", track); + int gtIx; + for (gtIx = 0; gtIx < vcff->genotypeCount; gtIx++) + { + int y = gtIx * pxPerGt; + hvGfxTextRight(hvgSide, leftLabelX, y+yStart, leftLabelWidth-1, (int)pxPerGt, + color, font, vcff->genotypeIds[gtIx]); + } + } +else + { + double pxPerHt = (double)height / gtHapCount; + if (pxPerHt < tl.fontHeight + 1) + warn("track %s: drawSampleLabels called with insufficient height", track); + int orderIx; + for (orderIx = 0; orderIx < gtHapCount; orderIx++) + { + int gtHapIx = gtHapOrder[orderIx]; + int gtIx = (gtHapIx >> 1); + int y = gtIx * pxPerHt; + hvGfxTextRight(hvgSide, leftLabelX, y+yStart, leftLabelWidth-1, (int)pxPerHt, + color, font, vcff->genotypeIds[gtIx]); + } + } +} + +static void drawSampleTitles(struct vcfFile *vcff, int yStart, int height, + unsigned short *gtHapOrder, int gtHapCount, char *track) +/* Draw mouseover labels / titles with the samples that are drawn at each pixel y offset */ +{ +double hapPerPx = (double)gtHapCount / height; +int labelEnd = leftLabelX + leftLabelWidth; +struct dyString *dy = dyStringNew(0); +int y; +for (y = 0; y < height; y++) + { + dyStringClear(dy); + int gtHapStart = y * hapPerPx; + int gtHapEnd = (y + 1) * hapPerPx; + if (gtHapEnd == gtHapStart) + gtHapEnd++; + char *lastSample = NULL; + int gtHapIx; + for (gtHapIx = gtHapStart; gtHapIx < gtHapEnd; gtHapIx++) + { + int gtIx = (gtHapOrder[gtHapIx] >> 1); + char *sample = vcff->genotypeIds[gtIx]; + if (!lastSample || differentString(sample, lastSample)) + { + if (isNotEmpty(dy->string)) + dyStringAppend(dy, ", "); + dyStringAppend(dy, sample); + lastSample = sample; + } + } + imgTrackAddMapItem(curImgTrack, TITLE_BUT_NO_LINK, dy->string, + leftLabelX, y+yStart, labelEnd, y+yStart+1, track); + } +} + +static void vcfGtHapFileOrderDraw(struct track *tg, int seqStart, int seqEnd, + struct hvGfx *hvg, int xOff, int yOff, int width, + MgFont *font, Color color, enum trackVisibility vis) +/* Draw rows in the same fashion as vcfHapClusterDraw, but instead of clustering, use the + * order in which samples appear in the VCF file. */ +{ +struct vcfFile *vcff = tg->extraUiData; +if (vcff->records == NULL) + return; +undefYellow = hvGfxFindRgb(hvg, &undefinedYellowColor); +enum hapColorMode colorMode = getColorMode(tg->tdb); +pushWarnHandler(ignoreEm); +struct vcfRecord *rec; +for (rec = vcff->records; rec != NULL; rec = rec->next) + vcfParseGenotypes(rec); +popWarnHandler(); +int ploidy = 2; // Assuming diploid genomes here, no XXY, tetraploid etc. +int gtCount = vcff->genotypeCount; +boolean isAllDiploid = TRUE; +unsigned short *gtHapOrder = needMem(gtCount * ploidy * sizeof(unsigned short)); +int orderIx = 0; +int gtIx; +// Determine the number of chromosome rows; for chrX, can be mix of diploid and haploid. +for (gtIx=0; gtIx < gtCount; gtIx++) + { + int gtHapIx = (gtIx << 1); + gtHapOrder[orderIx] = gtHapIx; + orderIx++; + for (rec = vcff->records; rec != NULL; rec = rec->next) + { + struct vcfGenotype *gt = &(rec->genotypes[gtIx]); + if (!gt->isHaploid) + { + gtHapOrder[orderIx] = gtHapIx+1; + orderIx++; + break; + } + else + isAllDiploid = FALSE; + } + } +int gtHapCount = orderIx; +for (rec = vcff->records; rec != NULL; rec = rec->next) + drawOneRec(rec, gtHapOrder, gtHapCount, tg, hvg, xOff, yOff, width, FALSE, FALSE, + colorMode); +// If height is sufficient, draw sample names as left labels; otherwise make mouseover titles +// with sample names for each pixel y offset. +int extraPixel = (colorMode == altOnlyMode) ? 1 : 0; +int hapHeight = tg->height - CLIP_PAD - 2*extraPixel; +int minHeightForLabels; +if (isAllDiploid) + minHeightForLabels = vcff->genotypeCount * (tl.fontHeight + 1); +else + minHeightForLabels = gtHapCount * (tl.fontHeight + 1); +if (hapHeight >= minHeightForLabels) + drawSampleLabels(vcff, isAllDiploid, yOff+extraPixel, hapHeight, gtHapOrder, gtHapCount, + font, color, tg->track); +else + drawSampleTitles(vcff, yOff+extraPixel, hapHeight, gtHapOrder, gtHapCount, tg->track); +} + static int vcfHapClusterTotalHeight(struct track *tg, enum trackVisibility vis) /* Return height of haplotype graph (2 * #samples * lineHeight); * 2 because we're assuming diploid genomes here, no XXY, tetraploid etc. */ { const struct vcfFile *vcff = tg->extraUiData; if (vcff->records == NULL) return 0; int ploidy = sameString(chromName, "chrY") ? 1 : 2; int simpleHeight = ploidy * vcff->genotypeCount * tg->lineHeight; int defaultHeight = min(simpleHeight, VCF_DEFAULT_HAP_HEIGHT); char *tdbHeight = trackDbSettingOrDefault(tg->tdb, VCF_HAP_HEIGHT_VAR, NULL); if (isNotEmpty(tdbHeight)) defaultHeight = atoi(tdbHeight); int cartHeight = cartOrTdbInt(cart, tg->tdb, VCF_HAP_HEIGHT_VAR, defaultHeight); if (tg->visibility == tvSquish) @@ -1118,30 +1248,34 @@ return tg->height; } static char *vcfHapClusterTrackName(struct track *tg, void *item) /* If someone asks for itemName/mapItemName, just send name of track like wiggle. */ { return tg->track; } static void vcfHapClusterOverloadMethods(struct track *tg, struct vcfFile *vcff) /* If we confirm at load time that we can draw a haplotype graph, use * this to overwrite the methods for the rest of execution: */ { tg->heightPer = (tg->visibility == tvSquish) ? (tl.fontHeight/4) : (tl.fontHeight / 2); tg->lineHeight = tg->heightPer + 1; +char *hapMethod = cartOrTdbString(cart, tg->tdb, VCF_HAP_METHOD_VAR, VCF_DEFAULT_HAP_METHOD); +if (sameString(hapMethod, VCF_HAP_METHOD_FILE_ORDER)) + tg->drawItems = vcfGtHapFileOrderDraw; +else tg->drawItems = vcfHapClusterDraw; tg->totalHeight = vcfHapClusterTotalHeight; tg->itemHeight = tgFixedItemHeight; tg->itemName = vcfHapClusterTrackName; tg->mapItemName = vcfHapClusterTrackName; tg->itemStart = tgItemNoStart; tg->itemEnd = tgItemNoEnd; tg->mapsSelf = TRUE; tg->extraUiData = vcff; } static void indelTweakMapItem(struct track *tg, struct hvGfx *hvg, void *item, char *itemName, char *mapItemName, int start, int end, int x, int y, int width, int height) /* Pass the original vcf chromStart to pgSnpMapItem, so if we have trimmed an identical * first base from item's alleles and start, we will still pass the correct start to hgc. */