dc1e0e76dbe49861bd0ebe8db64e27f587737794
max
  Mon Mar 30 15:40:03 2026 -0700
adding two more phased variants tracks, refs #37306

diff --git src/hg/hgc/vcfClick.c src/hg/hgc/vcfClick.c
index 4c6c13aa75e..6570da1f50b 100644
--- src/hg/hgc/vcfClick.c
+++ src/hg/hgc/vcfClick.c
@@ -213,70 +213,149 @@
     {
     struct vcfInfoElement *el = &(rec->infoElements[i]);
     const struct vcfInfoDef *def = vcfInfoDefForKey(vcff, el->key);
     if (looksTabular(def, el))
         {
         puts("<BR>");
         printf("<B>%s</B>: %s<BR>\n", el->key, def->description);
         puts("<TABLE class='stdTbl'>");
         int headerCount = printTabularHeaderRow(def);
         printTabularData(el, headerCount);
         puts("</TABLE>");
         }
     }
 }
 
-static void vcfGenotypeTable(struct vcfRecord *rec, char *track, char **displayAls)
+struct sampleMeta
+/* Metadata columns for one sample, loaded from sampleMetadataFile. */
+    {
+    char **values;      /* Array of column values */
+    };
+
+static void loadSampleMetadata(struct trackDb *tdb, struct hash **retHash,
+                                char ***retColNames, int *retColCount)
+/* Load sample metadata from file specified in trackDb setting sampleMetadataFile.
+ * File format: tab-separated, first line is header starting with #sample.
+ * Returns a hash mapping sample name -> sampleMeta struct, plus column names and count. */
+{
+*retHash = NULL;
+*retColNames = NULL;
+*retColCount = 0;
+char *fileName = trackDbSetting(tdb, VCF_SAMPLE_METADATA_FILE);
+if (fileName == NULL)
+    return;
+fileName = hReplaceGbdb(fileName);
+struct lineFile *lf = lineFileMayOpen(fileName, TRUE);
+if (lf == NULL)
+    return;
+char *line;
+int lineSize;
+// Read header line
+if (!lineFileNext(lf, &line, &lineSize))
+    {
+    lineFileClose(&lf);
+    return;
+    }
+// Strip leading # if present
+if (line[0] == '#')
+    line++;
+// Parse header columns
+int colCount = chopByChar(line, '\t', NULL, 0);
+char **allCols;
+AllocArray(allCols, colCount);
+chopByChar(line, '\t', allCols, colCount);
+// Column 0 is sample name; metadata columns start at 1
+int metaColCount = colCount - 1;
+if (metaColCount < 1)
+    {
+    lineFileClose(&lf);
+    return;
+    }
+char **colNames;
+AllocArray(colNames, metaColCount);
+int i;
+for (i = 0; i < metaColCount; i++)
+    colNames[i] = cloneString(allCols[i+1]);
+// Read data lines
+struct hash *hash = hashNew(0);
+while (lineFileNext(lf, &line, &lineSize))
+    {
+    char *row[colCount];
+    int fieldCount = chopByChar(line, '\t', row, colCount);
+    if (fieldCount < 2)
+        continue;
+    struct sampleMeta *sm;
+    AllocVar(sm);
+    AllocArray(sm->values, metaColCount);
+    for (i = 0; i < metaColCount && i + 1 < fieldCount; i++)
+        sm->values[i] = cloneString(row[i+1]);
+    hashAdd(hash, row[0], sm);
+    }
+lineFileClose(&lf);
+*retHash = hash;
+*retColNames = colNames;
+*retColCount = metaColCount;
+}
+
+static void vcfGenotypeTable(struct vcfRecord *rec, char *track, char **displayAls,
+                             struct trackDb *tdb)
 /* Put the table containing details about each genotype into a collapsible section. */
 {
 static struct dyString *tmp1 = NULL;
 if (tmp1 == NULL)
     tmp1 = dyStringNew(0);
 jsBeginCollapsibleSection(cart, track, "genotypes", "Detailed genotypes", FALSE);
 dyStringClear(tmp1);
 dyStringAppend(tmp1, rec->format);
 struct vcfFile *vcff = rec->file;
 enum vcfInfoType formatTypes[256];
 char *formatKeys[256];
 int formatCount = chopString(tmp1->string, ":", formatKeys, ArraySize(formatKeys));
 boolean firstInfo = TRUE;
 int i;
 for (i = 0;  i < formatCount;  i++)
     {
     if (sameString(formatKeys[i], vcfGtGenotype))
 	continue;
     if (firstInfo)
         {
         puts("<B>Genotype info key:</B><BR>");
         firstInfo = FALSE;
         }
     const struct vcfInfoDef *def = vcfInfoDefForGtKey(vcff, formatKeys[i]);
     char *desc = def ? def->description : "<em>not described in VCF header</em>";
     printf("&nbsp;&nbsp;<B>%s:</B> %s<BR>\n", formatKeys[i], desc);
     formatTypes[i] = def ? def->type : vcfInfoString;
     }
+// Load sample metadata if available
+struct hash *metaHash = NULL;
+char **metaColNames = NULL;
+int metaColCount = 0;
+loadSampleMetadata(tdb, &metaHash, &metaColNames, &metaColCount);
 hTableStart();
 boolean isDiploid = sameString(vcfHaplotypeOrSample(cart), "Haplotype");
 puts("<TR><TH>Sample ID</TH><TH>Genotype</TH>");
 if (isDiploid)
     puts("<TH>Phased?</TH>");
 for (i = 0;  i < formatCount;  i++)
     {
     if (sameString(formatKeys[i], vcfGtGenotype))
 	continue;
     printf("<TH>%s</TH>", formatKeys[i]);
     }
+for (i = 0; i < metaColCount; i++)
+    printf("<TH>%s</TH>", metaColNames[i]);
 puts("</TR>\n");
 for (i = 0;  i < vcff->genotypeCount;  i++)
     {
     struct vcfGenotype *gt = &(rec->genotypes[i]);
     char *hapA = ".", *hapB = ".";
     if (gt->hapIxA >= 0)
 	hapA = displayAls[(unsigned char)gt->hapIxA];
     if (gt->isHaploid)
 	hapB = "";
     else if (gt->hapIxB >= 0)
 	hapB = displayAls[(unsigned char)gt->hapIxB];
     char sep = gt->isHaploid ? ' ' : gt->isPhased ? '|' : '/';
     char *phasing = gt->isHaploid ? NA : gt->isPhased ? "Y" : "n";
     printf("<TR><TD>%s</TD><TD>%s%c%s</TD>", vcff->genotypeIds[i],
 	   hapA, sep, hapB);
@@ -289,30 +368,42 @@
 	    continue;
 	printf("<TD>");
 	struct vcfInfoElement *el = &(gt->infoElements[j]);
 	int k;
 	for (k = 0;  k < el->count;  k++)
 	    {
 	    if (k > 0)
 		printf(", ");
 	    if (el->missingData[k])
 		printf(".");
 	    else
 		vcfPrintDatum(stdout, el->values[k], formatTypes[j]);
 	    }
 	printf("</TD>");
 	}
+    // Print sample metadata columns
+    if (metaHash != NULL)
+        {
+        struct sampleMeta *sm = hashFindVal(metaHash, vcff->genotypeIds[i]);
+        for (j = 0; j < metaColCount; j++)
+            {
+            if (sm != NULL && sm->values[j] != NULL)
+                printf("<TD>%s</TD>", sm->values[j]);
+            else
+                printf("<TD></TD>");
+            }
+        }
     puts("</TR>");
     }
 hTableEnd();
 jsEndCollapsibleSection();
 }
 
 static void ignoreEm(char *format, va_list args)
 /* Ignore warnings from genotype parsing -- when there's one, there
  * are usually hundreds more just like it. */
 {
 }
 
 static void vcfGenotypesDetails(struct vcfRecord *rec, struct trackDb *tdb, char **displayAls)
 /* Print summary of allele and genotype frequency, plus collapsible section
  * with table of genotype details. */
