a7ab24571507f19cc40e4ef65800401b43000a0f
braney
  Mon Dec 6 13:16:27 2021 -0800
support itemRgb for bigPsl, remove overload of lf->extra field for
USE_ITEM_RGB and replace it with a reasonably named field in lf
(useItemRbg)

diff --git src/hg/hgTracks/interactTrack.c src/hg/hgTracks/interactTrack.c
index c9b2a9d..d701be3 100644
--- src/hg/hgTracks/interactTrack.c
+++ src/hg/hgTracks/interactTrack.c
@@ -1,908 +1,908 @@
 /* interactTrack -- draw interaction between two genomic regions */
 
 /* Copyright (C) 2018 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "obscure.h"
 #include "hgTracks.h"
 #include "bedCart.h"
 #include "bigWarn.h"
 #include "interact.h"
 #include "interactUi.h"
 
 static int interactTotalHeight(struct track *tg, enum trackVisibility vis)
 /* calculate height of all the interactions being displayed */
 {
 if ( tg->visibility == tvDense)
     return  tl.fontHeight;
 int min, max, deflt, current; 
 cartTdbFetchMinMaxPixels(cart, tg->tdb, 
                                 INTERACT_MINHEIGHT, INTERACT_MAXHEIGHT, atoi(INTERACT_DEFHEIGHT),
                                 &min, &max, &deflt, &current);
 return tg->height = current;
 }
 
 static Color interactItemColor(struct track *tg, void *item, struct hvGfx *hvg, int scoreMin, int scoreMax)
 /* Return color to draw an interaction */
 {
 struct interact *inter = item;
 if (tg->colorShades)
     {
     struct bed *bed = (struct bed *)inter;
     adjustBedScoreGrayLevel(tg->tdb, bed, scoreMin, scoreMax);
     return tg->colorShades[grayInRange(inter->score, 0, 1000)];
     }
 /*
  There must be a better way..., e.g.:
 
 unsigned red = COLOR_32_RED(inter->color);
 unsigned green = COLOR_32_GREEN(inter->color);
 unsigned blue = COLOR_32_BLUE(inter->color);
 */
 unsigned red = (inter->color & 0xff0000) >> 16;
 unsigned green = (inter->color & 0xff00) >> 8;
 unsigned blue = inter->color & 0xff;
 return hvGfxFindColorIx(hvg, red, green, blue);
 }
 
 boolean interactSourceInWindow(struct interact *inter)
 /* True if midpoint of source is on screen */
 {
 unsigned s = interactRegionCenter(inter->sourceStart, inter->sourceEnd);
 return (s >= winStart) && (s < winEnd);
 }
 
 boolean interactTargetInWindow(struct interact *inter)
 /* True if midpoint of target is on screen */
 {
 unsigned t = interactRegionCenter(inter->targetStart, inter->targetEnd);
 return (t >= winStart) && (t < winEnd);
 }
 
 static void loadAndFilterItems(struct track *tg)
 /* Load all interact items in region */
 {
 loadSimpleBedWithLoader(tg, (bedItemLoader)interactLoadAndValidate);
 
 // if the summary is filled in then the number of items in the region is greater than maxItems.
 if (tg->summary != NULL)
     {
     // too many items to display
     tg->drawItems = bigDrawWarning;
     tg->networkErrMsg = "Too many items in display (zoom in)"; 
     tg->totalHeight = bigWarnTotalHeight;
     return;
     }
 
 // filters
 struct interact *inter, *next, *filteredItems = NULL;
 int count = slCount(tg->items);
 
 // exclude if missing endpoint(s) in window
 char *endsVisible = cartUsualStringClosestToHome(cart, tg->tdb, FALSE,
                             INTERACT_ENDS_VISIBLE, INTERACT_ENDS_VISIBLE_DEFAULT);
 char *scoreFilter = cartOrTdbString(cart, tg->tdb, "scoreFilter", NULL);
 int minScore = 0;
 if (scoreFilter)
     minScore = atoi(scoreFilter);
 
 for (inter = tg->items; inter; inter = next)
     {
     next = inter->next;
 
     if (inter->score < minScore)
         continue;
 
     if (differentString(endsVisible, INTERACT_ENDS_VISIBLE_ANY))
         {
         boolean sOnScreen = interactSourceInWindow(inter);
         boolean tOnScreen = interactTargetInWindow(inter);
         if (sameString(endsVisible, INTERACT_ENDS_VISIBLE_TWO))
             {
             if (!(sOnScreen && tOnScreen))
                 continue;
             }
         if (sameString(endsVisible, INTERACT_ENDS_VISIBLE_ONE))
             {
             if (!(sOnScreen || tOnScreen))
                 continue;
             }
         }
     slAddHead(&filteredItems, inter);
     }
 
 slReverse(&filteredItems);
 // consider sorting by score/value so highest scored items draw last (on top)
 if (slCount(filteredItems) != count)
     labelTrackAsFiltered(tg);
 tg->items = filteredItems;
 }
 
 void interactNoLeftLabels(struct track *tg, int seqStart, int seqEnd,
     struct hvGfx *hvg, int xOff, int yOff, int width, int height,
     boolean withCenterLabels, MgFont *font,
     Color color, enum trackVisibility vis)
 /* Override default */
 {
 }
 
 /* Interaction displayed as a linked feature */
 
 struct interactLfEndNames
 /* Stash source and target names when converted to linked feature */
     {
     char *source;
     char *target;
     };
 
 static void setInteractLfEndNames(struct linkedFeatures *lf, char *sourceName, char *targetName)
 /* Stash endpoint names in linked feature */
 {
 struct interactLfEndNames *ends = NULL;
 AllocVar(ends);
 ends->source = sourceName;
 ends->target = targetName;
 lf->original = ends;    // coopt this void * field (would have preferred to use extra)
 }
 
 static struct interactLfEndNames *getInteractLfEndNames(struct linkedFeatures *lf)
 /* Retrieve endpoint names from linked feature */
 {
 return (struct interactLfEndNames *)lf->original;
 }
 
 void interactFreeItems(struct track *tg)
 /* Free up interact items track */
 {
 interactFreeList((struct interact **)(&tg->items));
 }
 
 void interactLfSortAndBound(struct linkedFeatures *lf)
 /* Sort simpleFeatures in the linkedFeature and set start and end based on simpleFetaures */
 // TODO: dedupe the simpleFeatures ?
 {
 struct simpleFeature *sfLast, *sf, *sfs = lf->components;
 slSort(&sfs, simpleFeatureCmp);
 lf->components = sfs;
 sfLast = (struct simpleFeature *)slLastEl(sfs);
 int start = sfs->start;
 int end = sfLast->end;
 for (sf = sfs; sf != NULL; sf = sf->next)
     {
     if (sf->start < start)
         start = sf->start;
     if (sf->end > end)
         end = sf->end;
     }
 lf->start = start;
 lf->end = end;
 }
 
 static struct linkedFeatures *interactToLf(struct interact *inter, boolean doColor)
 /* Convert interact BED to linkedFeatures */
 {
 struct bed *bed = interactToBed(inter);
 struct linkedFeatures *lf = lfFromBed(bed);
 
 // save source and target names to extra field of linked feature, so we can display them 
 //      in pack mode
 // TODO: code to free
 setInteractLfEndNames(lf, cloneString(inter->sourceName), cloneString(inter->targetName));
 
 // not sure why this is needed -- lfFromBed seems to reorder blocks, sometimes ?
 interactLfSortAndBound(lf);
 if (doColor)
     {
-    lf->extra = (void *)USE_ITEM_RGB;   /* signal for coloring */
+    lf->useItemRgb = TRUE;
     lf->filterColor = bed->itemRgb;
     }
 bedFree(&bed);
 return lf;
 }
 
 static boolean isLinkedFeaturesMode(struct track *tg)
 /* Determine if linked feature display will be used */
 {
 return (tg->visibility != tvFull);
 }
 
 int interactSize(struct interact *inter)
 /* Compute length of interaction (distance between middle of each region) in bp */
 {
 int sourceCenter = 0, targetCenter = 0;
 interactRegionCenters(inter, &sourceCenter, &targetCenter);
 return abs(targetCenter - sourceCenter);
 }
 
 int getX(int pos, int seqStart, double scale, int xOff)
 /* Get x coordinate of a genomic location. Return -1 if off-screen */
 {
 if (pos < seqStart)
     return -1;
 return ((double)(pos - seqStart + .5) * scale) + xOff;
 }
 
 static void setYOff(struct track *tg, int yOff)
 /* Stash y offset for this track */
 {
 tg->customInt = yOff;
 }
 
 static int getYOff(struct track *tg)
 /* Get y offset for this track (stashed by DrawItems) */
 {
 return tg->customInt;
 }
 
 static int flipY(struct track *tg, int y)
     /* Invert y coordinate if flipped display is requested */
 {
     int yOff = getYOff(tg);
     int flipped = yOff + tg->height + yOff - y;
     return flipped;
 }
 
 struct interactTrackInfo {
     boolean isDirectional; // source and target are distinct item types
     char *offset;          // which end to draw offset (source or target)
     char *clusterMode;     // cluster by source or target (or no cluster if NULL)
     boolean drawUp;          // draw interactions with peak up (hill)
     boolean doOtherLabels;  // true to suppress labels on other chrom items (prevent overlap)
     int maxSize;        // longest interaction (midpoint to midpoint) in bp
     int fontHeight;
     int sameCount;      // number of same chromosome interactions in window
     int sameHeight;     // vertical space for same chromosome interactions
     int otherCount;     // number of other chromosome interactions in window
     int otherHeight;    // vertical space for other chromosome interactions
 } interactTrackInfo;
 
 static void interactGetLayoutInfo(struct track *tg, int seqStart, struct hvGfx *hvg,                                                       int xOff, MgFont *font, double scale)
     /* Get layout info from interact items in window */
 {
     struct interactTrackInfo *tInfo = tg->customPt;
     tInfo->doOtherLabels = TRUE;
 
     char *otherChrom = NULL;
     int prevLabelEnd = 0, prevLabelStart = 0;
     char *prevLabel = 0;
     struct interact *inter;
 
     for (inter = (struct interact *)tg->items; inter; inter = inter->next)
         {
         otherChrom = interactOtherChrom(inter);
         if (otherChrom == NULL)
             {
             tInfo->sameCount++;
             // determine maximum interaction size, for later use laying out 'peaks'
             int size = interactSize(inter);
             if (size > tInfo->maxSize)
                 tInfo->maxSize = size;
             }
         else
             {
             tInfo->otherCount++;
             // suppress interchromosomal labels if they overlap
             if (!tInfo->doOtherLabels)
                 continue;
             int labelWidth = vgGetFontStringWidth(hvg->vg, font, otherChrom);
             int x = getX(inter->chromStart, seqStart, scale, xOff);
             int labelStart = round((double)(x - labelWidth)/2);
             int labelEnd = labelStart + labelWidth - 1;
             if (labelStart <= prevLabelEnd && 
                     !(labelStart == prevLabelStart && labelEnd == prevLabelEnd && 
                         sameString(otherChrom, prevLabel)))
                 tInfo->doOtherLabels = FALSE;
             prevLabelStart = labelStart;
             prevLabelEnd = labelEnd;
             prevLabel = otherChrom;
             }
         }
     tInfo->fontHeight = vgGetFontPixelHeight(hvg->vg, font);
     tInfo->otherHeight = (tInfo->otherCount) ? 3 * tInfo->fontHeight : 0;
     tInfo->sameHeight = (tInfo->sameCount) ? tg->height - tInfo->otherHeight : 0;
 }
 
 
 static int interactRightPixels(struct track *tg, void *item)
     /* Return number of pixels we need to the right, in linked features mode. */
 {
     struct interactTrackInfo *tInfo = tg->customPt;
     if (tInfo->clusterMode || !(tg->visibility == tvPack || tg->visibility == tvFull))
         return 0;
     struct linkedFeatures *lf = (struct linkedFeatures *)item;
     struct interactLfEndNames *ends = getInteractLfEndNames(lf);
     char *rightLabel = ends->target;
     if (lf->orientation != 0)
         rightLabel = (lf->orientation < 0 ? ends->source : ends->target);
     return mgFontStringWidth(tl.font, rightLabel);
 }
 
 static char *interactLfLeftEndName(struct track *tg, void *item)
     /* Return name of left end in single interaction linked feature, for left label */
 {
     struct linkedFeatures *lf = (struct linkedFeatures *)item;
     struct interactLfEndNames *ends = getInteractLfEndNames(lf);
     return (lf->orientation < 0 ? ends->target : ends->source);
 }
 
 static void interactLfMapItem(struct track *tg, struct hvGfx *hvg, void *item,
         char *itemName, char *mapItemName, int start, int end,
         int x, int y, int width, int height)
     /* Draw mapbox on interact item when displayed as linked feature */
 {
     struct linkedFeatures *lf = (struct linkedFeatures *)item;
     // restore full item name (using end name for label)
     itemName = mapItemName = lf->name;
     linkedFeaturesMapItem(tg, hvg, item, itemName, mapItemName, start, end, x, y, width, height);
 }
 
 void interactLoadItems(struct track *tg)
     /* Load interact items in interact format */
 {
     loadAndFilterItems(tg);
 
     struct interactTrackInfo *tInfo = NULL;
     AllocVar(tInfo);
     tg->customPt = tInfo;
     tInfo->isDirectional = interactUiDirectional(tg->tdb);
     tInfo->offset = interactUiOffset(tg->tdb);
     tInfo->drawUp = trackDbSettingClosestToHomeOn(tg->tdb, INTERACT_UP);
     tInfo->clusterMode = interactUiClusterMode(cart, tg->track, tg->tdb);
 
 // conditional for now -- pending QA/JK weigh in
 #define MR_FORCE_PACK 1
 #ifdef MR_FORCE_PACK
     if (!tInfo->clusterMode && !isLinkedFeaturesMode(tg) && sameString(virtModeType, "default"))
 #else
     if (!tInfo->clusterMode && !isLinkedFeaturesMode(tg))
 #endif
         {
         // draw curve display
         tg->mapsSelf = TRUE;
         tg->totalHeight = interactTotalHeight;
         tg->drawLeftLabels = interactNoLeftLabels;
         tg->freeItems = interactFreeItems;
         return;
         }
 
     // convert to BEDs for linked feature display
     struct interact *inters = tg->items, *inter;
     struct linkedFeatures *lfs = NULL, *lf;
     struct hash *intersCluster = hashNew(0);
     boolean doColor = !tg->colorShades;
     if (!tInfo->clusterMode)
         {
         tg->itemName = interactLfLeftEndName;
         tg->mapItem = interactLfMapItem;
         }
     for (inter = inters; inter; inter = inter->next)
         {
         if (tInfo->clusterMode)
             {
             boolean byTarget = sameString(tInfo->clusterMode, INTERACT_CLUSTER_TARGET);
             // hash by source or target name
             char *name = (byTarget ? inter->targetName : inter->sourceName);
             lf = (struct linkedFeatures *) hashFindVal(intersCluster, name);
             if (lf)
                 {
                 // add a simple feature for the other end (source or target) to the linked feature
                 struct simpleFeature *sf = NULL;
                 AllocVar(sf);
 
                 // tweak interact struct for intrachromsomal item to ease next steps
                 if (differentString(inter->targetChrom, inter->sourceChrom))
                     {
                     inter->sourceStart = inter->targetStart = inter->chromStart;
                     inter->sourceEnd = inter->targetEnd = inter->chromEnd;
                     }
 
                 sf->start = (byTarget ? inter->sourceStart : inter->targetStart);
                 sf->end = (byTarget ? inter->sourceEnd : inter->targetEnd);
                 struct simpleFeature *sfs = lf->components;
                 slAddHead(&sfs, sf);
                 lf->components = sfs;
                 if (lf->filterColor != inter->color)
                     lf->filterColor = MG_GRAY;
                 }
             else
                 {
                 // create a linked feature for this cluster
                 lf = interactToLf(inter, doColor);
                 lf->orientation = 0;
                 lf->name = (byTarget ? inter->targetName : inter->sourceName);
                 lf->tallStart = (byTarget ? inter->targetStart : inter->sourceStart);
                 lf->tallEnd = (byTarget ? inter->targetEnd : inter->sourceEnd);
                 hashAdd(intersCluster, lf->name, lf);
                 }
             }
         else 
             {
             // packed or squish mode view of single interaction (not cluster)
             lf = interactToLf(inter, doColor);
             if (tInfo->isDirectional)
                 {
                 lf->tallStart = inter->targetStart;
                 lf->tallEnd = inter->targetEnd;
                 }
             else
                 {
                 lf->orientation = 0;
                 }
             slAddHead(&lfs, lf);
             }
         }
     if (tInfo->clusterMode)
         {
         // sort simplefeatures and adjust bounds of clustered features
         struct hashEl *el, *els = hashElListHash(intersCluster);
         for (el = els; el; el = el->next)
             {
             lf = (struct linkedFeatures *)el->val;
             interactLfSortAndBound(lf);
             slAddHead(&lfs, lf);
             }
         slSort(&lfs, linkedFeaturesCmp);
         }
     else
         {
         slReverse(&lfs);
         }
     tg->items = lfs;
     // TODO: consider freeing interact items
 }
 
 char *interactMouseover(struct interact *inter, char *otherChrom)
     /* Make mouseover text for an interaction */
 {
     struct dyString *ds = dyStringNew(0);
     if (isEmptyTextField(inter->name))
         {
         if (!isEmptyTextField(inter->exp))
             dyStringPrintf(ds, "%s ", inter->exp);
         if (otherChrom)
             dyStringPrintf(ds, "%s", otherChrom);
         else
             {
             char buf[4096];
             sprintLongWithCommas(buf, inter->chromEnd - inter->chromStart);
             dyStringPrintf(ds, "%s bp", buf);
             }
         }
     else
         dyStringPrintf(ds, "%s", inter->name);
     if (inter->score)
         dyStringPrintf(ds, " %d", inter->score);
     if (inter->value != 0.0)
         dyStringPrintf(ds, " %0.2f", inter->value);
     return dyStringCannibalize(&ds);
 }
 
 int regionFootWidth(int start, int end, double scale)
     /* Return half foot width in pixels */
 {
     unsigned size = end - start;
     int width = scale * (double)size / 2;
     if (width == 0)
         width = 1;
     return width;
 }
 
 /* Draw helper routines */
 
 #define DRAW_LINE       0
 #define DRAW_CURVE      1
 #define DRAW_ELLIPSE    2
 
 static void drawFoot(struct track *tg, struct hvGfx *hvg, char *seq, int seqStart, int seqEnd, 
         int x, int y, int width, Color color, boolean drawUp, struct hash *footHash)
 /* Draw interaction end, 2 pixels high.  Force to black if it exactly overlaps another */
 {
 char buf[256];
 safef(buf, sizeof(buf), "%s:%d-%d", seq, seqStart, seqEnd);
 char *pos = cloneString(buf);
 Color footColor = color;
 if (hashLookup(footHash, pos))
     footColor = MG_BLACK;
 else
     hashStore(footHash, pos);
 if (drawUp)
     y = flipY(tg, y) - 2;
 hvGfxBox(hvg, x, y, width, 2, footColor);
 }
 
 static void drawLine(struct track *tg, struct hvGfx *hvg, int x1, int y1, int x2, int y2, 
                         Color color, boolean isDashed, boolean drawUp)
 /* Draw vertical or horizontal */
 {
 if (drawUp)
     {
     y1 = flipY(tg, y1);
     y2 = flipY(tg, y2);
     }
 if (isDashed)
     hvGfxDottedLine(hvg, x1, y1, x2, y2, color, TRUE);
 else
     hvGfxLine(hvg, x1, y1, x2, y2, color);
 }
 
 static void drawFootMapbox(struct track *tg, struct hvGfx *hvg, int start, int end, char *item, char *status,
                         int x, int y, int width, Color peakColor, Color highlightColor, boolean drawUp)
 /* Draw grab box and add map box */
 {
 // Add var to identify endpoint ('foot'), or NULL if no name for endpoint */
 char *clickArg = NULL;
 if (!isEmptyTextField(item))
     {
     char buf[256];
     safef(buf, sizeof(buf),"foot=%s", cgiEncode(item));
     clickArg = cloneString(buf);
     }
 char *itemBuf = isEmptyTextField(item) ? status : item;
 if (drawUp)
     y = flipY(tg, y) - 3;
 hvGfxBox(hvg, x-1, y, 3, 2, peakColor);
 hvGfxBox(hvg, x, y, 1, 1, highlightColor);
 mapBoxHgcOrHgGene(hvg, start, end, x - width, y, width * 2, 4,
                    tg->track, item, itemBuf, NULL, TRUE, clickArg);
 }
 
 void drawPeakMapbox(struct track *tg, struct hvGfx *hvg, int seqStart, int seqEnd, 
                         char *item, char *status, int x, int y, 
                         Color peakColor, Color highlightColor, boolean drawUp)
 /* Draw grab box and add map box */
 {
 if (drawUp)
     y = flipY(tg, y);
 hvGfxBox(hvg, x-1, y-1, 3, 3, peakColor);
 hvGfxBox(hvg, x, y, 1, 1, highlightColor);
 mapBoxHgcOrHgGene(hvg, seqStart, seqEnd, x-1, y-1, 3, 3,
                    tg->track, item, status, NULL, TRUE, NULL);
 }
 
 static void drawInteractItems(struct track *tg, int seqStart, int seqEnd,
         struct hvGfx *hvg, int xOff, int yOff, int width, 
         MgFont *font, Color color, enum trackVisibility vis)
 /* Draw a list of interact items with connectors (e.g. curves) */
 {
 // Determine drawing mode
 int draw = DRAW_LINE;
 boolean doDashes = FALSE;
 if (vis != tvDense)
     {
     char *drawMode = cartUsualStringClosestToHome(cart, tg->tdb, FALSE,
                                 INTERACT_DRAW, INTERACT_DRAW_DEFAULT);
     if (sameString(drawMode, INTERACT_DRAW_CURVE))
         draw = DRAW_CURVE;
     else if (sameString(drawMode, INTERACT_DRAW_ELLIPSE))
     draw = DRAW_ELLIPSE;
     doDashes = cartUsualBooleanClosestToHome(cart, tg->tdb, FALSE,
                                 INTERACT_DIRECTION_DASHES, INTERACT_DIRECTION_DASHES_DEFAULT);
     }
 double scale = scaleForWindow(width, seqStart, seqEnd);
 struct interact *inter = NULL;
 char buffer[1024];
 char itemBuf[2048];
 
 // Gather info for layout
 interactGetLayoutInfo(tg, seqStart, hvg, xOff, font, scale);
 struct interactTrackInfo *tInfo = (struct interactTrackInfo *)tg->customPt;
 setYOff(tg, yOff);      // TODO: better to stash this in tInfo, and save that in track struct */
 int highlightColor = MG_WHITE;
 boolean drawUp = trackDbSettingClosestToHomeOn(tg->tdb, INTERACT_UP) && vis == tvFull;
 
 // Get spectrum range
 int scoreMin = atoi(trackDbSettingClosestToHomeOrDefault(tg->tdb, "scoreMin", "0"));
 int scoreMax = atoi(trackDbSettingClosestToHomeOrDefault(tg->tdb, "scoreMax", "1000"));
 
 // Draw items
 struct hash *footHash = hashNew(0);     // track feet so we can override color to black 
 struct hash *footHashOther = hashNew(0);  // has for items on other chrom
 for (inter = (struct interact *)tg->items; inter; inter = inter->next)
     {
     char *otherChrom = interactOtherChrom(inter);
     safef(itemBuf, sizeof itemBuf, "%s", inter->name);
     char *statusBuf = interactMouseover(inter, otherChrom);
 
     // Pick colors
 
     #define MG_LIGHT_MAGENTA    0xffffbbff
     #define MG_LIGHT_GRAY       0xff909090
     color = interactItemColor(tg, inter, hvg, scoreMin, scoreMax);
     if (vis == tvDense && otherChrom && color == MG_BLACK)
         // use highlight color for other chrom items in dense mode
         color = MG_LIGHT_MAGENTA;
     int peakColor = (color == MG_BLACK || tg->colorShades) ? MG_LIGHT_MAGENTA : MG_LIGHT_GRAY;
     
     if (otherChrom)
         {
         // different chromosomes
         //      draw below same chrom items, if any
         int height = 0;
         int yOffOther = yOff;
         if (vis == tvDense)
             {
             height = tg->height;
             }
         else
             {
             height = tInfo->otherHeight/2;
             yOffOther = yOff + tInfo->sameHeight;
             }
         unsigned r = interactRegionCenter(inter->chromStart, inter->chromEnd);
         int x = getX(r, seqStart, scale, xOff); 
         int footWidth = regionFootWidth(inter->chromStart, inter->chromEnd, scale);
         unsigned yPos = yOffOther + height;
 
         // draw the foot (2 pixels high)
         drawFoot(tg, hvg, inter->chrom, inter->chromStart, inter->chromEnd, 
                 x - footWidth, yOffOther, footWidth + footWidth + 1, color, drawUp, footHashOther);
 
         // draw the vertical
         boolean isReversed = tInfo->isDirectional && 
                                 differentString(inter->chrom, inter->sourceChrom);
         drawLine(tg, hvg, x, yOffOther, x, yPos, color, isReversed && doDashes, drawUp);
         
         if (vis == tvDense)
             continue;
 
         // add map box to foot
 
         char *nameBuf = (inter->chromStart == inter->sourceStart ?      
                         inter->sourceName : inter->targetName);
         drawFootMapbox(tg, hvg, inter->chromStart, inter->chromEnd, nameBuf, statusBuf, 
                         x - footWidth, yOffOther, footWidth, peakColor, highlightColor, drawUp);
 
         // add map box to vertical
         mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, x - 2, yOffOther, 4, 
                             height, tg->track, itemBuf, statusBuf, NULL, TRUE, NULL);
         if (tInfo->doOtherLabels)
             {
             // draw label
             safef(buffer, sizeof buffer, "%s", sameString(inter->chrom, inter->sourceChrom) ?
                                         inter->targetChrom : inter->sourceChrom);
             yPos += 3;
             if (drawUp)
                 yPos = flipY(tg, yPos + 6);
             hvGfxTextCentered(hvg, x, yPos, 4, 4, MG_BLUE, font, buffer);
             int labelWidth = vgGetFontStringWidth(hvg->vg, font, buffer);
 
             // add map box to label
             mapBoxHgcOrHgGene(hvg, inter->chromStart, inter->chromEnd, x - labelWidth/2, 
                     yPos, labelWidth, tInfo->fontHeight, tg->track, itemBuf, statusBuf, 
                     NULL, TRUE, NULL);
             }
         continue;
         }
 
     // Draw same chromosome interaction
 
     // source region
     unsigned s = interactRegionCenter(inter->sourceStart, inter->sourceEnd);
     int sX = getX(s, seqStart, scale, xOff); 
     int sWidth = regionFootWidth(inter->sourceStart, inter->sourceEnd, scale);
     boolean sOnScreen = (s >= seqStart) && (s< seqEnd);
 
     // target region
     unsigned t = interactRegionCenter(inter->targetStart, inter->targetEnd);
     int tX = getX(t, seqStart, scale, xOff);
     int tWidth = regionFootWidth(inter->targetStart,inter->targetEnd, scale);
     boolean tOnScreen = (t >= seqStart) && (t< seqEnd);
 
     boolean isReversed = (tInfo->isDirectional && t < s);
     int interSize = abs(t - s);
     int peakHeight = (tInfo->sameHeight - 15) * ((double)interSize / tInfo->maxSize) + 10;
     int peak = yOff + peakHeight;
     if (vis == tvDense)
         peak = yOff + tg->height;
 
     // NOTE: until time permits, force to rectangle when in reversed strand mode.
 
     int yTarget = yOff;
     int ySource = yOff;
     if (tInfo->offset && draw != DRAW_ELLIPSE)
         // ellipse code doesn't support assymetrical ends
         {
         int yOffset = tg->height/10 + 1;
         if (sameString(tInfo->offset, INTERACT_OFFSET_TARGET))
             yTarget = yOff + yOffset;
         else if (sameString(tInfo->offset, INTERACT_OFFSET_SOURCE))
             ySource = yOff + yOffset;
         }
     unsigned footColor = color;
 
     if (sOnScreen)
         {
         drawFoot(tg, hvg, inter->sourceChrom, inter->sourceStart, inter->sourceEnd,
                             sX - sWidth, ySource, sWidth + sWidth + 1, footColor, drawUp, footHash);
         if (vis == tvDense || !tOnScreen || draw == DRAW_LINE || hvg->rc)
             {
             // draw vertical from foot to peak
             drawLine(tg, hvg, sX, ySource, sX, peak, color, isReversed && doDashes, drawUp);
             }
         }
     if (tOnScreen)
         {
         drawFoot(tg, hvg, inter->targetChrom, inter->targetStart, inter->targetEnd,
                             tX - tWidth, yTarget, tWidth + tWidth + 1, footColor, drawUp, footHash);
         if (vis == tvDense || !sOnScreen || draw == DRAW_LINE || hvg->rc)
             {
             // draw vertical from foot to peak
             drawLine(tg, hvg, tX, yTarget, tX, peak, color, isReversed && doDashes, drawUp);
             }
         }
     if (vis == tvDense)
         continue;
 
     // Full mode: add map boxes and draw interaction
 
     if (sOnScreen)
         {
         // draw grab box and map box to source region
         drawFootMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->sourceName, 
                             statusBuf, sX, ySource, sWidth, peakColor, highlightColor, drawUp);
         }
     if (tOnScreen)
         {
         // draw grab box and add map box to target region
         drawFootMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->targetName, 
                         statusBuf, tX, yTarget, tWidth, 
                         tInfo->isDirectional ? MG_MAGENTA : peakColor, highlightColor, drawUp);
         }
     if ((s < seqStart && t < seqStart) || (s > seqEnd && t > seqEnd))
         continue;
 
     // Draw interaction and map boxes
     int lowerX = 0, upperX = 0;
     if (s < t)
         {
         lowerX = sOnScreen ? sX : xOff;
         upperX = tOnScreen ? tX : xOff + width;
         }
     else
         {
         lowerX = tOnScreen ? tX : xOff;
         upperX = sOnScreen ? sX : xOff + width;
         }
     if (draw == DRAW_LINE || !sOnScreen || !tOnScreen || hvg->rc)
         {
         // draw horizontal line between region centers at 'peak' height
         drawLine(tg, hvg, lowerX, peak, upperX, peak, color, isReversed && doDashes, drawUp);
 
         // draw grab box and map box on mid-point of horizontal line
         int xMap = lowerX + (double)(upperX-lowerX)/2;
         drawPeakMapbox(tg, hvg, inter->chromStart, inter->chromEnd, itemBuf, statusBuf,
                             xMap, peak, peakColor, highlightColor, drawUp);
         continue;
         }
     // Draw curves
     if (draw == DRAW_CURVE)
         {
         int peakX = ((upperX - lowerX + 1) / 2) + lowerX;
         int fudge = 30;
         int peakY = peak + fudge; // admittedly a hack (obscure how to define ypeak of curve)
         int y1 = isReversed ? yTarget : ySource;
         int y2 = isReversed ? ySource : yTarget;
         if (drawUp)
             {
             y1 = flipY(tg, y1);
             y2 = flipY(tg, y2);
             peakY = flipY(tg, peakY);
             }
         int maxY = hvGfxCurve(hvg, lowerX, y1, peakX, peakY, upperX, y2, color, isReversed && doDashes);
         // curve drawer does not use peakY as expected, so it returns actual max Y used
         // draw grab box and map box on peak
         if (drawUp)
             {
             maxY = (maxY - peakY + 1)/2 + tg->customInt;
             if (tInfo->offset)
                 maxY += abs(yTarget - ySource)/4;
             }
         drawPeakMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->name, statusBuf,
                             peakX, maxY, peakColor, highlightColor, drawUp);
         }
     else if (draw == DRAW_ELLIPSE)
         {
         // can not support offsets
         int yLeft = yOff + peakHeight;
         int yTop = yOff - peakHeight;
         int ellipseOrient = ELLIPSE_BOTTOM;
         if (drawUp)
             {
             ellipseOrient = ELLIPSE_TOP;
             yLeft = yOff + tg->height - peakHeight;
             yTop = yOff + tg->height + peakHeight;
             }
         hvGfxEllipseDraw(hvg, lowerX, yLeft, upperX, yTop, color, ellipseOrient,
                                 isReversed && doDashes);
         // draw grab box and map box on peak
         int maxY = peakHeight + yOff;
         int peakX = ((upperX - lowerX + 1) / 2) + lowerX;
         drawPeakMapbox(tg, hvg, inter->chromStart, inter->chromEnd, inter->name, statusBuf,
                             peakX, maxY, peakColor, highlightColor, drawUp);
         }
     }
 }
 
 void interactLinkedFeaturesDrawAt(struct track *tg, void *item,
                           struct hvGfx *hvg, int xOff, int y, double scale,
                           MgFont *font, Color color, enum trackVisibility vis)
 /* Draw an item with target in contrasting color */
 {
 struct linkedFeatures *lf = item;
 if (vis == tvDense)
     {
     lf->filterColor = slightlyDarkerColor(hvg, MG_GRAY);
                 // can't distinguish overlapping colors, so force to gray
     }
 struct interactTrackInfo *tInfo = tg->customPt;
 
 linkedFeaturesDrawAt(tg, item, hvg, xOff, y, scale, font, color, vis);
 
 if (tInfo->clusterMode)
     {
     struct simpleFeature *sf;
     int shortHeight = tg->heightPer/2;
     for (sf = lf->components;  sf; sf = sf->next)
         {
         if (sf->start > lf->tallStart && sf->end < lf->tallEnd)
             {
             drawScaledBox(hvg, sf->start, sf->end, scale, xOff, y + shortHeight/2,
                                 shortHeight, MG_WHITE);
             }
         }
     }
 else
     {
     struct simpleFeature *sf1 = lf->components, *sf2 = sf1->next;
     if (sf2 && sf2->start > lf->tallStart && sf2->end < lf->tallEnd)
         drawScaledBox(hvg, sf2->start, sf2->end, scale, xOff, y, tg->heightPer, MG_WHITE);
     if (vis == tvPack || vis == tvFull)
         {
         // add right label
         int x2 = round((double)((int)lf->end - winStart) * scale) + xOff;
         int x = x2 + tl.mWidth/2;
         struct interactLfEndNames *ends = getInteractLfEndNames(lf);
         char *rightLabel = (lf->orientation < 0 ? ends->source : ends->target);
         int w = mgFontStringWidth(font, rightLabel);
         hvGfxTextCentered(hvg, x, y, w, tg->heightPer, color, font, rightLabel);
         }
     }
 }
 
 void interactDrawItems(struct track *tg, int seqStart, int seqEnd,
         struct hvGfx *hvg, int xOff, int yOff, int width, 
         MgFont *font, Color color, enum trackVisibility vis)
 /* Draw a list of interact structures. */
 {
 struct interactTrackInfo *tInfo = (struct interactTrackInfo *)tg->customPt;
 #ifdef MR_FORCE_PACK
 if (tInfo->clusterMode || isLinkedFeaturesMode(tg) || differentString(virtModeType, "default"))
 #else
 if (tInfo->clusterMode || isLinkedFeaturesMode(tg))
 #endif
     {
     tg->drawItemAt = interactLinkedFeaturesDrawAt;
     linkedFeaturesDraw(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis);
     }
 else
     // curve, etc. connector display
     drawInteractItems(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis);
 }
 
 void interactMethods(struct track *tg)
 /* Interact track type methods */
 {
 tg->bedSize = 12;
 linkedFeaturesMethods(tg);         // for most vis and mode settings
 tg->loadItems = interactLoadItems;
 tg->itemRightPixels = interactRightPixels;
 tg->drawItems = interactDrawItems;
 }
 
 void interactCtMethods(struct track *tg)
 /* Interact track methods for custom track */
 {
 interactMethods(tg);
 }