src/hg/instinct/hgHeatmap2/hgCircleMaps.c 1.12

1.12 2009/08/19 23:00:18 angie
Added option to mgSaveToGif and its call stack, to use GIF's Graphic Control Extension to make memgfx's background color (0) transparent. Also corrected terminology for PNG in .h files: useAlpha -> useTransparency.
Index: src/hg/instinct/hgHeatmap2/hgCircleMaps.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/instinct/hgHeatmap2/hgCircleMaps.c,v
retrieving revision 1.11
retrieving revision 1.12
diff -b -B -U 1000000 -r1.11 -r1.12
--- src/hg/instinct/hgHeatmap2/hgCircleMaps.c	4 Jun 2009 03:50:36 -0000	1.11
+++ src/hg/instinct/hgHeatmap2/hgCircleMaps.c	19 Aug 2009 23:00:18 -0000	1.12
@@ -1,1171 +1,1171 @@
 /********************************************************************************/
 /* Copyright 2007-2009 -- The Regents of the University of California           */
 /********************************************************************************/
 
 /* hgCircleMaps.c - circular heatmaps to visualize genomic high-throughput data for 
  * multiple samples in a concise image suitable for placement in a pathway diagram
  * This file is copyright 2009 J. Zachary Sanborn.  All rights reserved. */  
 
 #define EXPR_DATA_SHADES 16
 #define COLOR_SCALE 1
 #define DEFAULT_MAX_DEVIATION 0.7
 #define PI 3.14159265358979323846
 
 #include "common.h"
 #include "bed.h"
 #include "cart.h"
 #include "errCatch.h"
 #include "hash.h"
 #include "hdb.h"
 #include "hgColors.h"
 #include "hPrint.h"
 #include "vGfx.h"
 #include "hvGfx.h"
 #include "hgHeatmapLib.h"
 #include "featuresLib.h"
 #include "hgHeatmap2.h"
 #include "sortFeatures.h"
 #include "gfxPoly.h"
 #include "json.h"
 
 static char const rcsid[] = "$Id$";
 
 struct ring {
     struct ring *next;       /* next ring */
 
     char *name;              /* name of ring (optional) */
     char *dataset;           /* name of dataset */
     char *type;              /* type of data (feature, SNP, CNV, expression, etc.) */
 
     int numSamples;          /* num samples in ring */
     int numElements;         /* number of elements in ring */
     double minR;             /* min radius for current ring */
     double maxR;             /* max radius for current ring */
 
     double gain;             /* gain setting for ring */
     double maxDev;           /* maximum deviation for ring */
 
     double median;           /* median value for ring */
     boolean drawSummary;     /* draw summary */
 
     boolean visible;         /* invisible rings exist, can be sorted, not displayed */
     boolean sort;            /* Sort this ring or not */
     int sortDir;             /* +1 = ASC, -1 = DESC */
     double maxVal;           /* maximum value, used for sorting */
 
     struct rgbColor *zeroColor; /* colors for ring */
     struct rgbColor *highColor; 
     struct rgbColor *lowColor;
 
     struct hash *data;       /* data for ring, keyed by sample */
 };
 
 struct circleMap {
     char *name;              /* name of circle (e.g. gene) */
     struct slName *samples;  /* global list of samples, order of samples defined 
 			      * position in ring */
     struct ring *rings;      /* data defining all rings for circle */
 
     double minR;             /* min radius for all rings */
     double maxR;             /* max radius for all rings */
     int width;               /* pix width */
 
     boolean drawSummary;     /* draw summary (pie chart) */
     boolean subgrouped;      /* if successful subgrouping applied */
 
     Color bgColor;           /* Background color (default = MG_WHITE) */
     struct hash *data;       /* data for center (pie chart) */
 };
 
 char *heatMapDbProfile = "localDb";
 
 double maxDev(struct genoHeatmap *gh)
 {
 if (!gh)
     return DEFAULT_MAX_DEVIATION;
 
 if (gh->expScale > 0)
     return gh->expScale;
 
 return DEFAULT_MAX_DEVIATION;
 }
 
 void vgRadiusBox(struct vGfx *vg, int x0, int y0, double rStart, double rStop, 
 		 double tStart, double tStop, Color col)
 {
 if ((rStart == rStop) || (tStart == tStop)) // zero height/thickness
     return;
 
 struct gfxPoly *gp = gfxPolyNew();
 
 int x, y;
 double r, t, tStep;
 
 /* rStart -> rStop @ tStart -- one point at each edge */
 for (r = rStart; r <= rStop; r += (rStop - rStart))
     {
     x = floor(r * sin(tStart));
     y = floor(r * cos(tStart));
     gfxPolyAddPoint(gp, x + x0, y + y0);
     }
 
 /* tStart -> tStop @ rStop */
 tStep = 0.5 / (2.0*PI);
 for (t = tStart; t <= tStop; t += tStep)
     {
     x = floor(rStop * sin(t));
     y = floor(rStop * cos(t));
     gfxPolyAddPoint(gp, x + x0, y + y0);
     }
 
 /* rStop -> rStart @ tStop -- one point at each edge */
 for (r = rStop; r >= rStart; r -= (rStop - rStart))
     {
     x = floor(r * sin(tStop));
     y = floor(r * cos(tStop));
     gfxPolyAddPoint(gp, x + x0, y + y0);
     }
 
 /* tStop -> tStop @ rStart */
 for (t = tStop; t <= tStart; t -= tStep)
     {
     x = floor(rStart * sin(t));
     y = floor(rStart * cos(t));
     gfxPolyAddPoint(gp, x + x0, y + y0);
     }
 
 vgDrawPoly(vg, gp, col, TRUE);
 gfxPolyFree(&gp);
 }
 
 struct circleMap *newCircleMap(char *name)
 { /* initialize a new circle layout */
 struct circleMap *cm;
 AllocVar(cm);
 cm->name = cloneString(name);
 cm->samples = NULL;
 cm->rings = NULL;
 
 cm->minR = 0.0;
 cm->maxR = 0.0;
 cm->width = 0;
 cm->data = NULL;
 
 cm->drawSummary = FALSE;
 cm->subgrouped = FALSE;
 
 cm->bgColor = MG_WHITE;
 return cm;
 }
 
 struct ring *cloneRing(struct ring *rg)
 {
 if (!rg)
     return NULL;
 
 struct ring *rg2;
 AllocVar(rg2);
 rg2->name = cloneString(rg->name);
 rg2->dataset = cloneString(rg->dataset);
 rg2->type = cloneString(rg->type);
 rg2->maxDev = rg->maxDev;
 rg2->gain = rg->gain;
 rg2->numSamples = rg->numSamples;
 rg2->numElements = rg->numElements;
 rg2->minR = rg->minR;
 rg2->maxR = rg->maxR;
 rg2->data = NULL;
 
 rg2->drawSummary = rg->drawSummary;
 rg2->median = 0.0;
 
 rg2->visible = rg->visible;
 
 rg2->sort = rg->sort;   
 rg2->sortDir = rg->sortDir;
 rg2->maxVal = rg->maxVal;
 
 return rg2;
 }
 
 struct ring *addRingToCircleMap(struct circleMap *cm, char *name, struct genoHeatmap *gh, 
 				boolean isVisible)
 {  /* Initialize a ring, add it circleLay, and return reference to it */ 
 struct ring *rg;
 AllocVar(rg);
 rg->name = cloneString(name);
 rg->dataset = cloneString(gh->name);
 rg->type = cloneString(gh->platform);
 rg->maxDev = maxDev(gh);
 rg->gain = gh->gainSet;
 rg->numElements = 0;
 rg->numSamples = 0;
 rg->minR = 0.0;
 rg->maxR = 0.0;
 rg->data = NULL;
 
 rg->drawSummary = FALSE;
 rg->median = 0.0;
 
 rg->visible = isVisible;
 
 rg->sort = FALSE;   
 rg->sortDir = 0;
 rg->maxVal = 0.0;
 
 slAddHead(&cm->rings, rg);
 return rg;
 }
 
 void addGeneDataToRing(struct ring *rg, struct hash *geneHash, char *gene, 
 		       struct slName *sampleList)
 {
 if (!rg || !geneHash)
     return;
 
 struct hashEl *el = hashLookup(geneHash, gene);
 if (!el)
     return;
 rg->numElements = slCount(el);  // number of probes matching 'gene'
 
 if (!rg->data)
     rg->data = hashNew(0);
 
 struct bed *nb, *nbList = NULL;
 for ( ; el; el = el->next)      // loop through probes
     {
     struct bed *nb = el->val;
     if (!nb)
 	continue;
     slAddHead(&nbList, nb);
     }
 slSort(nbList, bedCmp);
 
 int i = 0;
 struct slName *sl;
 struct slDouble *ad, *allData = NULL;
 for (sl = sampleList; sl; sl = sl->next)
     {
     struct slDouble *sd, *sdList = NULL;
     for (nb = nbList; nb; nb = nb->next)
 	{
 	double val = nb->expScores[i];
 
 	if (val > rg->maxVal)
 	    rg->maxVal = val;
 
 	/* Store copy of all data in list to determine median value */
 	ad = slDoubleNew(val);
 	slAddHead(&allData, ad);
 
 	sd = slDoubleNew(val);
 	slAddHead(&sdList, sd);
 	}
     slReverse(&sdList);
 
     hashAdd(rg->data, sl->name, sdList);
     rg->numSamples += 1;
     i++;  // increment expId pointer.
     }
 
 rg->median = slDoubleMedian(allData);
 slFreeList(&allData);
 }
 
 void addFeatureDataToRing(struct sqlConnection *conn, struct ring *rg, 
 			  struct column *col, struct genoHeatmap *gh)
 {
 if (!rg)
     return;
 
 char *labTable = gh->patTable;
 char *value = gh->sampleField;
 char *key = gh->patField;
 char *db = gh->patDb; 
 
 if ((labTable == NULL) || (key == NULL) || (value == NULL) || (db==NULL))
     return; 
 
 if (!rg->data)
     rg->data = hashNew(0);
 rg->numElements = 1;
 
 char *colorScheme = col->cellColorSchemeVal(col);
 rg->type = cloneString(colorScheme);
 
 double absVal,  minVal, maxVal;
 char *minCutVal, *maxCutVal;
 double offset;
 int reverseColor;
 
 offset = atof(col->cellOffsetVal(col));
 reverseColor = atoi(col->cellColorReverseVal(col));
 
 char *minValStr = col->cellMinVal(col, conn);
 if (!minValStr)
     minVal = 0.0;
 else
     minVal = atof(minValStr) + offset;
 
 char *maxValStr = col->cellMaxVal(col, conn);
 if (!maxValStr)
     maxVal = 0.0;
 else
     maxVal = atof(maxValStr) + offset;
 
 if (reverseColor == -1)
     {
     maxVal = reverseColor*maxVal;
     minVal = reverseColor*maxVal;
     }
 minCutVal = col->cellMinCutVal(col, conn);
 maxCutVal = col->cellMaxCutVal(col, conn);
 
 if ((minVal<0) && (maxVal>0))  /* double color: like microarray */
     rg->maxDev = max(fabs(minVal), maxVal);
 else                           /* single color */
     rg->maxDev = maxVal - minVal;
    
 int i = 0;
 struct slName *sl;
 double val;
 for (sl = gh->sampleList; sl; sl = sl->next)
     {
     struct slName * id = slNameNew(getId(conn, labTable, key, sl->name, value));
     char *cellVal = col->cellVal(col, id, conn);
     if (!cellVal)
 	continue;
     
     val = atof(cellVal);
     if (minCutVal)
 	if (val < atof(minCutVal))
 	    continue;
     if (maxCutVal)
 	if (val > atof(maxCutVal))
 	    continue;
     absVal = fabs(val);
     absVal = reverseColor * (absVal + offset);
 
     if (!(minVal < 0 && maxVal > 0))  
 	absVal = absVal - minVal;
 
     if (val < 0)
 	absVal = -1.0 * absVal;
 
     /* store final data for drawing in ring */
     struct slDouble *sd = slDoubleNew(absVal);
 
     hashAdd(rg->data, sl->name, sd);
     i++;  // increment expId pointer.
     
     rg->numSamples++;
     freez(&id); 
     }
 }
 
 void ringSetDefaultColor(struct ring *rg)
 {
 if (!rg)
     return;
 
 struct rgbColor *low = NULL;
 struct rgbColor *zero = NULL;
 struct rgbColor *high = NULL;
 
 // default, expression data color scheme:
 if (sameString(rg->type, "CNV") || sameString(rg->type, "SNP"))
     { // CNV, SNP data
     AllocVar(zero);  // white
     zero->r = 255;
     zero->g = 255;
     zero->b = 255;
 
     AllocVar(high);  // red
     high->r = 255;
     high->g = 0;
     high->b = 0;
 
     AllocVar(low);  // blue
     low->r = 0;
     low->g = 0;
     low->b = 255;
     }
 else if (sameString(rg->type, "default"))
     { // default feature color
     AllocVar(zero);  // black
     zero->r = 0;
     zero->g = 0;
     zero->b = 0;
 
     AllocVar(high);  // yellow
     high->r = 255;
     high->g = 255;
     high->b = 0;
 
     AllocVar(low);  // green
     low->r = 0;
     low->g = 255;
     low->b = 0;
     }
 else
     { // expression data, others...
     AllocVar(zero);  // black
     zero->r = 0;
     zero->g = 0;
     zero->b = 0;
 
     AllocVar(high);  // red
     high->r = 255;
     high->g = 0;
     high->b = 0;
 
     AllocVar(low);  // green
     low->r = 0;
     low->g = 255;
     low->b = 0;
     }
 
 rg->zeroColor = zero;
 rg->highColor = high;
 rg->lowColor = low;
 } 
 
 Color getColor(double val, double gain, double maxDev, 
 	       Color *upShades, Color *downShades)
 {
 double absVal = fabs(val);
 double colorScale = COLOR_SCALE / maxDev;
 absVal = absVal * gain;
 
 int colorIndex = (int)(absVal * (EXPR_DATA_SHADES-1.0) * colorScale);
 /* Clip color index to fit inside of array, since we may have brightened it. */
 if (colorIndex < 0)
     colorIndex = 0;
 if (colorIndex >= EXPR_DATA_SHADES)
     colorIndex = EXPR_DATA_SHADES-1;
 
 if(val >= 0)
     return upShades[colorIndex];
 else
     return downShades[colorIndex];
 }
 
 void drawCenter(struct vGfx *vg, struct circleMap *cm)
 {
 int x0 = cm->width / 2;
 int y0 = cm->width / 2;
 Color valCol;
 double tStart, tStop;
 
 Color upShades[EXPR_DATA_SHADES];
 Color downShades[EXPR_DATA_SHADES];    
 
 double numSamples = (double) slCount(cm->samples);
 
 int rStart = 0;
 int rStop = cm->minR - 2;
 
 cm->data = hashNew(0);
 
 int maxSamples = -1;
 struct ring *rg;
 for (rg = cm->rings; rg; rg = rg->next)
     {    
     if (rg->numSamples == 0)
 	continue;
 
     ringSetDefaultColor(rg);
     
     vgMakeColorGradient(vg, rg->zeroColor, rg->highColor, EXPR_DATA_SHADES, upShades);
     vgMakeColorGradient(vg, rg->zeroColor, rg->lowColor, EXPR_DATA_SHADES, downShades);
 
     valCol = getColor(rg->median, rg->gain, rg->maxDev, upShades, downShades);
 
     if (rg->numSamples > maxSamples)
 	{ /* Only set background color according to median color of largest subset */
 	maxSamples = rg->numSamples;
 	cm->bgColor = valCol;
 	}
 	
     double i = 0.0;
     struct slName *sl;
     for (sl = cm->samples; sl; sl = sl->next)
 	{
 	tStart = 2.0 * PI * i / numSamples;
 	tStop  = 2.0 * PI * (i + 1.0) / numSamples;
 	i += 1.0;
 	
 	struct hashEl *el = hashLookup(rg->data, sl->name);
 	if (!el)
 	    continue;  // data exists for sample in ring
 	
 	el = hashLookup(cm->data, sl->name);
 	if (el)
 	    continue;  // only save the first
 	
 	vgRadiusBox(vg, x0, y0, rStart, rStop, tStart, tStop, valCol);
 	hashAddInt(cm->data, sl->name, 1);
 	}
     }
 }
 
 void drawRing(struct vGfx *vg, struct circleMap *cm, struct ring *rg)
 {
 if (!rg->data || rg->numSamples == 0) // nothing to draw
     return;
 
 Color valCol;
 
 Color upShades[EXPR_DATA_SHADES];
 Color downShades[EXPR_DATA_SHADES];
 
 ringSetDefaultColor(rg);
 
 vgMakeColorGradient(vg, rg->zeroColor, rg->highColor, EXPR_DATA_SHADES, upShades);
 vgMakeColorGradient(vg, rg->zeroColor, rg->lowColor, EXPR_DATA_SHADES, downShades);
 
 int x0 = cm->width / 2;
 int y0 = cm->width / 2;
 
 double rStart, rStop, rStep = (rg->maxR - rg->minR) / (double) rg->numElements;
 double tStart, tStop;  // thetas
 
 double numSamples = (double) slCount(cm->samples);
 
 double i = 0.0;
 struct slName *sl;
 struct slDouble *sd, *sdList;
 
 Color missingColor;
 if (cm->subgrouped)
     missingColor = MG_WHITE;  // when subgrouped, gray is distracting
 else
     missingColor = vgFindColorIx(vg, 225, 225, 225);  // light gray
 
 
 for (sl = cm->samples; sl; sl = sl->next)
     {
     tStart = 2.0 * PI * i / numSamples;
     tStop  = 2.0 * PI * (i + 1.0) / numSamples;
     i += 1.0;
     rStart = rg->minR;
     rStop = rg->minR + rStep;
 
     struct hashEl *el = hashLookup(rg->data, sl->name);
     if (!el)
 	{
 	vgRadiusBox(vg, x0, y0, rg->minR, rg->maxR, tStart, tStop, missingColor);
 	continue;
 	}
     sdList = el->val;  // slDouble list of expScores
 
     if (rg->drawSummary)
 	{
 	valCol = getColor(rg->median, rg->gain, rg->maxDev, upShades, downShades);
 	vgRadiusBox(vg, x0, y0, rg->minR, rg->maxR, tStart, tStop, valCol);
 	}
     else
 	{
 	for (sd = sdList; sd; sd = sd->next)
 	    {
 	    valCol = getColor(sd->val, rg->gain, rg->maxDev, upShades, downShades);
 	    vgRadiusBox(vg, x0, y0, rStart, rStop, tStart, tStop, valCol);
 	    
 	    rStart += rStep;
 	    rStop += rStep;
 	    }
 	}
     }
 }
 
 void drawRings(struct vGfx *vg, struct circleMap *cm)
 {
 struct ring *rg;
 for (rg = cm->rings; rg; rg = rg->next)
     drawRing(vg, cm, rg);
 }
 
 void drawName(struct vGfx *vg, struct circleMap *cm)
 {
 Color textCol = vgContrastingColor(vg, cm->bgColor);
 
 /* Draw gene label in center */
 MgFont *font = mgSmallFont(); 
 if (mgFontStringWidth(font, cm->name) < cm->minR * 2.0)
     vgTextCentered(vg, 0, 0, cm->width, cm->width, textCol, font, cm->name);  
 }
 
 void drawCircleMap(struct vGfx *vg, struct circleMap *cm)
 {
 if (!cm)
     return;
 
 if (cm->drawSummary)
     drawCenter(vg, cm);
 
 drawRings(vg, cm);
 drawName(vg, cm);
 }
 
 char *circleMapGif(struct circleMap *cm)
 /* Create genome GIF file and HT that includes it. */
 {
 if (!cm)
     return NULL;
 
 if (cm->width == 0)
     return NULL;
 
 struct tempName md5Tn;
 char modifier[128];
 safef(modifier, sizeof(modifier), "circleMapGif");
 char *strToHash = cartSettingsString(hgh2Prefix, modifier);
 
 trashDirMD5File(&md5Tn, "hgh", ".gif", strToHash);
 
 off_t size = fileSize(md5Tn.forCgi);
 if (!fileExists(md5Tn.forCgi) || (size == 0) || DEBUG_IMG)
     {
-    struct hvGfx *vg = hvGfxOpenGif(cm->width, cm->width, md5Tn.forCgi);
+    struct hvGfx *vg = hvGfxOpenGif(cm->width, cm->width, md5Tn.forCgi, FALSE);
     drawCircleMap(vg->vg, cm);
     hvGfxClose(&vg);
     }
 
 char *filename = replaceChars(md5Tn.forHtml, "..", "");
 return filename;
 }
 
 void layoutCircleMap(struct circleMap *cm, int width)
 {
 if (!cm)
     return;
 cm->width = width;
 cm->maxR = (double) cm->width / 2.0;
 cm->minR = 0.75 * cm->maxR;
 
 struct ring *rg;
 int numRings = 0;
 for (rg = cm->rings; rg; rg = rg->next)
     if (rg->visible && rg->numSamples > 0)
 	numRings++;
 
 double step = (cm->maxR - cm->minR) / (double) numRings;
 
 double buffer = 0.0;
 if (cm->subgrouped)
     buffer = step / 5.0;  // put buffer between subgrouped rings
 buffer = 0.0;
 
 double rStart = cm->minR;
 double rStop = cm->minR + (step - buffer);
 
 for (rg = cm->rings; rg; rg = rg->next)
     {
     if (!rg->visible)  // keep minR/maxR at zero for no display
 	continue;
     if (rg->numSamples == 0)
 	{
 	rg->minR = 0;
 	rg->maxR = 0;
 	continue;
 	}
 
     rg->minR = rStart;
     rg->maxR = rStop;
 
     rStart += step;
     rStop = rStart + (step - buffer);
     }
 }
 
 void setupSummary(struct circleMap *cm)
 {
 char *summarize = cartOptionalString(cart, hgh2Summarize);
 if (!summarize)
     return;
 
 char *summaryDisplay = cartUsualString(cart, hgh2SummaryDisplay, "ring");
 struct slName *names = slNameListFromComma(summarize);
 
 if (sameString(summaryDisplay, "center"))
     cm->drawSummary = TRUE;
 else
     {
     struct ring *rg;
     for (rg = cm->rings; rg; rg = rg->next)
 	{
 	if (!slNameInList(names, rg->name))
 	    continue;
 	rg->drawSummary = TRUE;
 	}
     }
 }
 
 int getSortDirection(char *sortDir)
 {
 if (!sortDir)
     return 1;
 if (sameString(sortDir, "ASC"))
     return 1;
 if (sameString(sortDir, "DESC"))
     return -1;  
 return 0;
 }
 
 void setupGeneSort(struct circleMap *cm)
 {
 char *sortGene = cartOptionalString(cart, hgh2SortGene);
 char *sortDir = cartOptionalString(cart, hgh2SortDir); 
 if (!sortGene)
     return;
 
 struct ring *rg;
 for (rg = cm->rings; rg; rg = rg->next)
     {
     if (!sameString(rg->name, sortGene))
 	continue;
     rg->sort = TRUE;
     rg->sortDir = getSortDirection(sortDir);    
     }
 }
 
 void setupFeatureSort(struct circleMap *cm)
 {
 char *featureSort = cartOptionalString(cart, hgh2FeatureSort);
 char *featureSortDir = cartOptionalString(cart, hgh2FeatureSortDir);
 
 struct slName *s, *sorts = NULL;
 struct slName *d, *sortDirs = NULL;
 if (!featureSort || !featureSortDir)
     return;
 
 sorts = slNameListFromComma(featureSort);
 sortDirs = slNameListFromComma(featureSortDir); 
 if (slCount(sorts) != slCount(sortDirs))
     {
     sorts = NULL;
     sortDirs = NULL;
     }
 
 if (!sorts || !sortDirs)
     return;
 
 struct ring *rg;
 for (rg = cm->rings; rg; rg = rg->next)
     {
     for (s = sorts, d = sortDirs; s && d; s = s->next, d = d->next)
 	{
 	if (!sameString(rg->name, s->name))
 	    continue;
 	rg->sort = TRUE;
 	rg->sortDir = getSortDirection(d->name);
 	}
     }
 }
 
 void sortCircleMap(struct circleMap *cm)
 { /* Sorting goes from inner ring out, averaging all probes within a ring for value */
 if (!cm->samples)
     return;
 
 /* Setup sorting by gene value */
 setupGeneSort(cm);
 
 /* Setup sorting by features */
 setupFeatureSort(cm);
 
 struct slName *sl;
 struct sortNode *sn, *snList = NULL;
 struct sortList *snl;
 struct ring *rg;
 double val = 0.0;
 for (sl = cm->samples; sl; sl = sl->next)
     {
     AllocVar(sn);
     sn->name = cloneString(sl->name);
     sn->list = NULL;
     for (rg = cm->rings; rg; rg = rg->next)
 	{
 	if (!rg->sort || !rg->data)
 	    continue;
 	struct hashEl *el = hashLookup(rg->data, sn->name);
 	if (!el)
 	    val = rg->maxVal + 0.1;  // if no val, set all to above inner ring's max
 	else
 	    {
 	    struct slDouble *sd, *sdList = el->val;
 	    val = 0.0;
 	    for (sd = sdList; sd; sd = sd->next)
 		val += sd->val;
 	    val = val / (double) slCount(sdList);
 	    }
 	AllocVar(snl);
 	snl->val = val;
 	snl->sortDirection = rg->sortDir;
 	slAddHead(&sn->list, snl);
 	}
     slReverse(&sn->list);
     slAddHead(&snList, sn);
     }
 slSort(&snList, sortNodeCmp);
 if (slCount(snList) != slCount(cm->samples))
     errAbort("Sorting failed");
 
 /* Reset sample order for circleMap according to sorted list of nodes */
 slNameFreeList(&cm->samples);
 cm->samples = NULL;
 for (sn = snList; sn; sn = sn->next)
     slNameAddHead(&cm->samples, sn->name);
 slReverse(&cm->samples);
 }
 
 void addGeneToCircleMap(struct circleMap *cm, char *datasets, char *gene)
 {
 if (!ghHash || !gene) // global, make sure it exists
     return;
 
 /* make a geneset containing single gene, for getChromHeatmapHash(...) */
 struct geneSet *gs = AllocA(struct geneSet);
 gs->name = NULL;
 gs->genes = slNameNew(gene);
 
 struct slName *sl, *slList = slNameListFromComma(datasets);
 for (sl = slList; sl; sl = sl->next)
     {
     struct hashEl *el = hashLookup(ghHash, sl->name);
     if (!el)
 	continue;
 
     struct genoHeatmap *gh = el->val;
 
     struct hash *geneHash = NULL;
     getChromHeatmapHash(&geneHash, gh->database, gh->probeTable,
 			gh->name, NULL, gs);    
     if (!geneHash)   // gene hash could not be allocated, bad.
 	continue;
     
     struct ring *rg = addRingToCircleMap(cm, gene, gh, TRUE);
     addGeneDataToRing(rg, geneHash, gene, gh->sampleList); 
 
     hashFree(&geneHash);
     }
 slReverse(&cm->rings);
 }
 
 void addFeaturesToCircleMap(struct circleMap *cm, char *datasets)
 {
 char *featureList = cartOptionalString(cart, hgh2FeatureList); 
 char *featureSort = cartOptionalString(cart, hgh2FeatureSort); 
 
 if (!featureList && !featureSort)
     return;
 
 boolean isVisible = TRUE;
 if (!featureList)
     { /* in order for sorting to work, need features in rings,
        * but should be invisible unless featureList was set */
     isVisible = FALSE;
     featureList = featureSort;
     }
 
 struct genoHeatmap *gh = NULL;
 struct slName *sl, *slList = slNameListFromComma(datasets);
 for (sl = slList; sl; sl = sl->next)
     {
     struct hashEl *el = hashLookup(ghHash, sl->name);
     if (!el)
 	continue;
     gh = el->val;
     break;
     }
 
 if (!gh)
     return;
 
 struct sqlConnection *conn = hAllocConnProfile(heatMapDbProfile, gh->patDb); 
 if (!conn)
     return;
 
 struct slName *f, *features = slNameListFromComma(featureList);
 /* necessary to reverse so that features show up in order requested
  * can't do a reverse of the rings in case there's already gene rings */
 slReverse(&features);
 
 char *raName = gh->raFile;  
 struct column *col, *colList = getColumns(conn, raName, gh->patDb); 
 for (f = features; f; f = f->next)
     {
     for (col = colList; col != NULL; col = col->next)
 	if (sameString(col->name, f->name))
 	    break;
     if (!col)
 	continue;
     
     struct ring *rg = addRingToCircleMap(cm, col->name, gh, isVisible); 
     addFeatureDataToRing(conn, rg, col, gh);
     }
 hFreeConn(&conn);
 }
 
 void addSamplesToCircleMap(struct circleMap *cm, struct slName *slList)
 {
 if (!cm || !slList)
     return;
 
 struct slName *sl;
 for (sl = slList; sl; sl = sl->next)
     {
     if (slNameInList(cm->samples, sl->name))
 	continue;
     slNameAddHead(&cm->samples, sl->name);
     }
 slReverse(&cm->samples);
 }
 
 void initSamplesCircleMap(struct circleMap *cm, char *datasets)
 {
 if (!ghHash) // global, make sure it exists
     return;
 
 struct slName *sl, *slList = slNameListFromComma(datasets);
 for (sl = slList; sl; sl = sl->next)
     {
     struct hashEl *el = hashLookup(ghHash, sl->name);
     if (!el)
 	continue;
 
     struct genoHeatmap *gh = el->val;
     defaultOrder(gh);   // populate sampleList, ensuring original order (no sorting)
     addSamplesToCircleMap(cm, gh->sampleList);
     }
 }
 
 boolean needSplit(struct ring *rg, struct slName **ptSubsets, int subsetNum)
 {
 if (!rg->data)   // no data, no split!
     return FALSE;
 
 int subset;
 struct slName *sl;
 
 int needSplit = 0;
 for (subset = 0; subset < subsetNum; subset++)
     {
     for (sl = ptSubsets[subset]; sl; sl = sl->next)
 	{
 	struct hashEl *el = hashLookup(rg->data, sl->name);
 	if (!el)
 	    continue;
 	needSplit++;
 	break;
 	}
     }
 
 return (needSplit > 1);
 }
 
 void removeSamplesFromRing(struct circleMap *cm, struct ring *rg)
 {
 if (!rg->data)  // nothing to remove
     return;
 
 struct slName *sl, *slList = NULL;
 for (sl = cm->samples; sl; sl = sl->next)
     {
     struct hashEl *el = hashLookup(rg->data, sl->name);
     if (!el)  // not in ring, so keep sample name in circle's list 
 	slNameAddHead(&slList, sl->name);
     }
 slReverse(&slList);            // maintain original order
 
 slNameFreeList(&cm->samples);  // remove old sample list
 cm->samples = slList;          // set to new list.
 }
 
 
 boolean removeRing(struct circleMap *cm, struct ring *toRemove)
 {
 /* Delete any circleMap samples on the to-be-removed ring */
 removeSamplesFromRing(cm, toRemove);
 
 if (cm->rings == toRemove) /* ring to remove is first, set to next */
     {
     cm->rings = toRemove->next;
     return TRUE;
     }
 
 struct ring *rg, *nextRg;
 for (rg = cm->rings; rg; rg = rg->next)
     {
     nextRg = rg->next;
     if (nextRg == toRemove)
 	{
 	rg->next = nextRg->next;
 	nextRg->next = NULL;
 	return TRUE;
 	}
     }
 
 return FALSE;
 }
 
 void splitRing(struct circleMap *cm, struct ring *rg, 
 	       struct slName **ptSubsets, int subsetNum)
 {
 int subset;
 struct ring *rg2, *ptRings[subsetNum];
 for (subset = 0; subset < subsetNum; subset++)
     ptRings[subset] = cloneRing(rg);
 
 struct slName *sl;
 for (subset = 0; subset < subsetNum; subset++)
     {
     rg2 = ptRings[subset];
     rg2->data = hashNew(0);
     rg2->numSamples = 0;
 
     struct slDouble *ad, *allData = NULL;
     for (sl = ptSubsets[subset]; sl; sl = sl->next)
 	{
 	struct hashEl *el = hashLookup(rg->data, sl->name);
 	if (!el)
 	    continue;
 	struct slDouble *sd, *sdList = el->val;
 
 	for (sd = sdList; sd; sd = sd->next)
 	    {
 	    ad = slDoubleNew(sd->val);
 	    slAddHead(&allData, ad);
 	    }
 	hashAdd(rg2->data, sl->name, sdList);
 	rg2->numSamples += 1;
 	hashRemove(rg->data, sl->name);
 	rg->numSamples -= 1;
 	}
     if (!allData)
 	rg2->median = 0.0;
     else
 	rg2->median = slDoubleMedian(allData);
 
     slFreeList(&allData);
     }
 
 /* Remove empty ring that was split */
 boolean removed = removeRing(cm, rg);
 if (!removed)
     errAbort("Problem with removing ring");
 
 /* Add new rings */
 for (subset = 0; subset < subsetNum; subset++)
     {
     rg = ptRings[subset];
     if (rg->numSamples == 0)
 	continue;
     slAddHead(&cm->rings, ptRings[subset]);
     }
 }
 
 void splitCircleMap(struct circleMap *cm, struct slName **ptSubsets, int subsetNum)
 {
 struct ring *rg, *nextRg;
 for (rg = cm->rings; rg; rg = nextRg)
     {
     nextRg = rg->next;
     if (!needSplit(rg, ptSubsets, subsetNum))
 	continue;
     splitRing(cm, rg, ptSubsets, subsetNum);
     }    
 }
 
 
 void setupSubgroups(struct circleMap *cm, char *datasets)
 {
 if (!ghHash) // global, make sure it exists
     return;
 int subsetNum = cartUsualInt(cart, hgh2SubgroupNum, hgh2SubgroupDefaultNum);
 
 struct slName *sl, *slList = slNameListFromComma(datasets);
 for (sl = slList; sl; sl = sl->next)
     {
     struct hashEl *el = hashLookup(ghHash, sl->name);
     if (!el)
 	continue;
     struct genoHeatmap *gh = el->val;
 
     setSubgroups(gh);
 
     char *raName = gh->raFile;
     /* This is the key function to call for subgrouping */
     struct slName **ptSubsets = NULL;
     int ifSubsets = getSubsetsIfAny(gh, subsetNum, raName, &ptSubsets);
     
     if (!ifSubsets)
 	continue;
 
     splitCircleMap(cm, ptSubsets, subsetNum);
     cm->subgrouped = TRUE;
     }
 }
 
 
 void setupCircleMap(struct circleMap *cm, char *datasets, char *gene, int width)
 {
 /* Add samples for all datasets, list will feature each sample id exactly once*/
 initSamplesCircleMap(cm, datasets);
 
 /* Add gene data (if gene is set) */
 addGeneToCircleMap(cm, datasets, gene);
 
 /* Add feature data (if featureList is set) */
 addFeaturesToCircleMap(cm, datasets);
 
 /* Set up any subgrouping info */
 setupSubgroups(cm, datasets);
 
 /* Setup any summary (displaying only median value) */
 setupSummary(cm);
 
 /* Sort circle map data */
 sortCircleMap(cm);
 
 /* Layout circle map, setting radial widths */
 layoutCircleMap(cm, width);
 }
 
 void modeGetCircleMap()
 {
 char *datasets = cartOptionalString(cart, hgh2Dataset);
 int width = cartUsualInt(cart, hgh2Width, hgHeatmapDefaultCircleWidth);
 char *gene = cartOptionalString(cart, hgh2Gene);
 char *featureList = cartOptionalString(cart, hgh2FeatureList);
 
 if (!ghHash || !datasets)
     return;
 
 if (!featureList && !gene)  // need to specify something to draw
     return;
 
 char *firstFeature = NULL;
 struct slName *f, *features;
 if (featureList)
     {
     features = slNameListFromComma(featureList);
     f = features;
     firstFeature = cloneString(f->name);
     }
 
 char *name;
 if (!gene)
     name = firstFeature;
 else
     name = gene;
 
 struct circleMap *cm = newCircleMap(name);
 setupCircleMap(cm, datasets, gene, width);
 
 struct json *js = newJson();
 char *gGif = circleMapGif(cm);
 jsonAddString(js, "heatmap", gGif);
 
 hPrintf("%s", js->print(js));
 }