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/hgc/barChartClick.c src/hg/hgc/barChartClick.c
index eb27e10ce94..7cb1d54f446 100644
--- src/hg/hgc/barChartClick.c
+++ src/hg/hgc/barChartClick.c
@@ -15,30 +15,31 @@
 #include "trackHub.h"
 #include "memgfx.h"
 #include "hgColors.h"
 #include "fieldedTable.h"
 
 #include "barChartBed.h"
 #include "barChartCategory.h"
 #include "barChartData.h"
 #include "barChartSample.h"
 #include "barChartUi.h"
 #include "hgConfig.h"
 #include "facetedBar.h"
 #include "pipeline.h"
 #include "chromAlias.h"
 #include "jsHelper.h"
+#include "jsonWrite.h"
 
 #define EXTRA_FIELDS_SIZE 256
 
 struct barChartItemData
 /* Measured value for a sample and the sample category at a locus.
  * Used for barChart track details (boxplot) */
     {
     struct barChartItemData *next;  /* Next in singly linked list. */
     char *sample;	/* Sample identifier */
     char *category;     /* Sample category (from barChartSample table  or barChartSampleUrl file) */
     double value;	/* Measured value (e.g. expression level) */
     };
 
 struct barChartPlusRow
 /* Keep original row around for url processing */
@@ -383,54 +384,30 @@
 bool useOldFonts = cgiBoolean("oldFonts");
 
 /* Exec R in quiet mode, without reading/saving environment or workspace */
 char *pipeCmd[] = {"Rscript","--vanilla","--slave","hgcData/barChartBoxplot.R",
     item, units, colorFile, df, pngTn.forHtml,
     isEmpty(name2) ? "n/a" : name2, useOldFonts ? "1" : "0", NULL};
 struct pipeline *pl = pipelineOpen1(pipeCmd, pipelineWrite | pipelineNoAbort, "/dev/null", NULL, 0);
 int ret = pipelineWait(pl);
 
 if (ret == 0)
     printf("<img src = \"%s\" border=1><br>\n", pngTn.forHtml);
 else
     warn("Error creating boxplot from sample data with command: %s", pipelineDesc(pl));
 }
 
