b8180d9f6d41dc708a2f249ba892cbca311e7a06
jcasper
  Mon Feb 27 11:38:55 2023 -0800
Adding transparency support for colors refs #30569

diff --git src/hg/hgGenome/mainPage.c src/hg/hgGenome/mainPage.c
index 4f8b436..c714545 100644
--- src/hg/hgGenome/mainPage.c
+++ src/hg/hgGenome/mainPage.c
@@ -1,694 +1,694 @@
 /* mainPage - draws the main hgGenome page, including some controls
  * on the top and the graphic. */
 
 /* Copyright (C) 2013 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "psGfx.h"
 #include "pscmGfx.h"
 #include "linefile.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "cart.h"
 #include "hui.h"
 #include "dbDb.h"
 #include "ra.h"
 #include "hdb.h"
 #include "web.h"
 #include "portable.h"
 #include "hgColors.h"
 #include "trackLayout.h"
 #include "chromInfo.h"
 #include "hvGfx.h"
 #include "genoLay.h"
 #include "cytoBand.h"
 #include "hCytoBand.h"
 #include "errCatch.h"
 #include "chromGraph.h"
 #include "customTrack.h"
 #include "hPrint.h"
 #include "jsHelper.h"
 #include "hgGenome.h"
 #include "trashDir.h"
 
 
 
 static char *allColors[] = {
     "black", "blue", "purple", "red", "orange", "yellow", "green", "gray",
 };
 
 static char *defaultColors[maxLinesOfGraphs][maxGraphsPerLine] = {
     {"blue", "red", "black", "yellow", },
     {"gray", "purple", "green", "orange",},
     {"blue", "red", "black", "yellow", },
     {"gray", "purple", "green", "orange",},
     {"blue", "red", "black", "yellow", },
     {"gray", "purple", "green", "orange",},
 };
 
 char *graphColorAt(int row, int col)
 /* Return graph color at given row/column, NULL if nonw. */
 {
 char *varName = graphColorVarName(row, col);
 char *color = cartUsualString(cart, varName, defaultColors[row][col]);
 if (color == NULL) color = defaultColors[0][0];
 return color;
 }
 
 Color colorFromAscii(struct hvGfx *hvg, char *asciiColor)
 /* Get color index for a named color. */
 {
 if (sameWord("red", asciiColor))
     return MG_RED;
 else if (sameWord("blue", asciiColor))
     return MG_BLUE;
 else if (sameWord("yellow", asciiColor))
     return hvGfxFindColorIx(hvg, 220, 220, 0);
 else if (sameWord("purple", asciiColor))
     return hvGfxFindColorIx(hvg, 150, 0, 200);
 else if (sameWord("orange", asciiColor))
     return hvGfxFindColorIx(hvg, 230, 120, 0);
 else if (sameWord("green", asciiColor))
     return hvGfxFindColorIx(hvg, 0, 180, 0);
 else if (sameWord("gray", asciiColor))
     return MG_GRAY;
 else
     return MG_BLACK;
 }
 
 /* Page drawing stuff. */
 
 void drawChromGraph(struct hvGfx *hvg, struct sqlConnection *conn, 
 	struct genoLay *gl, char *chromGraph, int yOff, int height, Color color,
 	boolean leftLabel, boolean rightLabel, boolean firstInRow)
 /* Draw chromosome graph on all chromosomes in layout at given
  * y offset and height. */
 {
 boolean yellowMissing = getYellowMissing();
 struct genoGraph *gg = hashFindVal(ggHash, chromGraph);
 if (gg != NULL)
     {
     /* Get binary data source and scaling info etc. */
     struct chromGraphBin *cgb = gg->cgb;
     struct chromGraphSettings *cgs = gg->settings;
     int maxGapToFill = cgs->maxGapToFill;
-    static struct rgbColor missingDataColor = { 180, 180, 120};
+    static struct rgbColor missingDataColor = { 180, 180, 120, 255};
     Color missingColor = hvGfxFindRgb(hvg, &missingDataColor);
     double pixelsPerBase = 1.0/gl->basesPerPixel;
     double gMin = cgs->minVal, gMax = cgs->maxVal, gScale;
     gScale = height/(gMax-gMin);
 
     /* Draw significance threshold as a light blue line */
     if (leftLabel)
         {
-	static struct rgbColor guidelineColor = { 220, 220, 255};
+	static struct rgbColor guidelineColor = { 220, 220, 255, 255};
 	Color lightBlue = hvGfxFindRgb(hvg, &guidelineColor);
 	struct slRef *ref;
 	struct genoLayChrom *chrom;
 	int rightX = gl->picWidth - gl->rightLabelWidth - gl->margin;
 	int leftX = gl->leftLabelWidth + gl->margin;
 	int width = rightX - leftX;
 	double threshold = getThreshold();
 	if (threshold >= gMin && threshold <= gMax)
 	    {
 	    int y = height - ((threshold - gMin)*gScale) + yOff;
 	    for (ref = gl->leftList; ref != NULL; ref = ref->next)
 		{
 		chrom = ref->val;
 		hvGfxBox(hvg, leftX, y + chrom->y, width, 1, lightBlue);
 		}
 	    ref = gl->bottomList;
 	    if (ref != NULL)
 		{
 		chrom = ref->val;
 		hvGfxBox(hvg, leftX, y + chrom->y, width, 1, lightBlue);
 		}
 	    }
 	}
 
     /* Draw graphs on each chromosome */
     chromGraphBinRewind(cgb);
     while (chromGraphBinNextChrom(cgb))
 	{
 	struct genoLayChrom *chrom = hashFindVal(gl->chromHash, cgb->chrom);
 	if (chrom)
 	    {
 	    int chromX = chrom->x, chromY = chrom->y;
 	    int minY = chromY + yOff;
 	    int maxY = chromY + yOff + height - 1;
 
 	    if (chromGraphBinNextVal(cgb))
 	        {
 		/* Set clipping so can't scribble outside of box. */
 		hvGfxSetClip(hvg, chromX, chromY, chrom->width+1, chrom->height);
 
 		/* Handle first point as special case here, so don't
 		 * have to test for first point in inner loop. */
 
 		double x,lastX;
 		int y,start,lastStart,lastY;
 
 		start = lastStart = cgb->chromStart;
 		x = lastX = pixelsPerBase*start + chromX;
 		y = lastY = (height - ((cgb->val - gMin)*gScale)) + chromY+yOff;
 		if (y < minY) y = minY;
 		else if (y > maxY) y = maxY;
 
 		struct pscmGfx *pscm = NULL;
 		if (hvg->pixelBased)
 		    {
     		    hvGfxDot(hvg, x, y, color);
 		    }
 		else
 		    {
 		    pscm = (struct pscmGfx *)hvg->vg->data;
 		    pscmSetColor(pscm, color);
 		    psFillEllipse(pscm->ps, x, y, 0.01, 0.01);
 		    psSetLineWidth(pscm->ps, 0.01);
 		    }
 
 		/* Draw rest of points, connecting with line to previous point
 		 * if not too far off. */
 		while (chromGraphBinNextVal(cgb))
 		    {
 		    start = cgb->chromStart;
 		    x = pixelsPerBase*start + chromX;
 		    if (hvg->pixelBased)
 			x = (int) x;
 		    y = (height - ((cgb->val - gMin)*gScale)) + chromY+yOff;
 		    if (y < minY) y = minY;
 		    else if (y > maxY) y = maxY;
 		    if (x != lastX || y != lastY)
 		        {
 			if (start - lastStart <= maxGapToFill)
 			    {
 			    if (hvg->pixelBased)
 				hvGfxLine(hvg, lastX, lastY, x, y, color);
 			    else
 				{
 				pscmSetColor(pscm, color);
 				psDrawLine(pscm->ps, lastX, lastY, x, y);
 				}
 			    }
 			else
 			    {
 			    if (yellowMissing && leftLabel)
 			        {
 				int width = x - lastX - 1;
 				if (width > 0)
 				    hvGfxBox(hvg, lastX+1, minY, width, height, missingColor);
 				}
 			    if (hvg->pixelBased)
 				{
 				hvGfxDot(hvg, x, y, color);
 				}
 			    else
 				{
 				pscmSetColor(pscm, color);
 				psFillEllipse(pscm->ps, x, y, 0.01, 0.01);
 				}
 			    }
 			}
 		    lastX = x;
 		    lastY = y;
 		    lastStart = start;
 		    }
 
 		if (!hvg->pixelBased)
 		    {
 		    psSetLineWidth(pscm->ps, 1);
 		    }
 
 		hvGfxUnclip(hvg);
 		}
 	    }
 	else
 	    {
 	    /* Just read and discard data. */
 	    while (chromGraphBinNextVal(cgb))
 	        ;
 	    }
 	}
 
 
     /* Draw labels. */
     if (withLabels && (leftLabel || rightLabel))
         {
 	int lineY = yOff;
 	int i,j;
 	int spaceWidth = tl.nWidth;
 	int tickWidth = spaceWidth*2/3;
 	int fontPixelHeight = mgFontPixelHeight(gl->font);
 	for (i=0; i<gl->lineCount; ++i)
 	    {
 	    hvGfxSetClip(hvg, 0, lineY, gl->picWidth, gl->lineHeight);
 	    for (j=0; j< cgs->linesAtCount; ++j)
 	        {
 		double lineAt = cgs->linesAt[j];
 		int y = (height - ((lineAt - gMin)*gScale)) + lineY;
 		int textTop = y - fontPixelHeight/2+1;
 		int textBottom = textTop + fontPixelHeight;
 		char label[24];
 		safef(label, sizeof(label), "%g", lineAt);
 		if (leftLabel)
 		    {
 		    hvGfxBox(hvg, gl->margin + gl->leftLabelWidth - tickWidth-1, y,
 		    	tickWidth, 1, color);
 		    if (textTop >= lineY && textBottom < lineY + height)
 			{
 			hvGfxTextRight(hvg, gl->margin, textTop, 
 			    gl->leftLabelWidth-spaceWidth, fontPixelHeight,
 			    color, gl->font, label);
 			}
 		    }
 		if (rightLabel)
 		    {
 		    hvGfxBox(hvg, 
 		    	gl->picWidth - gl->margin - gl->rightLabelWidth+1, 
 		    	y, tickWidth, 1, color);
 		    if (textTop >= lineY && textBottom < lineY + height)
 			{
 			hvGfxText(hvg,
 			    gl->picWidth - gl->margin - gl->rightLabelWidth + spaceWidth,
 			    textTop, color, gl->font, label);
 			}
 		    }
 		}
 	    lineY += gl->lineHeight;
 	    hvGfxUnclip(hvg);
 	    }
 	}
     }
 }
 
 void genomeGif(struct sqlConnection *conn, struct genoLay *gl,
 	int graphRows, int graphCols, int oneRowHeight, char *psOutput)
 /* Create genome GIF file and HTML that includes it. */
 {
 struct hvGfx *hvg;
 struct tempName gifTn;
 Color shadesOfGray[10];
 int maxShade = ArraySize(shadesOfGray)-1;
 int spacing = 1;
 int yOffset = 2*spacing;
 int innerHeight = oneRowHeight - 3*spacing;
 int i,j;
 
 
 if (psOutput)
     {
     hvg = hvGfxOpenPostScript(gl->picWidth, gl->picHeight, psOutput);
     }
 else
     {
 
     /* Create gif file and make reference to it in html. */
     trashDirFile(&gifTn, "hgg", "ideo", ".png");
     hvg = hvGfxOpenPng(gl->picWidth, gl->picHeight, gifTn.forCgi, FALSE);
 
     hPrintf("<INPUT TYPE=IMAGE SRC=\"%s\" BORDER=1 WIDTH=%d HEIGHT=%d NAME=\"%s\">",
 		gifTn.forHtml, gl->picWidth, gl->picHeight, hggClick);
     }
 
 /* Get our grayscale. */
 hMakeGrayShades(hvg, shadesOfGray, maxShade);
 
 /* Draw the labels and then the chromosomes. */
 genoLayDrawChromLabels(gl, hvg, MG_BLACK);
 genoLayDrawBandedChroms(gl, hvg, database, conn, 
 	shadesOfGray, maxShade, MG_BLACK);
 
 /* Draw chromosome graphs. */
 for (i=0; i<graphRows; ++i)
     {
     boolean firstInRow = TRUE;
     for (j=graphCols-1; j>=0; --j)
 	{
 	char *graph = graphSourceAt(i,j);
 	if (graph != NULL && graph[0] != 0)
 	    {
 	    Color color = colorFromAscii(hvg, graphColorAt(i,j));
 	    drawChromGraph(hvg, conn, gl, graph, 
 		    gl->betweenChromOffsetY + yOffset, 
 		    innerHeight,  color, j==0, j==1, firstInRow);
 	    firstInRow = FALSE;
 	    }
 	}
     yOffset += oneRowHeight;
     }
 
 hvGfxBox(hvg, 0, 0, gl->picWidth, 1, MG_GRAY);
 hvGfxBox(hvg, 0, gl->picHeight-1, gl->picWidth, 1, MG_GRAY);
 hvGfxBox(hvg, 0, 0, 1, gl->picHeight, MG_GRAY);
 hvGfxBox(hvg, gl->picWidth-1, 0, 1, gl->picHeight, MG_GRAY);
 hvGfxClose(&hvg);
 }
 
 void graphDropdown(struct sqlConnection *conn, char *varName, char *curVal, char *event, char *js)
 /* Make a drop-down with available chrom graphs */
 {
 int totalCount = 1;
 char **menu, **values;
 int i = 0;
 struct slRef *ref;
 for (ref = ggList; ref != NULL; ref = ref->next)
     {
     struct genoGraph *gg = ref->val;
     if (gg->isComposite == FALSE)
 	totalCount++;
     }
 
 AllocArray(menu, totalCount);
 AllocArray(values, totalCount);
 menu[0] = "-- nothing --";
 values[0] = "";
 
 for (ref = ggList; ref != NULL; ref = ref->next)
     {    
     struct genoGraph *gg = ref->val;
     if (gg->isComposite == FALSE)
 	{
 	++i;
 	menu[i] = gg->shortLabel;
 	values[i] = gg->name;
 	}
     }
 cgiMakeDropListFull(varName, menu, values, totalCount, curVal, event, js);
 freez(&menu);
 freez(&values);
 }
 
 void colorDropdown(int row, int col, char *event, char *js)
 /* Put up color drop down menu. */
 {
 char *varName = graphColorVarName(row, col);
 char *curVal = graphColorAt(row, col);
 cgiMakeDropListFull(varName, allColors, allColors, ArraySize(allColors), curVal, event, js);
 }
 
 static void addThresholdGraphCarries(struct dyString *dy, int graphRows, int graphCols, boolean cgaOnly)
 /* Add javascript that carries over threshold and graph vars
  * to new form. */
 {
 if (cgaOnly)
     return;
 jsTextCarryOver(dy, getThresholdName());
 int i,j;
 for (i=0; i<graphRows; ++i)
     for (j=0; j<graphCols; ++j)
         {
 	jsDropDownCarryOver(dy, graphVarName(i,j));
 	jsDropDownCarryOver(dy, graphColorVarName(i,j));
 	}
 }
 
 static struct dyString *onChangeStart(int graphRows, int graphCols, boolean cgaOnly)
 /* Return common prefix to onChange javascript string */
 {
 struct dyString *dy = jsOnChangeStart();
 addThresholdGraphCarries(dy, graphRows, graphCols, cgaOnly);
 return dy;
 }
 
 static char *onChangeClade(int graphRows, int graphCols, boolean cgaOnly)
 /* Return javascript executed when they change clade. */
 {
 struct dyString *dy = onChangeStart(graphRows, graphCols, cgaOnly);
 jsDropDownCarryOver(dy, "clade");
 dyStringAppend(dy, " document.hiddenForm.org.value=0;");
 dyStringAppend(dy, " document.hiddenForm.db.value=0;");
 return jsOnChangeEnd(&dy);
 }
 
 static char *onChangeOrg(int graphRows, int graphCols, boolean cgaOnly)
 /* Return javascript executed when they change organism. */
 {
 struct dyString *dy = onChangeStart(graphRows, graphCols, cgaOnly);
 jsDropDownCarryOver(dy, "clade");
 jsDropDownCarryOver(dy, "org");
 dyStringAppend(dy, " document.hiddenForm.db.value=0;");
 return jsOnChangeEnd(&dy);
 }
 
 static void saveOnChangeOtherFunction(int graphRows, int graphCols, boolean cgaOnly)
 /* Write out Javascript function to save vars in hidden
  * form and submit. */
 {
 struct dyString *dy = dyStringNew(4096);
 dyStringAppend(dy, 
 "var submitted=false;\n"
 "function changeOther()\n"
 "{\n"
 "if (!submitted)\n"
 "    {\n"
 "    submitted=true;\n"
 "    ");
 addThresholdGraphCarries(dy, graphRows, graphCols, cgaOnly);
 jsDropDownCarryOver(dy, "clade");
 jsDropDownCarryOver(dy, "org");
 jsDropDownCarryOver(dy, "db");
 dyStringAppend(dy, "document.hiddenForm.submit();\n");
 dyStringAppend(dy, 
 "    }\n"
 "}\n"
 );
 jsInline(dy->string);
 }
 
 static char *onChangeOther()
 /* Return javascript executed when they change database. */
 {
 return "changeOther();";
 }
 
 boolean renderGraphic(struct sqlConnection *conn, char *psOutput)
 /* draw just the graphic */
 {
 struct genoLay *gl;
 int graphRows = linesOfGraphs();
 int graphCols = graphsPerLine();
 boolean result = FALSE;
 if (ggList != NULL)
     {
     /* Get genome layout.  This can fail so it is wrapped in an error
      * catcher. */
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
 	{
 	gl = ggLayout(conn, graphRows, graphCols);
 
 	/* Draw picture. Enclose in table to add a couple of pixels between
 	 * it and controls on IE. */
 	genomeGif(conn, gl, graphRows, graphCols, graphHeight()+betweenRowPad, psOutput);
 	result = TRUE;
 	}
     errCatchEnd(errCatch);
     if (errCatch->gotError)
 	warn("%s", errCatch->message->string);
     errCatchFree(&errCatch); 
     }
 else
     {
     hPrintf("<BR><B>No graph data is available for this assembly.  "
 	    "Upload your own data or import from a table or custom track.</B>");
     }
 return result;
 }
 
 void handlePostscript(struct sqlConnection *conn)
 /* Deal with Postscript output. */
 {
 struct tempName psTn;
 char *pdfFile = NULL;
 trashDirFile(&psTn, "hgg", "hgg", ".eps");
 cartWebStart(cart, database, "%s Genome Graphs", genome);
 printf("<H1>PDF Output</H1>\n");
 
 boolean result = renderGraphic(conn, psTn.forCgi);
 if (result)
     {
     printf("EPS (PostScript) output has been discontinued in pursuit of additional features\n");
     printf("that are not PostScript-compatible.  If you require PostScript output for your\n");
     printf("workflow, please <a href='https://genome.ucsc.edu/contacts.html'>reach out to us</a>\n");
     printf("and let us know what your needs are - we may be able to help.\n");
     pdfFile = convertEpsToPdf(psTn.forCgi);
     if(pdfFile != NULL)
 	{
 	printf("<BR><BR>PDF can be viewed with Adobe Acrobat Reader.<BR>\n");
 	printf("<A TARGET=_blank HREF=\"%s\">Click here</A> "
 	       "to download the current browser graphic in PDF.", pdfFile);
 	}
     else
 	printf("<BR><BR>PDF format not available");
     freez(&pdfFile);
     }
 printf("<BR><BR><A HREF=\"../cgi-bin/hgGenome\">Return</A>");
 cartWebEnd();
 }
 
 
 
 void mainPage(struct sqlConnection *conn)
 /* Do main page of application:  hotlinks bar, controls, graphic. */
 {
 int graphRows = linesOfGraphs();
 int graphCols = graphsPerLine();
 int i, j;
 int realCount = 0;
 char *scriptName = "../cgi-bin/hgGenome";
 
 cartWebStart(cart, database, "%s Genome Graphs", genome);
 
 /* Start form and save session var. */
 hPrintf("<FORM ACTION=\"%s\" NAME=\"mainForm\" METHOD=GET>\n", scriptName);
 cartSaveSession(cart);
 
 
 
 /* it might be a non-chrom based assembly */
 
 // defined externally in import.c at the moment
 extern struct slName *getChroms();
 /* Get a chrom list. */
 
 
 struct slName *chromList = getChroms();
 int count = slCount(chromList);
 boolean cgaOnly = FALSE;
 if ((count > 500) || (count == 0))
     {
     cgaOnly = TRUE;
     }
 
 
 jsInit();
 saveOnChangeOtherFunction(graphRows, graphCols, cgaOnly);
 char *jsOther = onChangeOther();
 
 /* Print clade, genome and assembly line. */
 hPrintf("<TABLE>");
 boolean gotClade = hGotClade();
 if (gotClade)
     {
     hPrintf("<TR><TD><B>clade:</B>\n");
     printCladeListHtml(hGenome(database), "change", onChangeClade(graphRows, graphCols, cgaOnly));
     htmlNbSpaces(3);
     hPrintf("<B>genome:</B>\n");
     printGenomeListForCladeHtml(database, "change", onChangeOrg(graphRows, graphCols, cgaOnly));
     }
 else
     {
     hPrintf("<TR><TD><B>genome:</B>\n");
     printGenomeListHtml(database, "change", onChangeOrg(graphRows, graphCols, cgaOnly));
     }
 htmlNbSpaces(3);
 hPrintf("<B>assembly:</B>\n");
 printAssemblyListHtml(database, "change", jsOther);
 hPrintf("</TD></TR>\n");
 hPrintf("</TABLE>");
 
 
 if (cgaOnly)
     {
     if (count > 500)  /* Too-many-chrom assembly */
 	{
 	warn("Sorry, too many chromosomes. "
 	    "This one has %d. Please select another organism or assembly.", count);
 	}
     else if (count == 0)  /* non-chrom assembly */
 	{
 	warn("Sorry, can only do genome layout on assemblies mapped to chromosomes. "
 	    "This one has no usable chromosomes. Please select another organism or assembly.");
 	}
     }
 else
     {
 
     /* Draw graph controls. */
     hPrintf("<TABLE>");
     for (i=0; i<graphRows; ++i)
 	{
 	hPrintf("<TR>");
 	hPrintf("<TD><B>graph</B></TD>");
 	for (j=0; j<graphCols; ++j)
 	    {
 	    char *varName = graphVarName(i,j);
 	    char *curVal = cartUsualString(cart, varName, "");
 	    if (curVal[0] != 0)
 		++realCount;
 	    hPrintf("<TD>");
 	    graphDropdown(conn, varName, curVal, "change", jsOther);
 	    hPrintf(" <B>in</B> ");
 	    colorDropdown(i, j, "change", jsOther);
 	    if (j != graphCols-1) hPrintf(",");
 	    hPrintf("</TD>");
 	    }
 	hPrintf("</TR>");
 	}
     hPrintf("</TABLE>");
     cgiMakeButton(hggUpload, "upload");
     hPrintf(" ");
     cgiMakeButton(hggImport, "import");
     hPrintf(" ");
     cgiMakeButton(hggConfigure, "configure");
     hPrintf(" ");
     cgiMakeOptionalButton(hggCorrelate, "correlate", realCount < 2);
     hPrintf(" <B>significance threshold:</B>");
     hPrintf("<INPUT TYPE=\"TEXT\" NAME=\"%s\" id='%s' SIZE=\"%d\" VALUE=\"%g\">"
 	    , getThresholdName(),  getThresholdName(), 3, getThreshold());
     jsOnEventById("change"  , getThresholdName(), "changeOther();");
     jsOnEventById("keypress", getThresholdName(), "return submitOnEnter(event,document.mainForm);");
     hPrintf(" ");
     cgiMakeOptionalButton(hggBrowse, "browse regions", realCount == 0);
     hPrintf(" ");
     cgiMakeOptionalButton(hggSort, "sort genes", 
 	    realCount == 0 || !hgNearOk(database));
     hPrintf("<BR>");
 
     hPrintf("<TABLE CELLPADDING=2><TR><TD>\n");
 
     boolean result = renderGraphic(conn, NULL);
 
     hPrintf("</TD></TR></TABLE>\n");
     if (result)
 	/* Write a little click-on-help */
 	hPrintf("<i>Click on a chromosome to open Genome Browser at that position.</i>");
 
 
     }  /* end else non-chrom assembly */
 
 hPrintf("</FORM>\n");
 
 /* Hidden form - fo the benefit of javascript. */
     {
     /* Copy over both the regular, non-changing variables, and
     * also all the source/color pairs that depend on the 
     * configuration. */
     static char *regularVars[] = {
       "clade", "org", "db",
       };
     int regularCount = ArraySize(regularVars);
     int varCount = regularCount + 1 + 2 * graphRows * graphCols;
     int varIx = regularCount;
     char **allVars;
     AllocArray(allVars, varCount);
     CopyArray(regularVars, allVars, regularCount);
     allVars[varIx++] = cloneString(getThresholdName());
     for (i=0; i<graphRows; ++i)
         {
 	for (j=0; j<graphCols; ++j)
 	    {
 	    allVars[varIx++] = cloneString(graphVarName(i,j));
 	    allVars[varIx++] = cloneString(graphColorVarName(i,j));
 	    }
 	}
     jsCreateHiddenForm(cart, scriptName, allVars, varCount);
     }
 
 webNewSection("Using Genome Graphs");
 printMainHelp();
 cartWebEnd();
 }