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/lib/jsonWrite.c src/lib/jsonWrite.c index 1c8db7f2c23..5f271e5174e 100644 --- src/lib/jsonWrite.c +++ src/lib/jsonWrite.c @@ -231,30 +231,73 @@ void jsonWriteAppend(struct jsonWrite *jwA, char *var, struct jsonWrite *jwB) /* Append jwB's contents to jwA's. If jwB is non-NULL, it must be fully closed (no unclosed * list or object). If var is non-NULL, write it out as a tag before appending. * If both var and jwB are NULL, leave jwA unchanged. */ { if (jwB && jwB->stackIx) errAbort("jsonWriteAppend: second argument must be fully closed but its stackIx is %d not 0", jwB->stackIx); jsonWriteTag(jwA, var); if (jwB) dyStringAppendN(jwA->dy, jwB->dy->string, jwB->dy->stringSize); else if (var) dyStringAppend(jwA->dy, "null"); } +void jsonWriteJsonElement(struct jsonWrite *jw, char *var, struct jsonElement *el) +/* Write a jsonElement (parsed JSON) into jw. Handles all types recursively. */ +{ +switch (el->type) + { + case jsonString: + jsonWriteString(jw, var, el->val.jeString); + break; + case jsonNumber: + jsonWriteNumber(jw, var, el->val.jeNumber); + break; + case jsonDouble: + jsonWriteDouble(jw, var, el->val.jeDouble); + break; + case jsonBoolean: + jsonWriteBoolean(jw, var, el->val.jeBoolean); + break; + case jsonObject: + { + jsonWriteObjectStart(jw, var); + struct hashEl *hel, *helList = hashElListHash(el->val.jeHash); + for (hel = helList; hel != NULL; hel = hel->next) + jsonWriteJsonElement(jw, hel->name, hel->val); + hashElFreeList(&helList); + jsonWriteObjectEnd(jw); + break; + } + case jsonList: + { + jsonWriteListStart(jw, var); + struct slRef *ref; + for (ref = el->val.jeList; ref != NULL; ref = ref->next) + jsonWriteJsonElement(jw, NULL, ref->val); + jsonWriteListEnd(jw); + break; + } + case jsonNull: + jsonWriteTag(jw, var); + dyStringAppend(jw->dy, "null"); + break; + } +} + int jsonWritePopToLevel(struct jsonWrite *jw, uint level) /* Close out the objects and lists that are deeper than level, so we end up at level ready to * add new items. Return the level that we end up with, which may not be the same as level, * if level is deeper than the current stack. */ { int i; for (i = jw->stackIx; i > level; i--) { if (jw->objStack[i].isObject) jsonWriteObjectEnd(jw); else jsonWriteListEnd(jw); } return jw->stackIx; }