ddb85ced5e8b6127a233b5cda5fcb1fbe2260578 max Wed Mar 25 04:22:06 2026 -0700 Add detailsScript trackDb mechanism for JS visualizations on bigBed details pages Changing based on feedback from Jonathan, Chris and Brian after group discussion. Refactored existing Claude-generated code, moving functions into libraries. This is the first use of ES6 modules in the kent js code. In 2026, this should be acceptable? New trackDb syntax: detailsScript.<plotType>.<fieldName> <jsonConfig> The C code (bigBedClick.c) collects these settings, exports field values as JSON (bedDetails object), and dynamically imports hgc.<plotType>.js as an ES6 module. Fields used by detailsScript are shown in the HTML table with empty values, filled by JavaScript. Includes hgc.histogram.js module for drawing SVG bar chart histograms from logfmt-encoded data (space-separated key=value pairs). Applied to both the trexplorer and webstr tracks in the strVar supertrack. Also adds jsonWriteJsonElement() helper to jsonWrite.c for writing parsed jsonElement trees into a jsonWrite stream. max, refs #36652 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c index ba853ae81d5..017d99936ed 100644 --- src/hg/hgc/hgc.c +++ src/hg/hgc/hgc.c @@ -1784,51 +1784,75 @@ void printFieldLabel(char *entry) /* print the field label, the first column in the table, as a <td>. Allow a * longer description after a |-char, as some fields are not easy to * understand. */ { printFieldLabelInner(entry, NULL); } void printFieldLabelWithId(char *entry, char *fieldName) /* Like printFieldLabel but adds id="bfld_<fieldName>" to the <tr> element, * so JavaScript can find the row by field name. */ { printFieldLabelInner(entry, fieldName); } +static struct slName *detailsScriptFieldNames(struct trackDb *tdb) +/* Return list of bigBed field names used by detailsScript.* trackDb settings. + * These fields are rendered by JavaScript, so their values should not be printed in the HTML table. + * See also bigBedClick.c which parses the same settings to build JSON and load JS modules. */ +{ +struct slName *dsSettings = trackDbLocalSettingsWildMatch(tdb, DETAILS_SCRIPT_PREFIX); +struct slName *fieldNames = NULL; +struct slName *setting; +for (setting = dsSettings; setting != NULL; setting = setting->next) + { + char *dot1 = strchr(setting->name, '.'); + if (dot1) + { + char *dot2 = strchr(dot1 + 1, '.'); + if (dot2) + slNameAddHead(&fieldNames, dot2 + 1); + } + } +slFreeList(&dsSettings); +return fieldNames; +} + #define TDB_STATICTABLE_SETTING "extraDetailsTable" #define TDB_STATICTABLE_SETTING_2 "detailsStaticTable" int extraFieldsPrintAs(struct trackDb *tdb,struct sqlResult *sr,char **fields,int fieldCount, struct asObject *as) // Any extra bed or bigBed fields (defined in as and occurring after N in bed N + types. // sr may be null for bigBeds. // Returns number of extra fields actually printed. { // We are trying to print extra fields so we need to figure out how many fields to skip int start = extraFieldsStart(tdb, fieldCount, as); struct asColumn *col = as->columnList; char *urlsStr = trackDbSettingClosestToHomeOrDefault(tdb, "urls", NULL); struct hash* fieldToUrl = hashFromString(urlsStr); boolean skipEmptyFields = trackDbSettingOn(tdb, "skipEmptyFields"); // make list of fields to skip char *skipFieldsStr = trackDbSetting(tdb, "skipFields"); struct slName *skipIds = NULL; if (skipFieldsStr) skipIds = slNameListFromComma(skipFieldsStr); +struct slName *dsScriptFields = detailsScriptFieldNames(tdb); + // make list of fields that are separated from other fields char *sepFieldsStr = trackDbSetting(tdb, "sepFields"); struct slName *sepFields = NULL; if (sepFieldsStr) sepFields = slNameListFromComma(sepFieldsStr); // make list of fields that we want to substitute // this setting has format description|URLorFilePath, with the stuff before the pipe optional char *extraDetailsTableName = NULL, *extraDetails = cloneString(trackDbSetting(tdb, TDB_STATICTABLE_SETTING)); if (extraDetails && strchr(extraDetails,'|')) { extraDetailsTableName = extraDetails; extraDetails = strchr(extraDetails,'|'); *extraDetails++ = 0; } @@ -1934,31 +1958,34 @@ // split this table to separate current row from the previous one, if the trackDb option is set if (sepFields && slNameInList(sepFields, fieldName)) printf("</tr></table>\n<p>\n<table class='bedExtraTbl'>"); // field description char *entry; if (sameString(fieldName, "cdsStartStat") && sameString("enum('none','unk','incmpl','cmpl')", col->comment)) entry = "Status of CDS start annotation (none, unknown, incomplete, or complete)"; else if (sameString(fieldName, "cdsEndStat") && sameString("enum('none','unk','incmpl','cmpl')", col->comment)) entry = "Status of CDS end annotation (none, unknown, incomplete, or complete)"; else entry = col->comment; printFieldLabelWithId(entry, fieldName); - if (col->isList || col->isArray || col->lowType->stringy || asTypesIsInt(col->lowType->type)) + // detailsScript fields: print empty cell, JavaScript will fill it + if (dsScriptFields && slNameInList(dsScriptFields, fieldName)) + printf("<td></td></tr>\n"); + else if (col->isList || col->isArray || col->lowType->stringy || asTypesIsInt(col->lowType->type)) printIdOrLinks(col, fieldToUrl, tdb, fields[ix]); else if (asTypesIsFloating(col->lowType->type)) { double valDouble = strtod(fields[ix],NULL); if (errno == 0 && valDouble != 0) printf("<td>%g</td></tr>\n", valDouble); else printf("<td>%s</td></tr>\n", fields[ix]); // decided not to print error } else printf("<td class='bedExtraTblVal'>%s</td></tr>\n", fields[ix]); printCount++; } if (skipIds) slFreeList(skipIds);