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. */