5cdeda3ceacbf093b325e8ebd48e17c4ac03e6c6 chmalee Thu Nov 12 16:29:01 2020 -0800 Quality of life improvements on VCF details page, put the info tag annotations in a collapsible table, and add a border to said table so it's easier to distinguish between different tags. Also fix a bug in INFO tabular tables where the trailing separator was causing extra table cells to print, refs #26451 diff --git src/hg/hgc/vcfClick.c src/hg/hgc/vcfClick.c index 1fb648f..a2064a2 100644 --- src/hg/hgc/vcfClick.c +++ src/hg/hgc/vcfClick.c @@ -89,137 +89,146 @@ printf("Filter: "NA"
\n"); else if (rec->filterCount == 1 && sameString(rec->filters[0], "PASS")) printf("Filter: PASS
\n"); else { printf("Filter failures: "); printf("\n"); struct vcfFile *vcff = rec->file; printKeysWithDescriptions(vcff, rec->filterCount, rec->filters, vcff->filterDefs, FALSE); printf("\n"); } } -static void printTabularHeaderRow(const struct vcfInfoDef *def) +static int printTabularHeaderRow(const struct vcfInfoDef *def) /* Parse the column header parts out of def->description and print as table header row; - * call this only when looksTabular returns TRUE. */ + * call this only when looksTabular returns TRUE. + * Returns the number of columns in the header */ { regmatch_t substrArr[PATH_LEN]; if (regexMatchSubstr(def->description, COL_DESC_REGEX, substrArr, ArraySize(substrArr))) { puts(""); // Make a copy of the part of def->description that matches the regex, // then chop by '|' and print out header column tags: int matchSize = substrArr[0].rm_eo - substrArr[0].rm_so; char copy[matchSize+1]; safencpy(copy, sizeof(copy), def->description + substrArr[0].rm_so, matchSize); // Turn '_' into ' ' so description words can wrap inside headers, saving some space subChar(copy, '_', ' '); char *words[PATH_LEN]; int descColCount = chopByChar(copy, '|', words, ArraySize(words)); int i; for (i = 0; i < descColCount; i++) printf("%s", words[i]); puts(""); + return descColCount; } else errAbort("printTabularHeaderRow: code bug, if looksTabular returns true then " "regex should work here"); + return -1; } -static void printTabularData(struct vcfInfoElement *el) +static void printTabularData(struct vcfInfoElement *el, int headerCount) /* Print a row for each value in el, separating columns by '|'. */ { int j; for (j = 0; j < el->count; j++) { puts(""); char *val = el->values[j].datString; if (!isEmpty(val)) { int len = strlen(val); char copy[len+1]; safencpy(copy, sizeof(copy), val, len); char *words[PATH_LEN]; - int colCount = chopByChar(copy, '|', words, ArraySize(words)); + chopByChar(copy, '|', words, ArraySize(words)); int k; - for (k = 0; k < colCount; k++) + // printTabularHeaderRow strips off (but still prints!) a trailing '|' + // because of the regex, so enforce that here too so the rows after + // the header don't get all out of whack + for (k = 0; k < headerCount; k++) printf("%s", words[k]); } puts(""); } } -static void vcfInfoDetails(struct vcfRecord *rec) +static void vcfInfoDetails(struct vcfRecord *rec, char *trackName) /* Expand info keys to descriptions, then print out keys and values. */ { if (rec->infoCount == 0) return; struct vcfFile *vcff = rec->file; -puts("INFO column annotations:
"); -puts(""); +puts("
"); // wrapper table for collapsible section +jsBeginCollapsibleSectionFontSize(cart, trackName, "infoFields", "INFO column annotations:", FALSE, "medium"); +puts("
\n"); int i; for (i = 0; i < rec->infoCount; i++) { struct vcfInfoElement *el = &(rec->infoElements[i]); const struct vcfInfoDef *def = vcfInfoDefForKey(vcff, el->key); printf("\n"); } puts("
%s:", el->key); int j; enum vcfInfoType type = def ? def->type : vcfInfoString; if (type == vcfInfoFlag && el->count == 0) printf("Yes"); // no values, so we can't call vcfPrintDatum... // However, if this is older VCF, type vcfInfoFlag might have a value. if (looksTabular(def, el)) { // Make a special display below printf("see below"); } else { for (j = 0; j < el->count; j++) { if (j > 0) printf(", "); if (el->missingData[j]) printf("."); else vcfPrintDatum(stdout, el->values[j], type); } } - if (def != NULL) + if (def != NULL && !looksTabular(def, el)) printf("  %s", def->description); else printf(""); printf("
"); +jsEndCollapsibleSection(); +puts(""); // close the wrapper around the collapsible section // Now show the tabular fields, if any for (i = 0; i < rec->infoCount; i++) { struct vcfInfoElement *el = &(rec->infoElements[i]); const struct vcfInfoDef *def = vcfInfoDefForKey(vcff, el->key); if (looksTabular(def, el)) { puts("
"); printf("%s: %s
\n", el->key, def->description); puts(""); - printTabularHeaderRow(def); - printTabularData(el); + int headerCount = printTabularHeaderRow(def); + printTabularData(el, headerCount); puts("
"); } } } static void vcfGenotypeTable(struct vcfRecord *rec, char *track, char **displayAls) /* 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; @@ -492,31 +501,31 @@ seqName, rec->chromStart, formName); printf("\n"); } } char leftBase = rec->alleles[0][0]; unsigned int vcfStart = vcfRecordTrimIndelLeftBase(rec); boolean showLeftBase = (rec->chromStart == vcfStart+1); (void)vcfRecordTrimAllelesRight(rec); char *displayAls[rec->alleleCount]; makeDisplayAlleles(rec, showLeftBase, leftBase, 20, TRUE, FALSE, displayAls); printPosOnChrom(seqName, rec->chromStart, rec->chromEnd, NULL, FALSE, rec->name); printf("Reference allele: %s
\n", displayAls[0]); vcfAltAlleleDetails(rec, displayAls); vcfQualDetails(rec); vcfFilterDetails(rec); -vcfInfoDetails(rec); +vcfInfoDetails(rec, tdb->track); pgSnpCodingDetail(rec); makeDisplayAlleles(rec, showLeftBase, leftBase, 5, FALSE, TRUE, displayAls); vcfGenotypesDetails(rec, tdb, displayAls); } void doVcfDetailsCore(struct trackDb *tdb, char *fileOrUrl, boolean isTabix) /* Show item details using fileOrUrl. */ { genericHeader(tdb, NULL); int start = cartInt(cart, "o"); int end = cartInt(cart, "t"); int vcfMaxErr = -1; struct vcfFile *vcff = NULL; /* protect against temporary network or parsing error */ struct errCatch *errCatch = errCatchNew();