1e6071743e73bccbc7d56a9e38e93cc1daabccfd chmalee Tue Aug 22 11:59:42 2023 -0700 Make the INFO fields collapsible table have unique ids when there is more than one on an hgc page, refs #31987 diff --git src/hg/hgc/vcfClick.c src/hg/hgc/vcfClick.c index 7176d09..7614e1c 100644 --- src/hg/hgc/vcfClick.c +++ src/hg/hgc/vcfClick.c @@ -146,38 +146,40 @@ safencpy(copy, sizeof(copy), val, len); char *words[PATH_LEN]; chopByChar(copy, '|', words, ArraySize(words)); int 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("<TD class='withThinBorder'>%s</TD>", words[k]); } puts("</TR>"); } } -static void vcfInfoDetails(struct vcfRecord *rec, char *trackName) +static void vcfInfoDetails(struct vcfRecord *rec, char *trackName, int recordCount) /* Expand info keys to descriptions, then print out keys and values. */ { if (rec->infoCount == 0) return; struct vcfFile *vcff = rec->file; puts("<table>"); // wrapper table for collapsible section -jsBeginCollapsibleSectionFontSize(cart, trackName, "infoFields", "INFO column annotations:", FALSE, "medium"); +char infoId[32]; +safef(infoId, sizeof(infoId), "infoFields%d", recordCount); +jsBeginCollapsibleSectionFontSize(cart, trackName, infoId, "INFO column annotations:", FALSE, "medium"); puts("<TABLE class=\"stdTbl\">\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("<TR valign='top'><TD align=\"right\"><B>%s:</B></TD><TD>", 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)) { @@ -439,31 +441,31 @@ struct dyString *dy = dyStringNew(128); int i; for (i = 0; i < rec->alleleCount; i++) { dyStringClear(dy); if (showLeftBase) dyStringPrintf(dy, "(%c)", leftBase); abbreviateLongSeq(rec->alleles[i], endLength, showLength, dy); if (encodeHtml) displayAls[i] = htmlEncode(dy->string); else displayAls[i] = cloneString(dy->string); } } -static void vcfRecordDetails(struct trackDb *tdb, struct vcfRecord *rec) +static void vcfRecordDetails(struct trackDb *tdb, struct vcfRecord *rec, int recordCount) /* Display the contents of a single line of VCF, assumed to be from seqName * (using seqName instead of rec->chrom because rec->chrom might lack "chr"). */ { if (isNotEmpty(rec->name) && differentString(rec->name, ".")) printf("<B>Name:</B> %s<BR>\n", rec->name); // Add some special URL substitution variables for ExAC/GnomAD-style links struct slPair *substFields = slPairNew("ref", rec->alleles[0]); substFields->next = slPairNew("firstAlt", rec->alleles[1]); char posString[64]; safef(posString, sizeof posString, "%d", rec->chromStart+1); substFields->next->next = slPairNew("pos", posString); char *label = rec->name; if ((isEmpty(rec->name) || sameString(rec->name, ".")) && (startsWith("exac", tdb->track) || startsWith("gnomad", tdb->track))) { @@ -501,31 +503,31 @@ seqName, rec->chromStart, formName); printf("</TABLE></FORM>\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("<B>Reference allele:</B> %s<BR>\n", displayAls[0]); vcfAltAlleleDetails(rec, displayAls); vcfQualDetails(rec); vcfFilterDetails(rec); -vcfInfoDetails(rec, tdb->track); +vcfInfoDetails(rec, tdb->track, recordCount); 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(); @@ -537,33 +539,38 @@ vcff = vcfTabixFileAndIndexMayOpen(fileOrUrl, indexUrl, seqName, start, end, vcfMaxErr, -1); } else vcff = vcfFileMayOpen(fileOrUrl, seqName, start, end, vcfMaxErr, -1, TRUE); } errCatchEnd(errCatch); if (errCatch->gotError) { if (isNotEmpty(errCatch->message->string)) warn("%s", errCatch->message->string); } errCatchFree(&errCatch); if (vcff != NULL) { struct vcfRecord *rec; + // use the recordCount to make up unique id strings for html collapsible INFO section later + int recordCount = 0; for (rec = vcff->records; rec != NULL; rec = rec->next) + { if (rec->chromStart == start && rec->chromEnd == end) // in pgSnp mode, don't get name - vcfRecordDetails(tdb, rec); + vcfRecordDetails(tdb, rec, recordCount); + recordCount++; + } } else printf("Sorry, unable to open %s<BR>\n", fileOrUrl); printTrackHtml(tdb); } void doVcfTabixDetails(struct trackDb *tdb, char *item) /* Show details of an alignment from a VCF file compressed and indexed by tabix. */ { knetUdcInstall(); if (udcCacheTimeout() < 300) udcSetCacheTimeout(300); struct sqlConnection *conn = NULL;