0b780ebaab52c71cce8e8dc15c8540eb94e3a1bf chmalee Tue Aug 5 15:23:07 2025 -0700 Draw the barChart svgs on the client so the table can take advantage of the full screen width, refs #36124 diff --git src/hg/js/hgc.js src/hg/js/hgc.js index dcb18b56551..07cf4460bee 100644 --- src/hg/js/hgc.js +++ src/hg/js/hgc.js @@ -255,84 +255,190 @@ input = inputs[i]; if (!input.checked) { // pass hgsid and other variables on through continue; } if (input.name.endsWith("SetAllVis") || (input.getAttribute("data-default") === input.checked.toString())) { input.disabled = true; } else { input.value = input.value.toLowerCase(); } } }); } } +const svgNS = "http://www.w3.org/2000/svg"; +function getTextWidth(text) { + let hiddenSvg = document.createElementNS(svgNS, "svg"); + hiddenSvg.style.visibility = "hidden"; + hiddenSvg.style.position = "absolute"; + document.body.appendChild(hiddenSvg); + let textN = document.createElementNS(svgNS, "text"); + textN.setAttribute("x", 0); + textN.setAttribute("y", 0); + textN.textContent = text; + hiddenSvg.appendChild(textN); + let ret = textN.getComputedTextLength(); + document.body.removeChild(hiddenSvg); + return ret; +} -function initPage() { - if (typeof doHPRCTable !== "undefined") { - makeHPRCTable(); +function drawSvgTable(svg, data) { + // Given an id of an svg (which has already been mostly created), fill it in + // with the data in arr. data is an object with structure: + // {metricLabel, sampleLabel, values: [ + // {color: "", label: "", nValue: number, barValue: number}, + // {...}, + // ... + // ] + let parentTable = svg.parentNode; + while (parentTable && parentTable.nodeName !== "TABLE") { + parentTable = parentTable.parentNode; + } + let totalWidth = Math.round(parentTable.clientWidth * 0.95); + let padding = 5; + let hasSampleN = svg.getAttribute("hassamplen") === "true" || svg.getAttribute("hassamplen") === "TRUE"; + let labelWidth = 0; + let nWidth = 0; + for (let obj of data.values) { + labelWidth = Math.max(labelWidth, getTextWidth(obj.label)); + if (hasSampleN) { + if (typeof obj.nValue !== "undefined") { + nWidth = Math.max(nWidth, getTextWidth(obj.nValue)); + } + } + } + if (labelWidth > (totalWidth * 0.4)) { + labelWidth = totalWidth * 0.4; // clamp at 40% of max width + } + labelWidth += padding; // for padding on each + if (hasSampleN) { + nWidth += (2 * padding); } - if (typeof svgTable !== "undefined") { - // redraw the svg with appropriate widths for all columns - // swatchWidth and columnSpacer are taken from svgBarChart() in hgc/barChartClick.c - // they should probably be dynamically determined - var swatchWidth = 20.0; - var columnSpacer = 4.0; - var maxSampleWidth = 0.0; - // determine the size taken up by the sample names - $(".sampleLabel").each(function(s) { - if ((sampleLength = this.getComputedTextLength()) >= maxSampleWidth) { - maxSampleWidth = sampleLength; + // these values come from barChartClick.c: + let heightPer = 17; + let fontSize = 16; + let borderSize = 1; + let headerColor = "#d9e4f8"; + let swatchWidth = 15; + let swatchColWidth = swatchWidth + padding; + // find the total width leftover for the bar itself + // the barValue is specified as %5.3f in barChartClick.c, so max of 8 chars + let barWidth = totalWidth - (swatchColWidth + labelWidth + nWidth + getTextWidth("00000.000") + padding); + + let columnWidths = [swatchColWidth, labelWidth, nWidth, barWidth]; + + // now we can draw the header + let header = document.createElementNS(svgNS, "rect"); + header.id = "svgTableHeader"; + header.setAttribute("width", totalWidth); + header.setAttribute("height", 20); + header.setAttribute("fill", headerColor); + svg.appendChild(header); + let sampleColLabel = document.createElementNS(svgNS, "text"); + sampleColLabel.textContent = data.sampleLabel; + sampleColLabel.setAttribute("x", swatchColWidth); + sampleColLabel.setAttribute("y", heightPer - 1); + sampleColLabel.setAttribute("font-size", fontSize); + svg.appendChild(sampleColLabel); + + if (hasSampleN) { + let nColLabel = document.createElementNS(svgNS, "text"); + nColLabel.textContent = "N"; + nColLabel.setAttribute("x", swatchColWidth + labelWidth); + nColLabel.setAttribute("y", heightPer - 1); + nColLabel.setAttribute("font-size", fontSize); + svg.appendChild(nColLabel); } - }); - // determine the size taken up by the 'N' counts - var maxStatsWidth = 0.0; - $(".statsLabel").each(function(s) { - if ((statWidth = this.getComputedTextLength()) >= maxStatsWidth) { - maxStatsWidth = statWidth; + let metricColLabel = document.createElementNS(svgNS, "text"); + metricColLabel.textContent = data.metricLabel; + metricColLabel.setAttribute("x", swatchColWidth + labelWidth + nWidth); + metricColLabel.setAttribute("y", heightPer - 1); + metricColLabel.setAttribute("font-size", fontSize); + svg.appendChild(metricColLabel); + svg.setAttribute("width", totalWidth); + svg.setAttribute("height", (data.values.length * (heightPer + 1) + 20)); + + // finally we can draw each row of the table + let maxVal = data.values.reduce((a,b) => Math.max(a, b.barValue), -Infinity); + for (let i = 0; i < data.values.length; ++i) { + let textY = 20 + (i * (heightPer + 1)) + (heightPer / 2) + (fontSize / 3); + let swatch = document.createElementNS(svgNS, "rect"); + swatch.setAttribute("x", 0); + swatch.setAttribute("y", 20 + ((heightPer + 1) * i)); + swatch.setAttribute("width", swatchWidth); + swatch.setAttribute("fill", data.values[i].color); + swatch.setAttribute("height", heightPer); + + if (i & 1) { + let band = document.createElementNS(svgNS, "rect"); + band.setAttribute("x", swatchColWidth); + band.setAttribute("y", 20 + ((heightPer + 1) * i)); + band.setAttribute("fill", "#ffffff"); + band.setAttribute("width", labelWidth + nWidth); + band.setAttribute("height", heightPer); + svg.appendChild(band); } - }); - // the stat is right aligned so take into account it's width as well - statsRightOffset = swatchWidth + maxSampleWidth + (2 * columnSpacer) + maxStatsWidth; + let label = document.createElementNS(svgNS, "text"); + label.setAttribute("x", swatchColWidth); + label.setAttribute("y", textY); + label.setAttribute("font-size", fontSize); + label.textContent = data.values[i].label; - // The white band that separates every other row needs to be resized - $(".sampleBand").each(function(s) { - this.setAttribute("width", statsRightOffset - swatchWidth); - }); + if (typeof data.values[i].nValue !== "undefined") { + let optN = document.createElementNS(svgNS, "text"); + optN.setAttribute("x", swatchColWidth + labelWidth); + optN.setAttribute("y", textY); + optN.setAttribute("font-size", fontSize); + optN.textContent = data.values[i].nValue; + svg.appendChild(optN); + } - // now move the stat number - $(".statsLabel").each(function(s) { - this.setAttribute("x", statsRightOffset); - }); + let bar = document.createElementNS(svgNS, "rect"); + bar.setAttribute("x", swatchColWidth + labelWidth + nWidth); + bar.setAttribute("y", 20 + ((heightPer + 1) * i)); + let thisBarWidth = barWidth * data.values[i].barValue / maxVal; + bar.setAttribute("width", thisBarWidth); + bar.setAttribute("height", heightPer); + bar.setAttribute("fill", data.values[i].color); - // now shift the actual bars (plus value) over if necessary - $(".valueLabel").each(function(s) { - barName = "#bar" + s; - var barWidth = 0; - var newX = statsRightOffset + (2 * columnSpacer); - if ($(barName).length > 0) { - barWidth = parseInt($(barName)[0].getAttribute("width")); - $(barName)[0].setAttribute("x", newX); - this.setAttribute("x", newX + barWidth + 2 * columnSpacer); - } else { // the header label only - this.setAttribute("x", newX + barWidth); + let barVal = document.createElementNS(svgNS, "text"); + barVal.setAttribute("x", swatchColWidth + labelWidth + nWidth + thisBarWidth + padding); + barVal.setAttribute("y", textY); + barVal.setAttribute("font-size", fontSize); + barVal.textContent = data.values[i].barValue; + svg.appendChild(swatch); + svg.appendChild(label); + svg.appendChild(bar); + svg.appendChild(barVal); } - }); +} + + +function initPage() { + if (typeof doHPRCTable !== "undefined") { + makeHPRCTable(); + } + if (typeof svgTable !== "undefined") { + // redraw the svg with appropriate widths for all columns + // swatchWidth and columnSpacer are taken from svgBarChart() in hgc/barChartClick.c + // they should probably be dynamically determined + drawSvgTable(document.getElementById("svgBarChart"), barChartValues); } if (typeof _jsonHgcLabels !== "undefined") { var obj, o; for (obj in _jsonHgcLabels) { // build up the new table: var newTable = document.createElement("table"); var newRow = newTable.insertRow(); var newCell = newRow.insertCell(); var label = _jsonHgcLabels[obj].label; var data = _jsonHgcLabels[obj].data; var newText = document.createTextNode(label); newCell.appendChild(newText); newCell = newRow.insertCell(); newCell.appendChild(dataToTable(label, data)); // find the last details table and add a new table on: