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/hgc/hgc.c src/hg/hgc/hgc.c
index 1fdbec2d209..fafdfa1af60 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -24163,30 +24163,65 @@
 
 static void doOligoMatch(char *item)
 /* Print info about oligo match. */
 {
 char *oligo = cartUsualString(cart,
 	oligoMatchVar, cloneString(oligoMatchDefault));
 touppers(oligo);
 cartWebStart(cart, database, "Perfect Matches to Short Sequence");
 printf("<B>Sequence:</B> %s<BR>\n", oligo);
 printf("<B>Chromosome:</B> %s<BR>\n", seqName);
 printf("<B>Start:</B> %s<BR>\n", item+1);
 printf("<B>Strand:</B> %c<BR>\n", item[0]);
 webIncludeHelpFile(OLIGO_MATCH_TRACK_NAME, TRUE);
 }
 
+static void doGcOnFly(void)
+/* Display GC percent info for visible window, computed on the fly from sequence.
+ * No tdb or bigWig file needed - this is a synthetic track with no database table. */
+{
+char *winSizeStr = cartUsualString(cart, gcOnFlyWindowSize, gcOnFlyDefaultSize);
+int span = atoi(winSizeStr);
+char num1Buf[64], num2Buf[64];
+
+cartWebStart(cart, database, "GC Percent On the Fly");
+sprintLongWithCommas(num1Buf, BASE_1(winStart));
+sprintLongWithCommas(num2Buf, winEnd);
+printf("<B>Position:</B> %s:%s-%s<BR>\n", seqName, num1Buf, num2Buf);
+sprintLongWithCommas(num1Buf, winEnd - winStart);
+printf("<B>Total bases in view:</B> %s<BR>\n", num1Buf);
+printf("<B>Window size for GC calculation:</B> %d bases<BR>\n", span);
+
+struct dnaSeq *seq = hChromSeq(database, seqName, winStart, winEnd);
+if (seq != NULL)
+    {
+    int gcCount = 0, validBases = 0;
+    int i;
+    for (i = 0; i < seq->size; i++)
+        {
+        char b = seq->dna[i];
+        if (b == 'g' || b == 'c') { gcCount++;  validBases++; }
+        else if (b != 'n')          validBases++;
+        }
+    if (validBases > 0)
+        printf("<B>GC percent in view:</B> %.3f%%<BR>\n",
+               100.0 * gcCount / validBases);
+    dnaSeqFree(&seq);
+    }
+webIncludeHelpFile(GC_ON_FLY_TRACK_NAME, TRUE);
+}
+
 struct slName *cutterIsoligamers(struct cutter *myEnzyme)
 /* Find enzymes with same cut site. */
 {
 struct sqlConnection *conn;
 struct cutter *cutters = NULL;
 struct slName *ret = NULL;
 
 conn = hAllocConn("hgFixed");
 char query[1024];
 sqlSafef(query, sizeof query, "select * from cutters");
 cutters = cutterLoadByQuery(conn, query);
 ret = findIsoligamers(myEnzyme, cutters);
 hFreeConn(&conn);
 cutterFreeList(&cutters);
 return ret;
@@ -27149,30 +27184,32 @@
 else if (sameWord(table, "affyU95")
 	|| sameWord(table, "affyU133")
 	|| sameWord(table, "affyU74")
 	|| sameWord(table, "affyRAE230")
 	|| sameWord(table, "affyZebrafish")
 	|| sameWord(table, "affyGnf1h")
 	|| sameWord(table, "affyMOE430v2")
 	|| sameWord(table, "affyGnf1m") )
     {
     doAffy(tdb, item, NULL);
     }
 else if (sameWord(table, WIKI_TRACK_TABLE))
     doWikiTrack(item, seqName, winStart, winEnd);
 else if (sameWord(table, OLIGO_MATCH_TRACK_NAME))
     doOligoMatch(item);
+else if (sameWord(table, GC_ON_FLY_TRACK_NAME))
+    doGcOnFly();
 else if (sameWord(table, "refFullAli"))
     {
     doTSS(tdb, item);
     }
 else if (sameWord(table, "rikenMrna"))
     {
     doRikenRna(tdb, item);
     }
 else if (sameWord(table, "cgapSage"))
     {
     doCgapSage(tdb, item);
     }
 else if (sameWord(table, "ctgPos") || sameWord(table, "ctgPos2"))
     {
     doHgContig(tdb, item);