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/js/detailsHistogram.js src/hg/js/detailsHistogram.js deleted file mode 100644 index 7044d99a075..00000000000 --- src/hg/js/detailsHistogram.js +++ /dev/null @@ -1,132 +0,0 @@ -// detailsHistogram.js - Draw SVG histograms on hgc details pages from bigBed field data. -// Called automatically via the detailsJs trackDb mechanism. -// Expects bedDetails object with: track, chrom, start, end, fields, args -// args.histograms is an array of {binsField, valuesField, title} - -function detailsHistogram(bedDetails) { - if (!bedDetails || !bedDetails.args || !bedDetails.args.histograms) - return; - if (!bedDetails.fields) - return; - - var histograms = bedDetails.args.histograms; - for (var ci = 0; ci < histograms.length; ci++) { - var hist = histograms[ci]; - var binsStr = bedDetails.fields[hist.binsField]; - var valuesStr = bedDetails.fields[hist.valuesField]; - if (!binsStr || !valuesStr) - continue; - - var bins = binsStr.split(","); - var values = valuesStr.split(",").map(Number); - if (bins.length !== values.length || bins.length === 0) - continue; - - var svg = buildHistogramSvg(bins, values, hist.title); - - // Find the bins row by id attribute set by hgc and replace its value cell - var binsRow = document.getElementById("bfld_" + hist.binsField); - var valuesRow = document.getElementById("bfld_" + hist.valuesField); - if (binsRow) { - var valCell = binsRow.cells[1]; - if (valCell) - valCell.innerHTML = svg; - // Update the label to the histogram title - var labelCell = binsRow.cells[0]; - if (labelCell) - labelCell.innerHTML = hist.title || "Distribution"; - } - // Remove the values row since data is now visualized - if (valuesRow) - valuesRow.remove(); - } -} - -function buildHistogramSvg(bins, values, title) { - var maxVal = Math.max.apply(null, values); - if (maxVal === 0) - return "No data"; - - // Chart dimensions - var barWidth = Math.max(14, Math.min(36, Math.floor(600 / bins.length))); - var gap = Math.max(1, Math.floor(barWidth / 8)); - var chartHeight = 160; - var labelHeight = 30; - var topPad = 5; - var leftPad = 50; // room for y-axis labels - var svgWidth = leftPad + bins.length * barWidth + 10; - var svgHeight = chartHeight + labelHeight + topPad; - - var lines = []; - lines.push(''); - - // Y-axis: a few tick marks - var ticks = calcHistTicks(maxVal); - for (var ti = 0; ti < ticks.length; ti++) { - var tickVal = ticks[ti]; - var y = topPad + chartHeight - (tickVal / maxVal) * chartHeight; - lines.push(''); - lines.push('' + formatHistNumber(tickVal) + ''); - // Subtle grid line - lines.push(''); - } - - // Bars - for (var i = 0; i < bins.length; i++) { - var barHeight = (values[i] / maxVal) * chartHeight; - var bx = leftPad + i * barWidth + gap; - var by = topPad + chartHeight - barHeight; - var bw = barWidth - 2 * gap; - - lines.push(''); - lines.push('' + bins[i] + ': ' + values[i] + ''); - lines.push(''); - - // X-axis label (skip some if too crowded) - var labelEvery = Math.ceil(bins.length / (svgWidth / 30)); - if (i % labelEvery === 0 || bins.length <= 30) { - lines.push('' + bins[i] + ''); - } - } - - // Axis lines - lines.push(''); - lines.push(''); - - // X-axis label - lines.push('Allele size (repeat copies)'); - - lines.push(''); - return lines.join("\n"); -} - -function calcHistTicks(maxVal) { - // Return 3-5 nice tick values for the y-axis - if (maxVal <= 5) - return [1, 2, 3, 4, 5].filter(function(v) { return v <= maxVal; }); - var magnitude = Math.pow(10, Math.floor(Math.log10(maxVal))); - var step = magnitude; - if (maxVal / step < 3) - step = magnitude / 2; - else if (maxVal / step > 6) - step = magnitude * 2; - var ticks = []; - for (var v = step; v <= maxVal; v += step) - ticks.push(Math.round(v)); - return ticks; -} - -function formatHistNumber(n) { - if (n >= 1000000) return (n / 1000000).toFixed(1) + "M"; - if (n >= 1000) return (n / 1000).toFixed(1) + "K"; - return "" + n; -}