510ec86d77710a8670b5276a8eea40f3d6ab74cc
braney
  Fri Apr 7 12:16:08 2023 -0700
tweak bigChain support to work with snakes.  Change UI option to use the
words "rearrangement graph" instead of snake

diff --git src/hg/hgTracks/chainTrack.c src/hg/hgTracks/chainTrack.c
index 900bbbb..7dae716 100644
--- src/hg/hgTracks/chainTrack.c
+++ src/hg/hgTracks/chainTrack.c
@@ -1,623 +1,625 @@
 /* chainTrack - stuff to load and display chain type tracks in
  * browser.   Chains are typically from cross-species genomic
  * alignments. */
 
 /* 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 "hash.h"
 #include "localmem.h"
 #include "linefile.h"
 #include "jksql.h"
 #include "hdb.h"
 #include "hgTracks.h"
 #include "chainBlock.h"
 #include "chainLink.h"
 #include "chainDb.h"
 #include "chainCart.h"
 #include "hgColors.h"
 #include "hubConnect.h"
 #include "chromAlias.h"
 #include "hgConfig.h"
 #include "snake.h"
 
 struct cartOptions
     {
     enum chainColorEnum chainColor; /*  ChromColors, ScoreColors, NoColors */
     int scoreFilter ; /* filter chains by score if > 0 */
     };
 
 struct sqlClosure
 {
 struct sqlConnection *conn;
 };
 
 struct bbClosure
 {
 struct bbiFile *bbi;
 };
 
 typedef void  (*linkRetrieveFunc)(void *closure,  char *fullName,
 			struct lm *lm, struct hash *hash,
 			int start, int end, char * chainId, boolean isSplit);
 
 static void doBbQuery(void *closure, char *fullName,
 			struct lm *lm, struct hash *hash,
 			int start, int end, char * chainId, boolean isSplit)
 /* doBbQuery- check a bigBed for chain elements between
  * 	start and end.  Use the passed hash to resolve chain
  * 	id's and place the elements into the right
  * 	linkedFeatures structure
  */
 {
 struct bbClosure *bbClosure = (struct bbClosure *)closure;
 struct bbiFile *bbi = bbClosure->bbi;
 struct linkedFeatures *lf;
 struct simpleFeature *sf;
 
 struct bigBedInterval *bb, *bbList =  bigBedIntervalQuery(bbi, chromName, start, end, 0, lm);
 char *bedRow[5];
 char startBuf[16], endBuf[16];
 
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
     bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow));
     lf = hashFindVal(hash, bedRow[3]);
 
     if (lf != NULL)
 	{
 	lmAllocVar(lm, sf);
 	sf->start = sqlUnsigned(bedRow[1]);
 	sf->end = sqlUnsigned(bedRow[2]);
 	sf->qStart = sqlUnsigned(bedRow[4]);
 	sf->qEnd = sf->qStart + (sf->end - sf->start);
 	slAddHead(&lf->components, sf);
 	}
     }
 }
 
 static void doQuery(void *closure, char *fullName,
 			struct lm *lm, struct hash *hash,
 			int start, int end, char * chainId, boolean isSplit)
 /* doQuery- check the database for chain elements between
  * 	start and end.  Use the passed hash to resolve chain
  * 	id's and place the elements into the right
  * 	linkedFeatures structure
  */
 {
 struct sqlClosure *sqlClosure = (struct sqlClosure *)closure;
 struct sqlConnection *conn = sqlClosure->conn;
 struct sqlResult *sr = NULL;
 char **row;
 struct linkedFeatures *lf;
 struct simpleFeature *sf;
 struct dyString *query = dyStringNew(1024);
 
 if (chainId == NULL)
     {
     sqlDyStringPrintf(query,
 	"select chainId,tStart,tEnd,qStart from %sLink ",
 	fullName);
     if (isSplit)
 	sqlDyStringPrintf(query,"force index (bin) ");
     sqlDyStringPrintf(query,"where "); 
     }
 else
     sqlDyStringPrintf(query,
 	"select chainId, tStart,tEnd,qStart from %sLink where chainId=%s and ",
 	fullName, chainId);
 if (!isSplit)
     sqlDyStringPrintf(query, "tName='%s' and ", chromName);
 hAddBinToQuery(start, end, query);
 sqlDyStringPrintf(query, "tStart<%u and tEnd>%u", end, start);
 sr = sqlGetResult(conn, query->string);
 
 /* Loop through making up simple features and adding them
  * to the corresponding linkedFeature. */
 while ((row = sqlNextRow(sr)) != NULL)
     {
     lf = hashFindVal(hash, row[0]);
     if (lf != NULL)
 	{
 	lmAllocVar(lm, sf);
 	sf->start = sqlUnsigned(row[1]);
 	sf->end = sqlUnsigned(row[2]);
 	sf->qStart = sqlUnsigned(row[3]);
 	sf->qEnd = sf->qStart + (sf->end - sf->start);
 	slAddHead(&lf->components, sf);
 	}
     }
 sqlFreeResult(&sr);
 dyStringFree(&query);
 }
 
 static void loadLinks(struct track *tg, int seqStart, int seqEnd,
         enum trackVisibility vis)
 {
 struct linkedFeatures *lf;
 struct simpleFeature *sf;
 struct lm *lm;
 struct hash *hash;	/* Hash of chain ids. */
 #ifdef OLD
 double scale = ((double)(winEnd - winStart))/width;
 #endif /* OLD */
 
 char fullName[64];
 int start, end, extra;
 struct simpleFeature *lastSf = NULL;
 int maxOverLeft = 0, maxOverRight = 0;
 int overLeft, overRight;
 void *closure;
 struct sqlClosure sqlClosure;
 struct bbClosure bbClosure;
 linkRetrieveFunc queryFunc;
 if (tg->isBigBed)
     {
     closure = &bbClosure;
     queryFunc = doBbQuery;
     char *fileName = trackDbSetting(tg->tdb, "linkDataUrl");
     if (fileName == NULL)
         {
         warn("Cannot find linkDataUrl in custom track \"%s\"\n", tg->shortLabel);
         return;
         }
     struct bbiFile *bbi =  bigBedFileOpenAlias(fileName, chromAliasFindAliases);
     if (bbi == NULL)
         return;
     bbClosure.bbi =  bbi;
     }
 else
     {
     closure = &sqlClosure;
     queryFunc = doQuery;
     sqlClosure.conn = hAllocConn(database);
     }
 
 lm = lmInit(1024*4);
 hash = newHash(0);
 
 /* Make up a hash of all linked features keyed by
  * id, which is held in the extras field.  To
  * avoid burning memory on full chromosome views
  * we'll just make a single simple feature and
  * exclude from hash chains less than three pixels wide,
  * since these would always appear solid. */
 for (lf = tg->items; lf != NULL; lf = lf->next)
     {
 #ifdef OLD
     double pixelWidth = (lf->end - lf->start) / scale;
     if (pixelWidth >= 2.5)
 #endif /* OLD */
 	{
 	hashAdd(hash, lf->extra, lf);
 	overRight = lf->end - seqEnd;
 	if (overRight > maxOverRight)
 	    maxOverRight = overRight;
 	overLeft = seqStart - lf->start ;
 	if (overLeft > maxOverLeft)
 	    maxOverLeft = overLeft;
 	}
 #ifdef OLD
     else
 	{
 	lmAllocVar(lm, sf);
 	sf->start = lf->start;
 	sf->end = lf->end;
 	sf->grayIx = lf->grayIx;
 	lf->components = sf;
 	}
 #endif /* OLD */
     }
 
 /* if some chains are actually loaded */
 if (hash->elCount) 
     {
     boolean isSplit = TRUE;
     /* Make up range query. */
     safef(fullName, sizeof fullName, "%s_%s", chromName, tg->table);
     if (isHubTrack(tg->table) || !hTableExists(database, fullName))
 	{
 	strcpy(fullName, tg->table);
 	isSplit = FALSE;
 	}
 
     /* in dense mode we don't draw the lines
      * so we don't need items off the screen
      */
     if (vis == tvDense)
 	queryFunc(closure, fullName, lm,  hash, seqStart, seqEnd, NULL, isSplit);
     else
 	{
 	/* if chains extend beyond edge of window we need to get
 	 * elements that are off the screen
 	 * in both directions so we know whether to draw
 	 * one or two lines to the edge of the screen.
 	 */
 #define STARTSLOP	10000
 #define MULTIPLIER	10
 #define MAXLOOK		100000
 	extra = (STARTSLOP < maxOverLeft) ? STARTSLOP : maxOverLeft;
 	start = seqStart - extra;
 	extra = (STARTSLOP < maxOverRight) ? STARTSLOP : maxOverRight;
 	end = seqEnd + extra;
 	queryFunc(closure, fullName, lm,  hash, start, end, NULL, isSplit);
 
 	for (lf = tg->items; lf != NULL; lf = lf->next)
 	    {
 	    if (lf->components != NULL)
 		slSort(&lf->components, linkedFeaturesCmpStart);
 	    extra = (STARTSLOP < maxOverRight)?STARTSLOP:maxOverRight;
 	    end = seqEnd + extra;
 	    for (lastSf=sf=lf->components;  sf;  lastSf=sf, sf=sf->next)
 		;
 	    while (lf->end > end )
 		{
 		/* get out if we have an element off right side */
 		if (( (lastSf != NULL) &&(lastSf->end > seqEnd)) || (extra > MAXLOOK))
 		    break;
 
 		extra *= MULTIPLIER;
 		start = end;
 		end = start + extra;
                 queryFunc(closure, fullName, lm,  hash, start, end, lf->extra, isSplit);
 		if (lf->components != NULL)
 		    slSort(&lf->components, linkedFeaturesCmpStart);
 		for (sf=lastSf;  sf != NULL;  lastSf=sf, sf=sf->next)
 		    ;
 		}
 
 	    /* if we didn't find an element off to the right , add one */
 	    if ((lf->end > seqEnd) && ((lastSf == NULL) ||(lastSf->end < seqEnd)))
 		{
 		lmAllocVar(lm, sf);
 		sf->start = seqEnd;
 		sf->end = seqEnd+1;
 		sf->grayIx = lf->grayIx;
 		sf->qStart = 0;
 		sf->qEnd = sf->qStart + (sf->end - sf->start);
 		sf->next = lf->components;
 		lf->components = sf;
 		slSort(&lf->components, linkedFeaturesCmpStart);
 		}
 
 	    /* we know we have a least one component off right
 	     * now look for one off left
 	     */
 	    extra = (STARTSLOP < maxOverLeft) ? STARTSLOP:maxOverLeft;
 	    start = seqStart - extra;
 	    while((extra < MAXLOOK) && (lf->start < seqStart) &&
 		(lf->components != NULL) && (lf->components->start > seqStart))
 		{
 		extra *= MULTIPLIER;
 		end = start;
 		start = end - extra;
                 if (start < 0)
                     start = 0;
 		queryFunc(closure, fullName, lm,  hash, start, end, lf->extra, isSplit);
 		slSort(&lf->components, linkedFeaturesCmpStart);
 		}
 	    if ((lf->components != NULL) && (lf->components->start > seqStart) && (lf->start < lf->components->start))
 		{
 		lmAllocVar(lm, sf);
 		sf->start = 0;
 		sf->end = 1;
 		sf->grayIx = lf->grayIx;
 		sf->qStart = lf->components->qStart;
 		sf->qEnd = sf->qStart + (sf->end - sf->start);
 		sf->next = lf->components;
 		lf->components = sf;
 		slSort(&lf->components, linkedFeaturesCmpStart);
 		}
 	    }
 	}
     }
 for (lf = tg->items; lf != NULL; lf = lf->next)
         {
 	if ((lf) && lf->orientation == -1)
 	    {
             struct simpleFeature *sf = lf->components;
 
             for(; sf; sf = sf->next)
                 {
                 int temp;
 
                 temp = sf->qStart;
                 sf->qStart = lf->qSize - sf->qEnd;
                 sf->qEnd = lf->qSize - temp;
                 }
 	    }
         }
 if (tg->isBigBed)
     bbiFileClose(&bbClosure.bbi);
 else
     hFreeConn(&sqlClosure.conn);
     }
 void chainDraw(struct track *tg, int seqStart, int seqEnd,
         struct hvGfx *hvg, int xOff, int yOff, int width,
         MgFont *font, Color color, enum trackVisibility vis)
 /* Draw chained features. This loads up the simple features from
  * the chainLink table, calls linkedFeaturesDraw, and then
  * frees the simple features again. */
 {
 
 if (tg->items == NULL)		/*Exit Early if nothing to do */
     return;
 
 linkedFeaturesDraw(tg, seqStart, seqEnd, hvg, xOff, yOff, width,
 	font, color, vis);
 }
 
 void bigChainLoadItems(struct track *tg)
 /* Load up all of the chains from correct table into tg->items
  * item list.  At this stage to conserve memory for other tracks
  * we don't load the links into the components list until draw time. */
 {
 boolean doSnake = cartOrTdbBoolean(cart, tg->tdb, "doSnake" , FALSE);
 struct linkedFeatures *list = NULL, *lf;
 int qs;
 char *optionChrStr;
 struct cartOptions *chainCart;
 
 chainCart = (struct cartOptions *) tg->extraUiData;
 
 optionChrStr = cartOptionalStringClosestToHome(cart, tg->tdb, FALSE,
 	"chromFilter");
 
 struct bbiFile *bbi =  fetchBbiForTrack(tg);
 struct lm *lm = lmInit(0);
 struct bigBedInterval *bb, *bbList =  bigBedIntervalQuery(bbi, chromName, winStart, winEnd, 0, lm);
 int fieldCount = 11;
 char *bedRow[fieldCount];
 char startBuf[16], endBuf[16];
 
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
     bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow));
     if ((optionChrStr != NULL) && !startsWith(optionChrStr, bedRow[7]))
         continue;
 
     if (chainCart->scoreFilter >0)
         {
         unsigned score = sqlUnsigned(bedRow[4]);
         if  (score < chainCart->scoreFilter)
             continue;
         }
 
     struct bed *bed = bedLoadN(bedRow, 6);
     lf = bedMungToLinkedFeatures(&bed, tg->tdb, fieldCount,
         0, 1000, FALSE);
 
