39e533cc9ffcb46d9c484dc09a387a031a739391
Merge parents 1c86690 f642bab
kate
  Mon Sep 24 16:35:53 2018 -0700
Updating from master

diff --cc src/hg/hgTracks/interactTrack.c
index 1365ee9,ce8ee7a..da67bef
--- src/hg/hgTracks/interactTrack.c
+++ src/hg/hgTracks/interactTrack.c
@@@ -1,735 -1,609 +1,744 @@@
  /* interactTrack -- draw interaction between two genomic regions */
  
  /* Copyright (C) 2018 The Regents of the University of California 
   * See README in this or parent directory 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);
  }
  
 -void interactLoadItems(struct track *tg)
 +static void loadAndFilterItems(struct track *tg)
  /* Load all interact items in region */
  {
  loadSimpleBedWithLoader(tg, (bedItemLoader)interactLoadAndValidate);
  
  if (slCount(tg->items) == 0 && tg->limitedVisSet)
      {
      // too many items to display
      // borrowed behaviors in bamTrack and vcfTrack
      // TODO BRANEY: make this behavior generic for bigBeds
      // (bigBedSelectRange)
      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);
  for (inter = tg->items; inter; inter = next)
      {
      next = inter->next;
      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 interactDrawLeftLabels(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 */
 +{
 +}
 +
 +void interactFreeItems(struct track *tg)
 +/* Free up interact items track */
 +{
 +interactFreeList((struct interact **)(&tg->items));
 +}
 +
 +static boolean isBedMode(struct track *tg)
 +/* Pack and squish modes display using BED linked features code */
 +{
 +return tg->visibility == tvPack || tg->visibility == tvSquish;
 +}
 +
 +static struct linkedFeatures *interactToLf(struct interact *inter, boolean doColor)
 +/* Convert interact BED to linkedFeatures */
 +{
 +struct bed *bed = interactToBed(inter);
 +struct linkedFeatures *lf = lfFromBed(bed);
 +if (doColor)
 +    {
 +    lf->extra = (void *)USE_ITEM_RGB;   /* signal for coloring */
 +    lf->filterColor = bed->itemRgb;
 +    }
 +bedFree(&bed);
 +// TODO: use lfFromBedExtra with scoreMin, scoreMax ?
 +return lf;
 +}
 +
++static boolean isMergeMode(struct track *tg)
++/* Packed mode with merging on targets */
++{
++return cartBoolean(cart, "interactTargetMerge");
++}
++
 +void interactLoadItems(struct track *tg)
 +/* Load interact items in interact format */
 +{
 +loadAndFilterItems(tg);
 +if (isBedMode(tg))
 +    {
 +    // convert to BEDs for linked feature display
 +    struct interact *inters = tg->items, *inter;
 +    struct linkedFeatures *lfs = NULL, *lf;
 +    struct hash *intersMerge = hashNew(0);
-     boolean doMerge = cartVarExists(cart, "interactTargetMerge");
++    boolean doMerge = isMergeMode(tg);
 +    boolean doColor = !tg->colorShades;
 +    for (inter = inters; inter; inter = inter->next)
 +        {
 +        if (doMerge)
 +            {
 +            lf = (struct linkedFeatures *) hashFindVal(intersMerge, inter->targetName);
 +            if (lf)
 +                {
 +                // add a simple feature for this source to the linked feature
 +                struct simpleFeature *sf = NULL;
 +                AllocVar(sf);
 +                sf->start = inter->sourceStart;
 +                sf->end = inter->sourceEnd;
 +                struct simpleFeature *sfs = lf->components;
 +                slAddHead(&sfs, sf);
 +                lf->components = sfs;
 +                // TODO: consider averaging score
 +
 +/*
 +                // FIXME: just for demo
 +                struct bed *tempBed = interactToBed(inter);
 +                if (orientFromChar(tempBed->strand[0]) != lf->orientation)
 +                    lf->orientation = 0;
 +                bedFree(&tempBed);
 +*/
 +                }
 +            else
 +                {
 +                // create a linked feature for this target
 +                lf = interactToLf(inter, doColor);
 +                lf->name = inter->targetName;
 +                lf->tallStart = inter->targetStart;
 +                lf->tallEnd = inter->targetEnd;
 +                lf->orientation = 0;
 +                hashAdd(intersMerge, lf->name, lf);
 +                }
 +            }
 +        else 
 +            {
 +            lf = interactToLf(inter, doColor);
 +            slAddHead(&lfs, lf);
 +            }
 +        }
 +    if (doMerge)
 +        {
 +        // sort simplefeatures and adjust bounds of merged features
 +        struct hashEl *el, *els = hashElListHash(intersMerge);
 +        for (el = els; el; el = el->next)
 +            {
 +            lf = (struct linkedFeatures *)el->val;
 +            linkedFeaturesSortAndBound(lf);
 +            slAddHead(&lfs, lf);
 +            }
 +        slSort(&lfs, linkedFeaturesCmp);
 +        }
 +    else
 +        {
 +        slReverse(&lfs);
 +        }
 +    tg->items = lfs;
 +    // TODO: consider freeing interact items
 +    }
 +else
 +    {
 +    tg->mapsSelf = TRUE;
 +    tg->totalHeight = interactTotalHeight;
 +    tg->drawLeftLabels = interactDrawLeftLabels;
 +    tg->freeItems = interactFreeItems;
 +    }
 +}
 +
  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;
  }
  
 -void interactRegionCenters(struct interact *inter, int *sourceCenter, int *targetCenter)
 -/* Return genomic position of endpoint centers */
 -{
 -assert(sourceCenter);
 -assert(targetCenter);
 -*sourceCenter = interactRegionCenter(inter->sourceStart, inter->sourceEnd);
 -*targetCenter = interactRegionCenter(inter->targetStart, inter->targetEnd);
 -}
 -
  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;
  }
  
  struct interactTrackInfo {
      boolean isDirectional; // source and target are distinct item types
      char *offset;          // which end to draw offset (source or target)
      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;
  
  struct interactTrackInfo *interactGetTrackInfo(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 = NULL;
  AllocVar(tInfo);
  tInfo->doOtherLabels = TRUE;
  tInfo->isDirectional = interactUiDirectional(tg->tdb);
  tInfo->offset = interactUiOffset(tg->tdb);
  tInfo->drawUp = trackDbSettingClosestToHomeOn(tg->tdb, INTERACT_UP);
  
  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;
  return tInfo;
  }
  
  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;
  }
  
  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);
  }
  
 -void interactDrawItems(struct track *tg, int seqStart, int seqEnd,
 +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 structures. */
  {
  #define DRAW_LINE       0
  #define DRAW_CURVE      1
  #define DRAW_ELLIPSE    2
  
  // 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
  struct interactTrackInfo *tInfo = interactGetTrackInfo(tg, seqStart, hvg, xOff, font, scale);
  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 for overlapping
  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);
              if (drawUp)
                  yPos = flipY(tg, yPos);
              hvGfxTextCentered(hvg, x, yPos + 2, 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)/2 + tg->customInt;
          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 interactDrawLeftLabels(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 */
 +void interactLinkedFeaturesDrawAt(struct track *tg, void *item,
 +                          struct hvGfx *hvg, int xOff, int y, double scale,
 +                          MgFont *font, Color color, enum trackVisibility vis)
 +/* Draw a item with target in contrasting color */
 +{
 +linkedFeaturesDrawAt(tg, item, hvg, xOff, y, scale, font, color, vis);
 +
++if (isMergeMode(tg))
+     {
 +    struct linkedFeatures *lf = item;
 +    // TODO: use magenta if track isn't colored ?
 +    drawScaledBox(hvg, lf->tallStart, lf->tallEnd, scale, xOff, y, tg->heightPer, MG_BLACK);
 +    }
++}
 +
 +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. */
 +{
 +if (isBedMode(tg))
 +    {
 +    tg->drawItemAt = interactLinkedFeaturesDrawAt;
 +    linkedFeaturesDraw(tg, seqStart, seqEnd, hvg, xOff, yOff, width, font, color, vis);
 +    }
 +else
 +    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 pack and squish modes 
  tg->loadItems = interactLoadItems;
  tg->drawItems = interactDrawItems;
 -tg->drawLeftLabels = interactDrawLeftLabels;
 -tg->totalHeight = interactTotalHeight;
 -tg->mapsSelf = TRUE;
  }
  
  void interactCtMethods(struct track *tg)
  /* Interact track methods for custom track */
  {
  interactMethods(tg);
  }