text: /* sampleTracks - Ryan Weber's wiggle tracks. */
  
  /* Copyright (C) 2013 The Regents of the University of California 
   * See README in this or parent directory for licensing information. */
  
  #include "common.h"
  #include "hash.h"
  #include "linefile.h"
  #include "jksql.h"
  #include "hdb.h"
  #include "hgTracks.h"
  #include "sample.h"
  
  int sampleUpdateY( char *name, char *nextName, int lineHeight )
  /* only increment height when name root (minus the period if
   * there is one) is different from previous one.
    *This assumes that the entries are sorted by name as they would
    *be if loaded by hgLoadSample*/
  {
  int ret;
  char *tempstr = NULL;
  char *tempstr2 = NULL;
  
  tempstr = cloneString( name );
  if( strstr( name, "." ) != NULL )
     strtok( tempstr, "." );
  
  tempstr2 = cloneString( nextName );
  if( strstr( nextName, "." ) != NULL )
     strtok( tempstr2, "." );
  
  if( !sameString( tempstr, tempstr2 )) 
     ret = lineHeight; 
  else 
     ret = 0;
  
  freeMem(tempstr);
  freeMem(tempstr2);
  
  return(ret);
  }
  
  void samplePrintYAxisLabel( struct hvGfx *hvg, int y, struct track *track, char *labelString,
          double min0, double max0 )
  /*print a label for a horizontal y-axis line*/
  {
  double tmp;
  int fontHeight = tl.fontHeight;
  double ymin = y - (track->heightPer / 2) + fontHeight;
  int itemHeight0 = track->itemHeight(track, track->items);
  int inWid = insideX-gfxBorder*3;
  
  tmp = -whichSampleBin( atof(labelString), min0, max0, 1000 );
  tmp = (int)((double)ymin+((double)tmp)*(double)track->heightPer/1000.0+(double)track->heightPer)-fontHeight/2.0;
  if( !withCenterLabels ) tmp -= fontHeight;
  hvGfxTextRight(hvg, gfxBorder, tmp, inWid-1, itemHeight0, track->ixColor, tl.font, labelString );
  }
  
  
  static int sampleTotalHeight(struct track *tg, 
  	enum trackVisibility vis)
  /* Wiggle track will use this to figure out the height they use
  as defined in the cart */
  {
  struct slList *item;
  int lines = 0;
  
  int heightFromCart;
  char o1[128];
  
  safef( o1, 128, "%s.heightPer", tg->track);
  
  heightFromCart = atoi(cartUsualString(cart, o1, "50"));
  
  tg->lineHeight = max(tl.fontHeight+1, heightFromCart);
  tg->heightPer = tg->lineHeight - 1;
  switch (vis)
      {
      case tvFull:
  	lines = 1;
  	for (item = tg->items; item != NULL; item = item->next)
  	    {
  	    if( item->next != NULL )
  		if( sampleUpdateY( tg->itemName(tg, item), tg->itemName(tg, item->next), 1 ))
  		    lines++;
  	    }
  	tg->height = lines * tg->lineHeight;
  	break;
      case tvPack:
      case tvSquish:
          errAbort("Sorry can't handle pack in sampleTotalHeight (%s)", tg->track);
  	break;
      case tvDense:
  	tg->height = tg->lineHeight;
  	break;
      default:
          break;
      }
  return tg->height;
  }
  
  int consTotalHeight(struct track *tg, enum trackVisibility vis)
  /* Human/mouse conservation total height. */
  {
  if( vis == tvDense )
      return tg->height = tg->lineHeight = 10;
  else
      return sampleTotalHeight(tg, vis); 
  }
  
  struct linkedFeatures *lfFromSample(struct sample *sample)
  /* Return a linked feature from a full sample (wiggle) track. */
  {
  struct linkedFeatures *lf;
  struct simpleFeature *sf, *sfList = NULL;
  int grayIx = grayInRange(sample->score, 0, 1000);
  int start, i;
  unsigned *X = sample->samplePosition;
  int *Y = sample->sampleHeight;
  unsigned sampleCount = sample->sampleCount;
  
  assert(X != NULL && Y != NULL && sampleCount > 0);
  AllocVar(lf);
  lf->grayIx = grayIx;
  lf->name = cloneString(sample->name);
  lf->orientation = orientFromChar(sample->strand[0]);
  
  for (i=0; i<sampleCount; ++i)
      {
      AllocVar(sf);
      start = X[i] + sample->chromStart;
      sf->start = start;
  
      if( Y[i] < 0 )      /*hack for negative values not loading correctly*/
          sf->end = start;
      else if( Y[i] == 0 )
          sf->end = start + 1;
      else
          sf->end = start + Y[i];
  
      sf->grayIx = grayIx;
      slAddHead(&sfList, sf);
      }
  slReverse(&sfList);
  lf->components = sfList;
  linkedFeaturesBoundsAndGrays(lf);
  lf->start = sample->chromStart;
  lf->end = sample->chromEnd;
  return lf;
  }
  
  
  int whichSampleBin( double num, double thisMin, double thisMax, 
  	double binCount )
  /* Get bin value from num. */
  {
  return (num - thisMin) * binCount / (thisMax - thisMin);
  }
  
  double whichSampleNum( double bin, double thisMin, double thisMax, 
  	double binCount )
  /* gets range nums. from bin values*/
  {
  return( (thisMax - thisMin) / binCount * bin + thisMin );
  }
  
  
  int basePositionToXAxis( int base, int seqStart, int seqEnd, int
                  width, int xOff  ) 
  {
  double scale = scaleForPixels(width);
  double x1 = round((double)((int)base-seqStart)*scale) + xOff; 
  return(x1);
  }
  
  int humMusZoomLevel( void )
  {
  int zoom1 = 80000, zoom2 = 5000, zoom3 = 300; /* bp per data point */      
  int pixPerBase = (winEnd - winStart)/ tl.picWidth;
  if(pixPerBase >= zoom1)
      return(1);
  else if( pixPerBase >= zoom2 ) 
      return(2);
  else if(pixPerBase >= zoom3)
      return(3);
  else    
      return(0);
  }
  
  
  static void drawWiggleHorizontalLine( struct hvGfx *hvg, 
  	double where, double min0, double max0, 
  	int binCount, int y, double hFactor, int heightPer, 
  	Color lineColor )
  /* Draws a blue horizontal line on a wiggle track at a specified
   * location based on the range and number of bins*/
  {
  int bin;
  double y1;
  bin = -whichSampleBin( where, min0, max0, binCount);
  y1 = (int)((double)y+((double)bin)*hFactor+(double)heightPer);
  hvGfxBox(hvg, 0, y1, hvg->width, 1, lineColor);
  }
  
  static void wiggleLinkedFeaturesDraw(struct track *tg, 
      int seqStart, int seqEnd,
      struct hvGfx *hvg, int xOff, int yOff, int width, 
      MgFont *font, Color color, enum trackVisibility vis)
  /* Currently this routine is adapted from Terry's 
   * linkedFeatureSeriesDraw() routine.
   * It is called for 'sample' tracks as specified in the trackDb.ra.
   * and it looks at the cart to decide whether to interpolate, fill blocks,
   * and use anti-aliasing.*/
  {
  int i;
  struct linkedFeatures *lf;
  struct simpleFeature *sf;
  int y = yOff;
  int heightPer = tg->heightPer;
  int lineHeight = tg->lineHeight;
  int x1,x2;
  boolean isFull = (vis == tvFull);
  Color bColor = tg->ixAltColor;
  double scale = scaleForPixels(width);
  int prevX = -1;
  int gapPrevX = -1;
  double prevY = -1;
  double y1 = -1, y2;
  int ybase;
  int sampleX, sampleY; /* A sample in sample coordinates. 
                         * Sample X coordinate is chromosome coordinate.
  		       * Sample Y coordinate is usually 0-1000 */
  int binCount = 1.0/tg->scaleRange;   /* Maximum sample Y coordinate. */
  int bin;	      /* Sample Y coordinates are first converted to
                         * bin coordinates, and then to pixels.  I'm not
  		       * totally sure why.  */
  
  
  
  int currentX, currentXEnd, currentWidth;
  
  int leftSide, rightSide;
  
  int noZoom = 1;
  enum wiggleOptEnum wiggleType;
  char *interpolate = NULL;
  char *aa = NULL; 
  boolean antiAlias = FALSE;
  int fill; 
  int lineGapSize;
  double min0, max0;
  
  char o1[128]; /* Option 1 - linear interp */
  char o2[128]; /* Option 2 - anti alias */
  char o3[128]; /* Option 3 - fill */
  char o4[128]; /* Option 4 - minimum vertical range cutoff of plot */	
  char o5[128]; /* Option 5 - maximum vertical range cutoff of plot */
  char o6[128]; /* Option 6 - max gap where interpolation is still done */
  char cartStr[64];
  char *fillStr;
  
  double hFactor;
  double minRange, maxRange;
  double minRangeCutoff, maxRangeCutoff;
  
  
  Color gridColor = hvGfxFindRgb(hvg, &guidelineColor); /* for horizontal lines*/
  
  lf=tg->items;    
  if(lf==NULL) return;
  
  //take care of cart options
  safef( o1, 128,"%s.linear.interp", tg->track);
  safef( o2, 128, "%s.anti.alias", tg->track);
  safef( o3, 128,"%s.fill", tg->track);
  safef( o4, 128,"%s.min.cutoff", tg->track);
  safef( o5, 128,"%s.max.cutoff", tg->track);
  safef( o6, 128,"%s.interp.gap", tg->track);
  
  interpolate = cartUsualString(cart, o1, "Linear Interpolation");
  wiggleType = wiggleStringToEnum(interpolate);
  aa = cartUsualString(cart, o2, "on");
  antiAlias = sameString(aa, "on");
  
  //don't fill gcPercent track by default (but fill others)
  if(sameString( tg->table, "pGC") && sameString(database,"zooHuman3"))
  {
      fillStr = cartUsualString(cart, o3, "0");
  }
  else
  {
      fillStr = cartUsualString(cart, o3, "1");
  }
  fill = atoi(fillStr);
  cartSetString(cart, o3, fillStr );
  
  //the 0.1 is so the label doesn't get truncated with integer valued user input min
  //display range.
  minRangeCutoff = max( atof(cartUsualString(cart,o4,"0.0"))-0.1, tg->minRange );
  maxRangeCutoff = min( atof(cartUsualString(cart,o5,"1000.0"))+0.1, tg->maxRange);
  
  lineGapSize = atoi(cartUsualString(cart, o6, "200"));
  
  //update cart settings to reflect truncated range cutoff values
  cartSetString( cart, "win", "F" );
  safef( cartStr, 64, "%g", minRangeCutoff );
  cartSetString( cart, o4, cartStr );
  safef( cartStr, 64, "%g", maxRangeCutoff );
  cartSetString( cart, o5, cartStr );
  
  heightPer = tg->heightPer+1;
  hFactor = (double)heightPer*tg->scaleRange;
  
  //errAbort( "min=%g, max=%g\n", minRangeCutoff, maxRangeCutoff );
  
  
  if( sameString( tg->table, "zoo" ) || sameString( tg->table, "zooNew" ) )
      binCount = binCount - 100;    //save some space at top, between each zoo species
  
  minRange = whichSampleBin( minRangeCutoff, tg->minRange, tg->maxRange, binCount );
  maxRange = whichSampleBin( maxRangeCutoff, tg->minRange, tg->maxRange, binCount );
  
  //errAbort( "(%g,%g) cutoff=(%g,%g)\n", tg->minRange, tg->maxRange, minRangeCutoff, maxRangeCutoff );
  
  
  if( sameString( tg->table, "zoo" ) || sameString( tg->table, "zooNew" ) )
      {
      /*Always interpolate zoo track (since gaps are explicitly defined*/
      lineGapSize = -1;
      }
  else if( tg->minRange == 0 && tg->maxRange == 8 )    //range for all L-score tracks
      {
      if( isFull )
      {
          min0 = whichSampleNum( minRange, tg->minRange, tg->maxRange, binCount );
          max0 = whichSampleNum( maxRange, tg->minRange, tg->maxRange,  binCount );
          for( i=1; i<=6; i++ )
              drawWiggleHorizontalLine(hvg, (double)i, min0, max0,
  	            binCount, y, hFactor, heightPer, gridColor );
          }
      }
  
  for(lf = tg->items; lf != NULL; lf = lf->next) 
      {
      gapPrevX = -1;
      prevX = -1;
      ybase = (int)((double)y+hFactor+(double)heightPer);
  
  
      for (sf = lf->components; sf != NULL; sf = sf->next)
  	{
  	sampleX = sf->start;
  	sampleY = sf->end - sampleX;	// Stange encoding but so it is. 
  					// It is to deal with the fact that
  					// for a BED: sf->end = sf->start + length
  					// but in our case length = height (or y-value)
  					// so to recover height we take
  					// height = sf->end - sf->start.
  					// Otherwise another sf variable would 
  					// be needed.
   
  
  	/*mapping or sequencing gap*/
  	if (sampleY == 0)
  	    {
  	    bin = -whichSampleBin( (int)((maxRange - minRange)/5.0+minRange), 
  	    	minRange, maxRange, binCount );
  	    y1 = (int)((double)y+((double)bin)* hFactor+(double)heightPer);
  	    if( gapPrevX >= 0 )
  		drawScaledBox(hvg, sampleX, gapPrevX, scale, 
  			xOff, (int)y1, (int)(.10*heightPer), shadesOfGray[2]);
  	    gapPrevX = sampleX;
  	    prevX = -1; /*connect next point with gray bar too*/
  	    continue;
  	    }
  	if (sampleY > maxRange)
  	    sampleY = maxRange;
  	if (sampleY < minRange)
  	    sampleY = minRange;
  	bin = -whichSampleBin( sampleY, minRange, maxRange, binCount );
  
  
  	x1 = round((double)(sampleX-winStart)*scale) + xOff;
  	y1 = (int)((double)y+((double)bin)* hFactor+(double)heightPer);
  
  	
  
  	if (prevX > 0)
  	    {
  	    y2 = prevY;
  	    x2 = round((double)(prevX-winStart)*scale) + xOff;
  	    if( wiggleType == wiggleLinearInterpolation ) 
  	    /*connect samples*/
  		{
  		if( lineGapSize < 0 || prevX - sampleX <= lineGapSize )   
  		    /*don't interpolate over large gaps*/
  		    {
  		    if (fill)
  			hvGfxFillUnder(hvg, x1,y1, x2,y2, ybase, bColor);
  		    else
  			hvGfxLine(hvg, x1,y1, x2,y2, color);
  		    }
  		}
  	    }
  
  	//if( x1 < 0 || x1 > tl.picWidth )
  	//printf("x1 = %d, sampleX=%d, winStart = %d\n<br>", x1, sampleX, winStart );
  	if( x1 >= 0 && x1 <= tl.picWidth )
  	{
  	/* Draw the points themselves*/
  	drawScaledBox(hvg, sampleX, sampleX+1, scale, xOff, (int)y1-1, 3, color);
  	if( fill )
  		drawScaledBox(hvg, sampleX, sampleX+1, scale, xOff, (int)y1+2, 
  			      ybase-y1-2, bColor);
  	}
  
  	prevX = gapPrevX = sampleX;
  	prevY = y1;
  	}
  
      leftSide = max( tg->itemStart(tg,lf), winStart );
      rightSide = min(  tg->itemEnd(tg,lf), winEnd );
  
      currentX =  round((double)((int)leftSide-winStart)*scale) + xOff;
      currentXEnd =  round((double)((int)rightSide-winStart)*scale) + xOff;
      currentWidth = currentXEnd - currentX;
  
      if( noZoom && isFull )
  	{
  	mapBoxHc(hvg, lf->start, lf->end, currentX ,y, currentWidth,
  	    heightPer, tg->track, tg->mapItemName(tg, lf), tg->itemName(tg, lf));
  
  	if( lf->next != NULL )
  	    y += sampleUpdateY( lf->name, lf->next->name, lineHeight );
  	else
  	    y += lineHeight;
  	}
  
      }
  }
  
  struct track *sortGroupList = NULL; /* Used temporarily for sample sorting. */
  
  int lfNamePositionCmp(const void *va, const void *vb)
  /* Compare based on name, then chromStart, used for
     sorting sample based tracks. */
  {
  const struct linkedFeatures *a = *((struct linkedFeatures **)va);
  const struct linkedFeatures *b = *((struct linkedFeatures **)vb);
  int dif;
  char *tgName = NULL;
  if(sortGroupList != NULL)
      tgName = sortGroupList->shortLabel;
  if(tgName != NULL)
      {
      if(sameString(a->name, tgName) && differentString(b->name, tgName))
  	return -1;
      if(sameString(b->name, tgName) && differentString(a->name, tgName))
  	return 1;
      }
  dif = strcmp(a->name, b->name);
  if (dif == 0)
      dif = a->start - b->start;
  return dif;
  }
  
  
  void loadSampleIntoLinkedFeature(struct track *tg)
  /* Convert sample info in window to linked feature. */
  {
  int maxWiggleTrackHeight = 2500;
  struct sqlConnection *conn = hAllocConn(database);
  struct sqlResult *sr;
  char **row;
  int rowOffset;
  struct sample *sample;
  struct linkedFeatures *lfList = NULL, *lf;
  char *hasDense = NULL;
  char *where = NULL;
  char query[256];
  
  /*see if we have a summary table*/
  sqlSafef(query, sizeof(query), 
  	"select name from %s where name = '%s' limit 1", 
  	tg->table, tg->shortLabel);
  //errAbort( "%s", query );
  hasDense = sqlQuickQuery(conn, query, query, sizeof(query));
  
  /* If we're in dense mode and have a summary table load it. */
  if(tg->visibility == tvDense)
      {
      if(hasDense != NULL)
  	{
  	sqlSafefFrag(query, sizeof(query), " name = '%s' ", tg->shortLabel);
  	where = cloneString(query);
  	}
      }
  
  sr = hRangeQuery(conn, tg->table, chromName, winStart, winEnd, where, &rowOffset);
  while ((row = sqlNextRow(sr)) != NULL)
      {
      sample = sampleLoad(row + rowOffset);
      lf = lfFromSample(sample);
      slAddHead(&lfList, lf);
      sampleFree(&sample);
      }
  if(where != NULL)
      freez(&where);
  sqlFreeResult(&sr);
  hFreeConn(&conn);
  slReverse(&lfList);
  
  /* sort to bring items with common names to the same line
  but only for tracks with a summary table (with name=shortLabel) in
  dense mode*/
  
  if( hasDense != NULL )
      {
      sortGroupList = tg; /* used to put track name at top of sorted list. */
      slSort(&lfList, lfNamePositionCmp);
      sortGroupList = NULL;
      }
  tg->items = lfList;
  
  /*turn off full mode if there are too many rows or each row is too
   * large. A total of maxWiggleTrackHeight is allowed for number of
   * rows times the rowHeight*/
  if( tg->visibility == tvFull && sampleTotalHeight( tg, tvFull ) > maxWiggleTrackHeight  )
      {
      tg->limitedVisSet = TRUE;
      tg->limitedVis = tvDense;
      }
  }
  
  
  
  void sampleLinkedFeaturesMethods(struct track *tg)
  /* Fill in track methods for 'sample' tracks. */
  {
  linkedFeaturesMethods(tg);
  tg->drawItems = wiggleLinkedFeaturesDraw;
  tg->totalHeight = sampleTotalHeight;
  tg->itemHeight = tgFixedItemHeight;
  }
  
  
  void sampleMethods(struct track *track, struct trackDb *tdb, int wordCount, char *words[])
  /* Load up methods for a generic sample type track. */
  {
  track->scaleRange = 0.001;
  track->minRange = 0.0;
  track->maxRange = 1000.0;
  if (wordCount > 1)     /*sample minRange maxRange*/
      track->minRange = atof(words[1]);
  if (wordCount > 2)
      track->maxRange = atof(words[2]);
  if (wordCount > 3)
      track->scaleRange = atof(words[3]);
  track->bedSize = 9;
  track->subType = lfSubSample;     /*make subType be "sample" (=2)*/
  sampleLinkedFeaturesMethods(track);
  track->loadItems = loadSampleIntoLinkedFeature;
  }
  
  void loadHumMusL(struct track *tg)
  /* Load humMusL track with 2 zoom levels and one normal level. 
   * Also used for loading the musHumL track (called Human Cons) 
   * on the mouse browser. It decides which of 4 tables to
   * load based on how large of a window the user is looking at*/
  {
  struct sqlConnection *conn = hAllocConn(database);
  struct sqlResult *sr;
  char **row;
  int rowOffset;
  struct sample *sample;
  struct linkedFeatures *lfList = NULL, *lf;
  char *hasDense = NULL;
  char *where = NULL;
  char tableName[256];
  int z;
  float pixPerBase = 0;
  
  if(tl.picWidth == 0)
      errAbort("hgTracks.c::loadHumMusL() - can't have pixel width of 0");
  pixPerBase = (winEnd - winStart)/ tl.picWidth;
  
  
  /* Determine zoom level. */
   if (!strstr(tg->table,"HMRConservation"))
     z = humMusZoomLevel();
   else z=0;
  
  
  if(z == 1 )
      safef(tableName, sizeof(tableName), "%s_%s", "zoom1",
  	    tg->table);
  else if( z == 2)
      safef(tableName, sizeof(tableName), "%s_%s", "zoom50",
  	    tg->table);
  else if(z == 3)
      safef(tableName, sizeof(tableName), "%s_%s",
  	    "zoom2500", tg->table);
  else
      safef(tableName, sizeof(tableName), "%s", tg->table);
  
  //printf("(%s)", tableName );
  
  sr = hRangeQuery(conn, tableName, chromName, winStart, winEnd,
      where, &rowOffset);
  while ((row = sqlNextRow(sr)) != NULL)
      {
      sample = sampleLoad(row+rowOffset);
      lf = lfFromSample(sample);
      slAddHead(&lfList, lf);
      sampleFree(&sample);
      }
  if(where != NULL)
      freez(&where);
  sqlFreeResult(&sr);
  hFreeConn(&conn);
  slReverse(&lfList);
  
  /* sort to bring items with common names to the same line
     but only for tracks with a summary table (with name=shortLabel)
     in
     dense mode*/
  if( hasDense != NULL )
      {
      sortGroupList = tg; /* used to put track name at
  	                 * top of sorted list. */
      slSort(&lfList, lfNamePositionCmp);
      sortGroupList = NULL;
      }
  tg->items = lfList;
  }
  
  void humMusLMethods( struct track *tg )
  /* Overide the humMusL load function to look for zoomed out tracks. */
  {
  tg->loadItems = loadHumMusL;
  tg->totalHeight = consTotalHeight;
  }
  
  struct hash *zooSpeciesHash = NULL;
  
  void zooSpeciesHashInit()
  /* Function to init list of zoo species */
  {
  char *name = NULL;
  char *val = NULL;
  
  if (zooSpeciesHash != NULL)
      return;
  zooSpeciesHash = hashNew(6);
  
  name = cloneString("Human");
  val = cloneString("1");
  hashAdd(zooSpeciesHash, name, val);
  cartSetBoolean( cart, "zooSpecies.Human", TRUE );
  
  name = cloneString("Chimpanzee");
  val = cloneString("2");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Baboon");
  val = cloneString("3");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Orangutan");
  val = cloneString("4");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Macaque");
  val = cloneString("5");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Vervet");
  val = cloneString("6");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Lemur");
  val = cloneString("7");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Cat");
  val = cloneString("8");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Dog");
  val = cloneString("9");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Cow");
  val = cloneString("10");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Pig");
  val = cloneString("11");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Horse");
  val = cloneString("12");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Rabbit");
  val = cloneString("13");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Hedgehog");
  val = cloneString("14");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("ajBat");
  val = cloneString("15");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("cpBat");
  val = cloneString("16");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Rat");
  val = cloneString("17");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Mouse");
  val = cloneString("18");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Platypus");
  val = cloneString("19");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Opossum");
  val = cloneString("20");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Dunnart");
  val = cloneString("21");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Chicken");
  val = cloneString("22");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Fugu");
  val = cloneString("23");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Tetraodon");
  val = cloneString("24");
  hashAdd(zooSpeciesHash, name, val);
  
  name = cloneString("Zebrafish");
  val = cloneString("25");
  hashAdd(zooSpeciesHash, name, val);
  }
  
  
  int lfZooCmp(const void *va, const void *vb)
  /* Compare based on name, then chromStart, used for
     sorting sample based tracks. */
  {
  struct linkedFeatures *a = *((struct linkedFeatures **)va);
  struct linkedFeatures *b = *((struct linkedFeatures **)vb);
  char *aVal =  hashFindVal(zooSpeciesHash, a->name);
  char *bVal =  hashFindVal(zooSpeciesHash, b->name);
  int aV = atoi(aVal);
  int bV = atoi(bVal);
  return aV - bV;
  }
  
  void loadSampleZoo(struct track *tg)
  /* Convert sample info in window to linked feature. */
  {
  int maxWiggleTrackHeight = 2500;
  struct sqlConnection *conn = hAllocConn(database);
  struct sqlResult *sr;
  char **row;
  int rowOffset;
  struct sample *sample;
  struct linkedFeatures *lfList = NULL, *lf;
  char *hasDense = NULL;
  char *where = NULL;
  char query[256];
  char option[64];
  
  zooSpeciesHashInit();
  
  /*see if we have a summary table*/
  sqlSafef(query, sizeof(query), "select name from %s where name = '%s' limit 1", tg->table, tg->shortLabel);
  //errAbort( "%s", query );
  hasDense = sqlQuickQuery(conn, query, query, sizeof(query));
  
  /* If we're in dense mode and have a summary table load it. */
  if(tg->visibility == tvDense)
      {
      if(hasDense != NULL)
  	{
  	sqlSafefFrag(query, sizeof(query), " name = '%s' ", tg->shortLabel);
  	where = cloneString(query);
  	}
      }
  
  sr = hRangeQuery(conn, tg->table, chromName, winStart, winEnd, where, &rowOffset);
  while ((row = sqlNextRow(sr)) != NULL)
      {
      sample = sampleLoad(row + rowOffset);
      lf = lfFromSample(sample);
      safef( option, sizeof(option), "zooSpecies.%s", sample->name );
      if( cartUsualBoolean(cart, option, TRUE ))
      slAddHead(&lfList, lf);
      sampleFree(&sample);
      }
  if(where != NULL)
      freez(&where);
  sqlFreeResult(&sr);
  hFreeConn(&conn);
  slReverse(&lfList);
  
  /* sort to bring items with common names to the same line
   * but only for tracks with a summary table 
   * (with name=shortLabel) in dense mode */
  if( hasDense != NULL )
      {
      sortGroupList = tg; /* used to put track name at top of sorted list. */
      slSort(&lfList, lfNamePositionCmp);
      sortGroupList = NULL;
      }
  
  /* Sort in species phylogenetic order */
  slSort(&lfList, lfZooCmp);
  
  
  tg->items = lfList;
  
  /*turn off full mode if there are too many rows or each row is too
   * large. A total of maxWiggleTrackHeight is allowed for number of
   * rows times the rowHeight*/
  if( tg->visibility == tvFull && sampleTotalHeight( tg, tvFull ) > maxWiggleTrackHeight  )
      {
      tg->limitedVisSet = TRUE;
      tg->limitedVis = tvDense;
      }
  }
  
  void zooMethods( struct track *tg )
  /* Overide the zoo sample type load function to look for zoomed out tracks. */
  {
  tg->loadItems = loadSampleZoo;
  }
  
  
  void loadAffyTranscriptome(struct track *tg)
  /* Convert sample info in window to linked feature. */
  {
  struct sqlConnection *conn = hAllocConn(database);
  struct sqlResult *sr;
  char **row;
  int rowOffset;
  struct sample *sample;
  struct linkedFeatures *lfList = NULL, *lf;
  char *hasDense = NULL;
  char *where = NULL;
  char query[256];
  char tableName[256];
  int zoom1 = 23924, zoom2 = 2991; /* bp per data point */
  float pixPerBase = 0;
  /* Set it up so we don't have linear interpretation.. */
  char *noLinearInterpString = wiggleEnumToString(wiggleNoInterpolation);
  cartSetString(cart, "affyTranscriptome.linear.interp", noLinearInterpString);
  
  if(tl.picWidth == 0)
      errAbort("hgTracks.c::loadAffyTranscriptome() - can't have pixel width of 0");
  pixPerBase = (winEnd - winStart)/ tl.picWidth;
  
  
  /* Determine zoom level. */
  if(pixPerBase >= zoom1)
      safef(tableName, sizeof(tableName), "%s_%s", "zoom1", tg->table);
  else if(pixPerBase >= zoom2)
      safef(tableName, sizeof(tableName), "%s_%s", "zoom2", tg->table);
  else 
      safef(tableName, sizeof(tableName), "%s", tg->table);
  
  /*see if we have a summary table*/
  if(hTableExists(database, tableName))
      sqlSafef(query, sizeof(query), "select name from %s where name = '%s' limit 1",  tableName, tg->shortLabel);
  else
      {
      warn("<p>Couldn't find table %s<br><br>", tableName);
      sqlSafef(query, sizeof(query), "select name from %s where name = '%s' limit 1",  tg->table, tg->shortLabel);
      safef(tableName, sizeof(tableName), "%s", tg->table);
      }
  
  hasDense = sqlQuickQuery(conn, query, query, sizeof(query));
  
  /* If we're in dense mode and have a summary table load it. */
  if(tg->visibility == tvDense)
      {
      if(hasDense != NULL)
  	{
  	sqlSafefFrag(query, sizeof(query), " name = '%s' ", tg->shortLabel);
  	where = cloneString(query);
  	}
      }
  
  sr = hRangeQuery(conn, tableName, chromName, winStart, winEnd, where, &rowOffset);
  while ((row = sqlNextRow(sr)) != NULL)
      {
      sample = sampleLoad(row+rowOffset);
      lf = lfFromSample(sample);
      slAddHead(&lfList, lf);
      sampleFree(&sample);
      }
  if(where != NULL)
      freez(&where);
  sqlFreeResult(&sr);
  hFreeConn(&conn);
  slReverse(&lfList);
  
  /* sort to bring items with common names to the same line
  but only for tracks with a summary table (with name=shortLabel) in
  dense mode*/
  if( hasDense != NULL )
      {
      sortGroupList = tg; /* used to put track name at top of sorted list. */
      slSort(&lfList, lfNamePositionCmp);
      sortGroupList = NULL;
      }
  tg->items = lfList;
  }
  
  
  void affyTranscriptomeMethods(struct track *tg)
  /* Overide the load function to look for zoomed out tracks. */
  {
  tg->loadItems = loadAffyTranscriptome;
  }
  
  