+    lf->qSize = sqlUnsigned(bedRow[8]);
+
     if (*bedRow[5] == '-')
 	{
 	lf->orientation = -1;
         qs = sqlUnsigned(bedRow[8]) - sqlUnsigned(bedRow[10]);
 	}
     else
         {
 	lf->orientation = 1;
 	qs = sqlUnsigned(bedRow[9]);
 	}
 
     int len = strlen(bedRow[7]) + 32;
     lf->name = needMem(len);
     if (!doSnake)
         safef(lf->name, len, "%s %c %dk", bedRow[7], *bedRow[5], qs/1000);
     else
         safef(lf->name, len, "%s", bedRow[7]);
     lf->extra = cloneString(bedRow[3]);
     lf->components = NULL;
     slAddHead(&list, lf);
     }
 
 /* Make sure this is sorted if in full mode. Sort by score when
  * coloring by score and in dense */
 if (tg->visibility != tvDense)
     slSort(&list, linkedFeaturesCmpStart);
 else if ((tg->visibility == tvDense) &&
 	(chainCart->chainColor == chainColorScoreColors))
     slSort(&list, chainCmpScore);
 else
     slReverse(&list);
 tg->items = list;
 
 bbiFileClose(&bbi);
 
 loadLinks(tg, winStart, winEnd, tvFull);
 maybeLoadSnake(tg);
 }
 
 void chainLoadItems(struct track *tg)
 /* Load up all of the chains from correct table into tg->items
  * item list.  At this stage to conserve memory for other tracks
  * we don't load the links into the components list until draw time. */
 {
 boolean doSnake = cartOrTdbBoolean(cart, tg->tdb, "doSnake" , FALSE);
 char *table = tg->table;
 struct chain chain;
 int rowOffset;
 char **row;
 struct sqlConnection *conn = hAllocConn(database);
 struct sqlResult *sr = NULL;
 struct linkedFeatures *list = NULL, *lf;
 int qs;
 char *optionChrStr;
 char extraWhere[128] ;
 struct cartOptions *chainCart;
 
 chainCart = (struct cartOptions *) tg->extraUiData;
 
 optionChrStr = skipLeadingSpaces(cartUsualStringClosestToHome(cart, tg->tdb,
     FALSE, "chromFilter", "All"));
 
 if (strlen(optionChrStr) > 0 && differentWord("All",optionChrStr))
     {
     sqlSafef(extraWhere, sizeof(extraWhere),
             "qName = \"%s\" and score > %d",optionChrStr,
             chainCart->scoreFilter);
     sr = hRangeQuery(conn, table, chromName, winStart, winEnd,
             extraWhere, &rowOffset);
     }
 else
     {
     if (chainCart->scoreFilter > 0)
         {
         sqlSafef(extraWhere, sizeof(extraWhere),
                 "score > \"%d\"",chainCart->scoreFilter);
         sr = hRangeQuery(conn, table, chromName, winStart, winEnd,
                 extraWhere, &rowOffset);
         }
     else
         {
         sr = hRangeQuery(conn, table, chromName, winStart, winEnd,
                 NULL, &rowOffset);
         }
     }
 while ((row = sqlNextRow(sr)) != NULL)
     {
     char buf[16];
     chainHeadStaticLoad(row + rowOffset, &chain);
     AllocVar(lf);
     lf->start = lf->tallStart = chain.tStart;
     lf->end = lf->tallEnd = chain.tEnd;
     lf->qSize = chain.qSize;
     lf->grayIx = maxShade;
     if (chainCart->chainColor == chainColorScoreColors)
 	{
 	float normScore = sqlFloat((row+rowOffset)[11]);
         lf->grayIx = hGrayInRange(normScore, 0, 100, maxShade+1);
 	lf->score = normScore;
 	}
     else
 	lf->score = chain.score;
 
     lf->filterColor = -1;
 
     if (chain.qStrand == '-')
 	{
 	lf->orientation = -1;
         qs = chain.qSize - chain.qEnd;
 	}
     else
         {
 	lf->orientation = 1;
 	qs = chain.qStart;
 	}
     int len = strlen(chain.qName) + 32;
     lf->name = needMem(len);
     if (!doSnake)
         safef(lf->name, len, "%s %c %dk", chain.qName, chain.qStrand, qs/1000);
     else
         safef(lf->name, len, "%s", chain.qName);
     safef(buf, sizeof(buf), "%d", chain.id);
     lf->extra = cloneString(buf);
     slAddHead(&list, lf);
     }
 
 /* Make sure this is sorted if in full mode. Sort by score when
  * coloring by score and in dense */
 if (tg->visibility != tvDense)
     slSort(&list, linkedFeaturesCmpStart);
 else if ((tg->visibility == tvDense) &&
 	(chainCart->chainColor == chainColorScoreColors))
     slSort(&list, chainCmpScore);
 else
     slReverse(&list);
 tg->items = list;
 loadLinks(tg, winStart, winEnd, tvFull);
 
 maybeLoadSnake(tg);
 
 /* Clean up. */
 sqlFreeResult(&sr);
 hFreeConn(&conn);
 }	/*	chainLoadItems()	*/
 
 static Color chainScoreColor(struct track *tg, void *item, struct hvGfx *hvg)
 {
 struct linkedFeatures *lf = (struct linkedFeatures *)item;
 
 return(tg->colorShades[lf->grayIx]);
 }
 
 static Color chainNoColor(struct track *tg, void *item, struct hvGfx *hvg)
 {
 return(tg->ixColor);
 }
 
 static void setNoColor(struct track *tg)
 {
 tg->itemColor = chainNoColor;
 tg->color.r = 0;
 tg->color.g = 0;
 tg->color.b = 0;
 tg->color.a = 255;
 tg->altColor.r = 127;
 tg->altColor.g = 127;
 tg->altColor.b = 127;
 tg->altColor.a = 255;
 tg->ixColor = MG_BLACK;
 tg->ixAltColor = MG_GRAY;
 }
 
 void chainMethods(struct track *tg, struct trackDb *tdb,
 	int wordCount, char *words[])
 /* Fill in custom parts of alignment chains. */
 {
 struct cartOptions *chainCart;
 
 AllocVar(chainCart);
 
 boolean normScoreAvailable = chainDbNormScoreAvailable(tdb);
 
 /*	what does the cart say about coloring option	*/
 chainCart->chainColor = chainFetchColorOption(cart, tdb, FALSE);
 int scoreFilterDefault = atoi(trackDbSettingOrDefault(tdb, "scoreFilter", "0"));
 chainCart->scoreFilter = cartUsualIntClosestToHome(cart, tdb,
 	FALSE, SCORE_FILTER, scoreFilterDefault);
 
 linkedFeaturesMethods(tg);
 tg->itemColor = lfChromColor;	/*	default coloring option */
 tg->exonArrowsAlways = TRUE;
 
 /*	if normScore column is available, then allow coloring	*/
 if (normScoreAvailable)
     {
     switch (chainCart->chainColor)
 	{
 	case (chainColorScoreColors):
 	    tg->itemColor = chainScoreColor;
 	    tg->colorShades = shadesOfGray;
 	    break;
 	case (chainColorNoColors):
 	    setNoColor(tg);
 	    break;
 	default:
 	case (chainColorChromColors):
 	    break;
 	}
     }
 else
     {
     char *optionStr;	/* this old option was broken before */
 
     optionStr = cartUsualStringClosestToHome(cart, tdb, FALSE, "color", "on");
     if (differentWord("on",optionStr))
 	{
 	setNoColor(tg);
 	chainCart->chainColor = chainColorNoColors;
 	}
     else
 	chainCart->chainColor = chainColorChromColors;
     }
 
 
 if (tg->isBigBed)
     tg->loadItems = bigChainLoadItems;
 else
     tg->loadItems = chainLoadItems;
 tg->drawItems = chainDraw;
 tg->mapItemName = lfMapNameFromExtra;
 tg->subType = lfSubChain;
 tg->extraUiData = (void *) chainCart;
 }