b33425c626414803bbddc702caecc256c5022277 max Fri Mar 20 09:10:59 2026 -0700 defining detailsHistogram tdb statements and an example histogram drawing code for it, used in the trexplorer track, refs #37273 diff --git src/hg/hgc/bigBedClick.c src/hg/hgc/bigBedClick.c index 2e0e62feeed..23c972f03f3 100644 --- src/hg/hgc/bigBedClick.c +++ src/hg/hgc/bigBedClick.c @@ -4,30 +4,31 @@ * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "wiggle.h" #include "cart.h" #include "hgc.h" #include "hCommon.h" #include "hgColors.h" #include "bigBed.h" #include "hui.h" #include "subText.h" #include "web.h" #include "chromAlias.h" #include "quickLift.h" #include "hgConfig.h" +#include "jsHelper.h" static void bigGenePredLinks(char *track, char *item) /* output links to genePred driven sequence dumps */ { printf("<H3>Links to sequence:</H3>\n"); printf("<UL>\n"); puts("<LI>\n"); hgcAnchorSomewhere("htcTranslatedPredMRna", item, "translate", seqName); printf("Translated Protein</A> from genomic DNA\n"); puts("</LI>\n"); puts("<LI>\n"); hgcAnchorSomewhere("htcGeneMrna", item, track, seqName); printf("Predicted mRNA</A> \n"); puts("</LI>\n"); @@ -279,31 +280,31 @@ // skip an optional '#' on the first field name if (i == 0 && startsWith("#", name)) name = skipBeyondDelimit(name, '#'); if (startsWith("_", name) && !(startsWith("_json", name)) && !(startsWith("json", name))) detailsTabPrintSpecial(name, val, extraFields); else if (slNameInList(tblFieldNames, name)) { userTbl = (struct embeddedTbl *)hashFindVal(fieldsToEmbeddedTbl, name); userTbl->encodedTbl = val; printEmbeddedTable(tdb, userTbl, tableLabelsDy); } else { - printFieldLabel(name); + printFieldLabelWithId(name, name); printf("<td>%s</td></tr>\n", val); } printCount++; } printf("</table>\n"); dyStringPrintf(tableLabelsDy, "];\n"); jsInline(dyStringCannibalize(&tableLabelsDy)); return printCount; } struct slPair *parseDetailsTablUrls(struct trackDb *tdb) /* Parse detailsUrls setting string into an slPair list of {offset column name, fileOrUrl} */ { char *detailsUrlsStr = trackDbSetting(tdb, "detailsUrls"); if (!detailsUrlsStr) @@ -527,30 +528,117 @@ } if (isCustomTrack(tdb->track)) { time_t timep = bbiUpdateTime(bbi); printBbiUpdateTime(&timep); } char *motifPwmTable = trackDbSetting(tdb, "motifPwmTable"); if (motifPwmTable) { struct dnaSeq *seq = hDnaFromSeq(database, bed->chrom, bed->chromStart, bed->chromEnd, dnaLower); if (bed->strand[0] == '-') reverseComplement(seq->dna, seq->size); struct dnaMotif *motif = loadDnaMotif(bed->name, motifPwmTable); motifHitSection(seq, motif); } + + // detailsJs: load JavaScript files and export selected field data as JSON + char *detailsJs = trackDbSetting(tdb, "detailsJs"); + if (detailsJs) + { + // Include each comma-separated JS file + char *jsFiles = cloneString(detailsJs); + char *words[64]; + int jsFileCount = chopCommas(jsFiles, words); + int ji; + for (ji = 0; ji < jsFileCount; ji++) + { + char *jsFile = trimSpaces(words[ji]); + if (isNotEmpty(jsFile)) + jsIncludeFile(jsFile, NULL); + } + + // Build the bedDetails JSON object + struct dyString *ds = dyStringNew(1024); + dyStringPrintf(ds, "var bedDetails = {\"track\":\"%s\",\"chrom\":\"%s\"," + "\"start\":%d,\"end\":%d", + tdb->track, chrom, bed->chromStart, bed->chromEnd); + + // Export requested fields + char *detailsJsFieldsStr = trackDbSetting(tdb, "detailsJsFields"); + if (detailsJsFieldsStr && extraFieldPairs) + { + dyStringAppend(ds, ",\"fields\":{"); + char *fieldsCopy = cloneString(detailsJsFieldsStr); + char *fieldNames[256]; + int nFields = chopCommas(fieldsCopy, fieldNames); + boolean first = TRUE; + int fi; + for (fi = 0; fi < nFields; fi++) + { + char *fn = trimSpaces(fieldNames[fi]); + char *fv = slPairFindVal(extraFieldPairs, fn); + if (fv == NULL) + fv = ""; + if (!first) + dyStringAppendC(ds, ','); + dyStringPrintf(ds, "\"%s\":", fn); + dyStringAppendC(ds, '"'); + // JSON-escape the value + char *c; + for (c = fv; *c; c++) + { + if (*c == '"') + dyStringAppend(ds, "\\\""); + else if (*c == '\\') + dyStringAppend(ds, "\\\\"); + else if (*c == '\n') + dyStringAppend(ds, "\\n"); + else + dyStringAppendC(ds, *c); + } + dyStringAppendC(ds, '"'); + first = FALSE; + } + dyStringAppendC(ds, '}'); + } + + // Include detailsJsArgs if present + char *detailsJsArgs = trackDbSetting(tdb, "detailsJsArgs"); + if (detailsJsArgs) + dyStringPrintf(ds, ",\"args\":%s", detailsJsArgs); + + dyStringAppend(ds, "};\n"); + + // Call the default function derived from each JS filename (strip .js) + // e.g. barChart.js -> barChart(bedDetails) + for (ji = 0; ji < jsFileCount; ji++) + { + char *jsFile = trimSpaces(words[ji]); + if (isEmpty(jsFile)) + continue; + char funcName[256]; + safecpy(funcName, sizeof(funcName), jsFile); + // strip .js extension + char *dot = strrchr(funcName, '.'); + if (dot) + *dot = '\0'; + dyStringPrintf(ds, "$(document).ready(function() { %s(bedDetails); });\n", funcName); + } + + jsInline(dyStringCannibalize(&ds)); + } } if (!found) { printf("No item %s starting at %d\n", emptyForNull(item), start); } lmCleanup(&lm); bbiFileClose(&bbi); } void genericBigBedClick(struct sqlConnection *conn, struct trackDb *tdb, char *item, int start, int end, int bedSize) /* Handle click in generic bigBed track. */ { char *fileName = bbiNameFromSettingOrTable(tdb, conn, tdb->table); bigBedClick(fileName, tdb, item, start, end, bedSize);