da33d70c10ddaae6c3290cb900d7d0cc2b6ee01b
hiram
  Tue Mar 17 13:57:24 2026 -0700
allow calculation of GC percent on the fly with code help from claude refs #35958

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index 9138d99c494..278122e2f27 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -2271,30 +2271,48 @@
 "}\n");
 printf("<input name='%s' id='%s' size=\"%d\" value=\"%s\" type=\"TEXT\">",
     oligoMatchVar, oligoMatchVar, 45, oligo);
 puts("<br>Examples: TATAWAAR, AAAAA");
 jsOnEventById("input", oligoMatchVar, "packTrack();");
 
 puts("<P><B>Search strand:</B> ");
 cgiMakeRadioButton(oligoMatchStrandVar, "both", sameString(strand, "both"));
 puts(" Both ");
 cgiMakeRadioButton(oligoMatchStrandVar, "forward", sameString(strand, "forward"));
 puts(" Forward (+) ");
 cgiMakeRadioButton(oligoMatchStrandVar, "reverse", sameString(strand, "reverse"));
 puts(" Reverse (-) ");
 }
 
+static void gcOnFlyUi(struct trackDb *tdb)
+/* UI for oligo match track */
+{
+char *winSize = cartUsualString(cart, gcOnFlyWindowSize, gcOnFlyDefaultSize);
+puts("<P><B>GC Percent calculation window size:&nbsp;</B>");
+jsInline(
+"function fullTrack()\n"
+"{\n"
+"var box = jQuery('select[name$=gcOnFly]');\n"
+"if (box.val()=='hide')\n"
+"    box.val('full');\n"
+"}\n");
+printf("<input name='%s' id='%s' size=\"%d\" value=\"%s\" type=\"TEXT\">",
+    gcOnFlyWindowSize, gcOnFlySizeVar, 15, winSize);
+jsOnEventById("input", gcOnFlySizeVar, "fullTrack();");
+puts("<P>UCSC standard window size is 5 bases.  Adjust size as desired.</P>");
+}
+
 void cutterUi(struct trackDb *tdb)
 /* UI for restriction enzyme track */
 {
 char *enz = cartUsualString(cart, cutterVar, cutterDefault);
 puts("<P><B>Filter display by enzymes (separate with commas):</B><BR>");
 cgiMakeTextVar(cutterVar, enz, 100);
 }
 
 void genericWiggleUi(struct trackDb *tdb, int optionNum )
 /* put up UI for any standard wiggle track (a.k.a. sample track)*/
 {
 
 char options[7][256];
 int thisHeightPer, thisLineGap;
 float thisMinYRange, thisMaxYRange;
@@ -3314,30 +3332,32 @@
     // add explicitly here only if track has another type (bed, chain).
     // For crossSpeciesCfgUi, the
     // default for chrom coloring is "on", unless track setting
     // colorChromDefault is set to "off"
     crossSpeciesCfgUi(cart,tdb);
 else if (sameString(track, "affyTranscriptome"))
     affyTranscriptomeUi(tdb);
 else if (sameString(track, WIKI_TRACK_TABLE))
     wikiTrackUi(tdb);
 else if (sameString(track, RULER_TRACK_NAME))
     rulerUi(tdb);
 else if (sameString(trackHubSkipHubName(track), "quickLiftChain"))
     quickLiftUi(tdb);
 else if (sameString(track, OLIGO_MATCH_TRACK_NAME))
     oligoMatchUi(tdb);
+else if (sameString(track, GC_ON_FLY_TRACK_NAME))
+    gcOnFlyUi(tdb);
 else if (sameString(track, CUTTERS_TRACK_NAME))
     cutterUi(tdb);
 else if(sameString(track, "affyTransfrags"))
     affyTransfragUi(tdb);
 else if (sameString(track, "gvPos"))
     gvUi(tdb);
 else if (sameString(track, "oreganno"))
     oregannoUi(tdb);
 else if (startsWith("retroposons", track))
     retroposonsUi(tdb);
 else if (sameString(track, "tfbsConsSites"))
     tfbsConsSitesUi(tdb);
 else if (sameString(track, "CGHBreastCancerUCSF"))
     ucsfdemoUi(tdb);
 else if (startsWith("hapmapSnps", track))
@@ -3981,30 +4001,41 @@
 /* Create a trackDb entry for the wikiTrack.
    It is not a real track, so doesn't appear in trackDb */
 {
 return trackDbForPseudoTrack(WIKI_TRACK_TABLE,
 	WIKI_TRACK_LABEL, WIKI_TRACK_LONGLABEL, tvFull, FALSE);
 }
 
 struct trackDb *trackDbForRuler()
 /* Create a trackDb entry for the base position ruler.
    It is not (yet?) a real track, so doesn't appear in trackDb */
 {
 return trackDbForPseudoTrack(RULER_TRACK_NAME,
 	RULER_TRACK_LABEL, RULER_TRACK_LONGLABEL, tvFull, FALSE);
 }
 
+static struct trackDb *trackDbForGcOnFly()
+/* Create a trackDb entry for the GC on the fly pseudo-track. */
+{
+char longLabel[1024];
+safef(longLabel, sizeof(longLabel), "GC FLY Percent in %s-Base Windows", gcOnFlyWinSize(cart));
+struct trackDb *tdb = trackDbForPseudoTrack(GC_ON_FLY_TRACK_NAME,
+        GC_ON_FLY_TRACK_LABEL, longLabel, tvFull, TRUE);
+tdb->canPack = 0;
+return tdb;
+}
+
 struct trackDb *trackDbForOligoMatch()
 /* Create a trackDb entry for the oligo matcher pseudo-track. */
 {
 return trackDbForPseudoTrack(OLIGO_MATCH_TRACK_NAME,
 	OLIGO_MATCH_TRACK_LABEL, OLIGO_MATCH_TRACK_LONGLABEL, tvHide, TRUE);
 }
 
 static char *handleDupOp(char *track, struct hash *trackHash)
 /* Handle the duplication operation in the URL if any.  Return dupe name if
  * a dupe has happened. The trackHash is keyed by track name and has 
  * struct trackDb values. */
 {
 char *newTrack = NULL;
 
 /* Handle duplicate, and possible in the future other operations on tracks. */
@@ -4094,30 +4125,32 @@
 		dupTdb = NULL;  /* We use it! */
 		}
 	    }
 	if (dupTdb != NULL)
 	    slAddHead(&tdbList, dupTdb);
 	}
     }
 
 if (sameWord(track, WIKI_TRACK_TABLE))
     tdb = trackDbForWikiTrack();
 else if (sameWord(track, RULER_TRACK_NAME))
     /* special handling -- it's not a full-fledged track */
     tdb = trackDbForRuler();
 else if (sameWord(track, OLIGO_MATCH_TRACK_NAME))
     tdb = trackDbForOligoMatch();
+else if (sameWord(track, GC_ON_FLY_TRACK_NAME))
+    tdb = trackDbForGcOnFly(cart);
 else if (sameWord(track, CUTTERS_TRACK_NAME))
     tdb = trackDbForPseudoTrack(CUTTERS_TRACK_NAME, CUTTERS_TRACK_LABEL, CUTTERS_TRACK_LONGLABEL, tvHide, TRUE);
 else if (isCustomTrack(track))
     {
     ctList = customTracksParseCart(database, cart, NULL, NULL);
     for (ct = ctList; ct != NULL; ct = ct->next)
         {
         if (sameString(track, ct->tdb->track))
             {
             tdb = ct->tdb;
             break;
             }
         }
     }
 else if (isHubTrack(track))