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.. The C code (bigBedClick.c) collects these settings, exports field values as JSON (bedDetails object), and dynamically imports hgc..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) 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 . 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_" to the 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("\n

\n"); // 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("\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("\n", valDouble); else printf("\n", fields[ix]); // decided not to print error } else printf("\n", fields[ix]); printCount++; } if (skipIds) slFreeList(skipIds);
%g
%s
%s