-static double estimateStringWidth(char *s)
-/* Get estimate of string width based on a memory font that is about the
- * same size as svg will be using.  After much research I don't think we
- * can get the size from the server, would have to be in Javascript to get
- * more precise */
-{
-MgFont *font = mgHelvetica14Font();
-return mgFontStringWidth(font, s);
-}
-
-static double longestLabelSize(struct barChartCategory *categList)
-/* Get estimate of longest label in pixels */
-{
-int longest = 0;
-struct barChartCategory *categ;
-for (categ = categList; categ != NULL; categ = categ->next)
-    {
-    int size = estimateStringWidth(categ->label);
-    if (size > longest)
-        longest = size;
-    }
-return longest * 1.02;
-}
-
 void deunderbarColumn(struct fieldedTable *ft, int fieldIx)
 /* Ununderbar all of a column inside table because space/underbar gets
  * so confusing */
 {
 struct fieldedRow *row;
 for (row = ft->rowList; row != NULL; row = row->next)
     replaceChar(row->row[fieldIx], '_', ' ');
 }
 
 static void svgBarChart(struct barChartBed *chart, struct trackDb *tdb, double maxVal, char *metric)
 /* Plot bar chart without quartiles or anything fancy just using SVG */
 {
 jsIncludeFile("hgc.js", NULL);
 puts("<p>");
 /* Load up input labels, color, and data */
@@ -446,99 +423,72 @@
 char *statsFile = hReplaceGbdb(trackDbSetting(tdb, "barChartStatsUrl"));
 struct hash *statsHash = NULL;
 int countStatIx = 0;
 double statsSize = 0.0;
 if (statsFile != NULL)
     {
     char *required[] = { "count", "total"};
     struct fieldedTable *ft = fieldedTableFromTabFile(
 	statsFile, statsFile, required, ArraySize(required));
     deunderbarColumn(ft, 0);
     statsHash = fieldedTableIndex(ft, ft->fields[0]);
     countStatIx = fieldedTableFindFieldIx(ft, "count");
     statsSize = 8*(fieldedTableMaxColChars(ft, countStatIx)+1);
     }
 
-/* Some constants that control layout */
-double heightPer=18.0;
-double totalWidth=1250.0;
-double borderSize = 1.0;
-
-double headerHeight = heightPer + 2*borderSize;
-double innerHeight=heightPer-borderSize;
-double labelWidth = longestLabelSize(categs) + 9;  // Add some because size is just estimate
-if (labelWidth > totalWidth/2) labelWidth = totalWidth/2;  // Don't let labels take up more than half
-double patchWidth = heightPer;
-double labelOffset = patchWidth + 2*borderSize;
-double statsOffset = labelOffset + labelWidth;
-double barOffset = statsOffset + statsSize;
-double statsRightOffset = barOffset - 9;
-double barNumLabelWidth = estimateStringWidth(" 1234.000");
-double barMaxWidth = totalWidth-barOffset -barNumLabelWidth ;
-double totalHeight = headerHeight + heightPer * categCount + borderSize;
-
 jsInline("var svgTable = true;\n");
-printf("<svg id='svgBarChart' width=\"%g\" height=\"%g\">\n", totalWidth, totalHeight);
 
-/* Draw header */
-printf("<rect width=\"%g\" height=\"%g\" style=\"fill:#%s\"/>\n", totalWidth, headerHeight, HG_COL_HEADER);
+struct jsonWrite *jw = jsonWriteNew();
+jsonWriteObjectStart(jw, NULL);
 char *sampleLabel = trackDbSettingOrDefault(tdb, "barChartLabel", "Sample");
-printf("<text class=\"sampleLabel\" x=\"%g\" y=\"%g\" font-size=\"%g\">%s</text>\n",
-    labelOffset, innerHeight-1, innerHeight-1, sampleLabel);
-if (statsSize > 0.0)
-    printf("<text class=\"statsLabel\" x=\"%g\" y=\"%g\" font-size=\"%g\" text-anchor=\"end\">%s</text>\n",
-	statsRightOffset, innerHeight-1, innerHeight-1, "N");
-printf("<text class=\"valueLabel\" x=\"%g\" y=\"%g\" font-size=\"%g\">%s %s</text>\n",
-    barOffset, innerHeight-1, innerHeight-1, metric, "Value");
-
-/* Set up clipping path for the pesky labels, which may be too long */
-printf("<clipPath id=\"labelClip\"><rect x=\"%g\" y=\"0\" width=\"%g\" height=\"%g\"/></clipPath>\n",
-    labelOffset, barOffset-labelOffset, totalHeight);
-
-double yPos = headerHeight;
+jsonWriteString(jw, "sampleLabel", sampleLabel);
+char metricLabel[512];
+safef(metricLabel, sizeof(metricLabel), "%s Value", metric);
+jsonWriteString(jw, "metricLabel", metricLabel);
+jsonWriteListStart(jw, "values");
 struct barChartCategory *categ;
-int i;
-for (i=0, categ=categs; i<categCount; ++i , categ=categ->next, yPos += heightPer)
+int i = 0;
+for (categ = categs; i < categCount && categ != NULL; ++i, categ = categ->next)
     {
     double score = chart->expScores[i];
-    double barWidth = 0;
-    if (maxVal > 0.0)
-	barWidth = barMaxWidth * score/maxVal;
     char *deunder = cloneString(categ->label);
     replaceChar(deunder, '_', ' ');
-    printf("<rect x=\"0\" y=\"%g\" width=\"15\" height=\"%g\" style=\"fill:#%06X\"/>\n",
-	yPos, innerHeight, categ->color);
-    printf("<rect id=\"bar%d\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" style=\"fill:#%06X\"/>\n",
-	i+1, barOffset, yPos, barWidth, innerHeight, categ->color);
-    if (i&1)  // every other time
-	printf("<rect class=\"sampleBand\" x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" style=\"fill:#%06X\"/>\n",
-	    labelOffset, yPos, labelWidth+statsSize, innerHeight, 0xFFFFFF);
-    printf("<text class=\"sampleLabel\" x=\"%g\" y=\"%g\" font-size=\"%g\" clip-path=\"url(#labelClip)\"\">%s</text>\n",
- 	labelOffset, yPos+innerHeight-1, innerHeight-1, deunder);
+    jsonWriteObjectStart(jw, NULL);
+    jsonWriteTag(jw, "color");
+    dyStringPrintf(jw->dy, "\"#%06X\"", categ->color);
+    jsonWriteString(jw, "label", deunder);
     if (statsSize > 0.0)
         {
         struct fieldedRow *fr = hashFindVal(statsHash, deunder);
         if (fr != NULL)
             {
-	    printf("<text class=\"statsLabel\" x=\"%g\" y=\"%g\" font-size=\"%g\" text-anchor=\"end\">%s</text>\n",
-		statsRightOffset, yPos+innerHeight-1, innerHeight-1, fr->row[countStatIx]);
+            jsonWriteString(jw, "nValue", fr->row[countStatIx]);
+            }
         }
+    else
+        {
+        jsonWriteDouble(jw, "nValue", -1);
         }
-    printf("<text class=\"valueLabel\" x=\"%g\" y=\"%g\" font-size=\"%g\">%5.3f</text>\n",
-	barOffset+barWidth+2, yPos+innerHeight-1, innerHeight-1, score);
+    jsonWriteTag(jw, "barValue");
+    dyStringPrintf(jw->dy, "%5.3f", score);
+    jsonWriteObjectEnd(jw);
     }
-printf("</svg>");
+jsonWriteListEnd(jw);
+jsonWriteObjectEnd(jw);
+jsInlineF("var barChartValues = %s\n", jw->dy->string);
+jsonWriteFree(&jw);
+printf("<svg id='svgBarChart' hasSampleN=%s></svg>\n", statsSize > 0.0 ? "true" : "false");
 }
 
 
 static void printBarChart(char *item, struct barChartBed *chart, struct trackDb *tdb,
     double maxVal, char *metric)
 /* Plot bar chart without expressionMatrix or R plots*/
 {
 char *statsFile = hReplaceGbdb(trackDbSetting(tdb, "barChartStatsUrl"));
 char *facets = trackDbSetting(tdb, "barChartFacets");
 if (facets != NULL && statsFile != NULL)
     facetedBarChart(item, chart, tdb, maxVal, statsFile, facets, metric);
 else
     svgBarChart(chart, tdb, maxVal, metric);
 }