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);