@@ -381,31 +472,31 @@
 	{
 	boolean showHW = cartOrTdbBoolean(cart, tdb, VCF_SHOW_HW_VAR, FALSE);
 	if (showHW)
             {
             double altAf = (double)alCounts[1]/totalAlleles;
 	    printf("<B><A HREF=\"http://en.wikipedia.org/wiki/Hardy%%E2%%80%%93Weinberg_principle\" "
 		   "TARGET=_BLANK>Hardy-Weinberg equilibrium</A>:</B> "
 		   "P(%s/%s) = %.3f%%; P(%s/%s) = %.3f%%; P(%s/%s) = %.3f%%<BR>",
 		   displayAls[0], displayAls[0], 100*refAf*refAf,
 		   displayAls[0], displayAls[1], 100*2*refAf*altAf,
 		   displayAls[1], displayAls[1], 100*altAf*altAf);
             }
 	}
     }
 puts("<BR>");
-vcfGenotypeTable(rec, tdb->track, displayAls);
+vcfGenotypeTable(rec, tdb->track, displayAls, tdb);
 puts("</TABLE>");
 }
 
 static void pgSnpCodingDetail(struct vcfRecord *rec)
 /* Translate rec into pgSnp (with proper chrom name) and call Belinda's
  * coding effect predictor from pgSnp details. */
 {
 char *genePredTable = "knownGene";
 if (hTableExists(database, genePredTable))
     {
     struct pgSnp *pgs = pgSnpFromVcfRecord(rec);
     if (!sameString(rec->chrom, seqName))
 	// rec->chrom might be missing "chr" prefix:
 	pgs->chrom = seqName;
     printSeqCodDisplay(database, pgs, genePredTable);