src/hg/lib/hui.c 1.247

1.247 2009/11/11 22:04:48 tdreszer
Changes to allow the 5th dimension
Index: src/hg/lib/hui.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/lib/hui.c,v
retrieving revision 1.246
retrieving revision 1.247
diff -b -B -U 4 -r1.246 -r1.247
--- src/hg/lib/hui.c	6 Nov 2009 22:25:42 -0000	1.246
+++ src/hg/lib/hui.c	11 Nov 2009 22:04:48 -0000	1.247
@@ -1837,8 +1837,29 @@
 return (sameString(type1, type2) ||
 	(startsWith("wig ", type1) && startsWith("wig ", type2)));
 }
 
+static char *labelRoot(char *label, char** suffix)
+/* Parses a label which may be split with a   into root and suffix
+   Always free labelRoot.  suffix, which may be null does not need to be freed. */
+{
+char *root = cloneString(label);
+char *extra=strstrNoCase(root," "); //   mean don't include the reset as part of the link
+if ((long)(extra)==-1)
+    extra=NULL;
+if (extra!=NULL)
+    {
+    *extra='\0';
+    if(suffix != NULL)
+        {
+        extra+=5;
+        *extra=' '; // Converts the   to ' ' and include the ' '
+        *suffix = extra;
+        }
+    }
+return root;
+}
+
 typedef struct _dividers {
     int count;
     char**subgroups;
     char* setting;
@@ -2090,11 +2111,12 @@
 if(dimensions!=NULL)
     {
     for(ix=0;ix<dimensions->count;ix++)
         {
-        if(startsWith("dimension",dimensions->names[ix])
-        && strlen(dimensions->names[ix]) == 10
-        && lastChar(dimensions->names[ix]) == dimension)
+        if(lastChar(dimensions->names[ix]) != dimension)
+            continue;
+        if((strlen(dimensions->names[ix]) == 10 && startsWith("dimension",dimensions->names[ix]))
+        || (strlen(dimensions->names[ix]) == 4  && startsWith("dim",dimensions->names[ix])))
             {
             members_t *members = subgroupMembersGet(parentTdb, dimensions->subgroups[ix]);
             dimensionsFree(&dimensions);
             return members;
@@ -2732,47 +2754,36 @@
 #define COLOR_BG_ALTDEFAULT_IX  1
 #define DIVIDING_LINE "<TR valign=\"CENTER\" line-height=\"1\" BGCOLOR=\"%s\"><TH colspan=\"5\" align=\"CENTER\"><hr noshade color=\"%s\" width=\"100%%\"></TD></TR>\n"
 #define DIVIDER_PRINT(color) printf(DIVIDING_LINE,COLOR_BG_DEFAULT,(color))
 
-static char *checkBoxIdMakeForTrack(struct trackDb *tdb,char *tagX,char *tagY,char *tagZ,membership_t *membership)
+static char *checkBoxIdMakeForTrack(struct trackDb *tdb,members_t** dims,int dimMax,membership_t *membership)
 /* Creates an 'id' string for subtrack checkbox in style that matrix understand: "cb_dimX_dimY_view_cb" */
 {
 int ix;
 #define CHECKBOX_ID_SZ 128
-char *id = needMem(CHECKBOX_ID_SZ);
 // What is wanted: id="cb_ES_K4_SIG_cb"
-safef(id, CHECKBOX_ID_SZ, "cb_%s_", tdb->tableName);
-//safecpy(id,CHECKBOX_ID_SZ,"cb_");
-if(tagX != NULL)
+struct dyString *id = newDyString(CHECKBOX_ID_SZ);
+dyStringPrintf(id,"cb_%s_", tdb->tableName);
+int dimIx=1; // Skips over view dim for now
+for(;dimIx<dimMax;dimIx++)
     {
-    ix = stringArrayIx(tagX, membership->subgroups, membership->count);
-    if(ix >= 0)
-        safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
-    }
-if(tagY != NULL)
+    if(dims[dimIx] != NULL)
     {
-    ix = stringArrayIx(tagY, membership->subgroups, membership->count);
+        ix = stringArrayIx(dims[dimIx]->tag, membership->subgroups, membership->count);
     if(ix >= 0)
-        safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
+            dyStringPrintf(id,"%s_", membership->membership[ix]);
     }
-if(tagZ != NULL)
-    {
-    ix = stringArrayIx(tagZ, membership->subgroups, membership->count);
-    if(ix >= 0)
-        safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
     }
-if(membership != NULL)
+if(dims[0] != NULL) // The view is saved for last
     {
     ix = stringArrayIx("view", membership->subgroups, membership->count);   // view is a known tagname
     if(ix >= 0)
-        safef(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "%s_", membership->membership[ix]);
+        dyStringPrintf(id,"%s_", membership->membership[ix]);
     }
-safecat(id+strlen(id), CHECKBOX_ID_SZ-strlen(id), "cb");
-// If all else fails:
-//if(strlen(id) <= 5)
-//    safef(id, CHECKBOX_ID_SZ, "cb_%s", tdb->tableName);
-return id;
+dyStringAppend(id,"cb");
+return dyStringCannibalize(&id);
 }
+
 static void checkBoxIdFree(char**id)
 /* Frees 'id' string */
 {
 if(id && *id)
@@ -3019,18 +3030,34 @@
 hierarchy_t *hierarchy = hierarchySettingGet(parentTdb);
 
 enum
 {
-    dimX=0,
-    dimY=1,
-    dimZ=2,
-    dimV=3,
-    dimMax=4
+    dimV=0, // View first
+    dimX=1, // X & Y next
+    dimY=2,
+    dimA=3, // dimA is start of first of the optional non-matrix, non-view dimensions
 };
-members_t* dimensions[dimMax];
-dimensions[dimX]=subgroupMembersGetByDimension(parentTdb,'X');
-dimensions[dimY]=subgroupMembersGetByDimension(parentTdb,'Y');
-dimensions[dimZ]=subgroupMembersGetByDimension(parentTdb,'Z');
+int dimMax=dimA;  // This can expand, depending upon ABC dimensions
+members_t* dimensions[27]; // Just pointers, so make a bunch!
+dimensions_t *dims = dimensionSettingsGet(parentTdb);
+if(dims != NULL)
+    {
+    int ix;
+    for(ix=0;ix<dims->count;ix++)
+        {
+        char letter = lastChar(dims->names[ix]);
+        if(letter != 'X' && letter != 'Y')
+            {
+            dimensions[dimMax]=subgroupMembersGet(parentTdb, dims->subgroups[ix]);
+            dimMax++;
+            }
+        else if(letter == 'X')
+            dimensions[dimX]=subgroupMembersGet(parentTdb, dims->subgroups[ix]);
+        else
+            dimensions[dimY]=subgroupMembersGet(parentTdb, dims->subgroups[ix]);
+        }
+    dimensionsFree(&dims);
+    }
 dimensions[dimV]=subgroupMembersGet(parentTdb,"view");
 int dimCount=0,di;
 for(di=0;di<dimMax;di++) { if(dimensions[di]) dimCount++; }
 sortOrder_t* sortOrder = sortOrderGet(cart,parentTdb);
@@ -3181,24 +3208,24 @@
                 if( divisionIfNeeded(lastDivide,dividers,membership) )
                     colorIx = (colorIx == COLOR_BG_DEFAULT_IX ? COLOR_BG_ALTDEFAULT_IX : COLOR_BG_DEFAULT_IX);
                 }
 
-            char *id = checkBoxIdMakeForTrack(subtrack,
-                        (dimensions[dimX]?dimensions[dimX]->tag:NULL),
-                        (dimensions[dimY]?dimensions[dimY]->tag:NULL),
-                        (dimensions[dimZ]?dimensions[dimZ]->tag:NULL),membership); // view is known tag
+            char *id = checkBoxIdMakeForTrack(subtrack,dimensions,dimMax,membership); // view is known tag
             printf("<TR valign='top' BGCOLOR=\"%s\"",colors[colorIx]);
             if(useDragAndDrop)
                 printf(" class='trDraggable' title='Drag to Reorder'");
 
             printf(" id=\"tr_%s\" nowrap%s>\n<TD>",id,(selectedOnly?" style='display:none'":""));
             dyStringClear(dyHtml);
             dyStringPrintf(dyHtml, "onclick='matSubCbClick(this);' onmouseover=\"this.style.cursor='default';\" class=\"subCB");
-            for(di=0;di<dimMax;di++)
+            for(di=dimX;di<dimMax;di++)
                 {
                 if(dimensions[di] && -1 != (ix = stringArrayIx(dimensions[di]->tag, membership->subgroups, membership->count)))
                     dyStringPrintf(dyHtml, " %s",membership->membership[ix]);
                 }
+            // Save view for last
+            if(dimensions[dimV] && -1 != (ix = stringArrayIx(dimensions[dimV]->tag, membership->subgroups, membership->count)))
+                dyStringPrintf(dyHtml, " %s",membership->membership[ix]);
             dyStringAppendC(dyHtml,'"');
             cgiMakeCheckBox2BoolWithIdAndJS(htmlIdentifier,checkedCB,enabledCB,id,dyStringContents(dyHtml));
             if(sortOrder != NULL || useDragAndDrop)
                 {
@@ -3214,16 +3241,19 @@
                     {
                     ix = stringArrayIx(sortOrder->column[sIx], membership->subgroups, membership->count);                        // TODO: Sort needs to expand from subGroups to labels as well
                     if(ix >= 0)
                         {
+                        char *titleRoot=labelRoot(membership->titles[ix],NULL);
+
 #define CFG_SUBTRACK_LINK  "<A HREF='#a_cfg_%s' onclick='return subtrackCfgShow(\"%s\");' title='Subtrack Configuration'>%s</A>\n"
 #define MAKE_CFG_SUBTRACK_LINK(table,title) printf(CFG_SUBTRACK_LINK, (table),(table),(title))
                         printf ("<TD id='%s' nowrap abbr='%s' align='left'>&nbsp;",sortOrder->column[sIx],membership->membership[ix]);
                         if(cType != cfgNone && sameString("view",sortOrder->column[sIx]))
-                            MAKE_CFG_SUBTRACK_LINK(subtrack->tableName,membership->titles[ix]);  // FIXME: Currently configurable under sort only supported when multiview
+                            MAKE_CFG_SUBTRACK_LINK(subtrack->tableName,titleRoot);  // FIXME: Currently configurable under sort only supported when multiview
                         else
-                            printf("%s\n",membership->titles[ix]);
+                            printf("%s\n",titleRoot);
                         puts ("</TD>");
+                        freeMem(titleRoot);
                         }
                     }
                 }
             else
@@ -3285,9 +3315,9 @@
 if (!primarySubtrack)
     puts("<script type='text/javascript'>matInitializeMatrix();</script>");
 if(dependentCfgsNeedBinding)
     cfgLinkToDependentCfgs(parentTdb,parentTdb->tableName);
-for(di=dimX;di<dimMax;di++)
+for(di=0;di<dimMax;di++)
     subgroupMembersFree(&dimensions[di]);
 dyStringFree(&dyHtml)
 sortOrderFree(&sortOrder);
 dividersFree(&dividers);
@@ -4843,29 +4873,21 @@
 boolean found=FALSE;
 if((count = chopByWhite(cloneString(vocab), words,15)) <= 1)
     return cloneString(label);
 
-char labelRoot[128];
-safecpy(labelRoot,sizeof(labelRoot),label);
-char *extra=strstrNoCase(labelRoot,"&nbsp;"); // &nbsp; mean don't include the reset as part of the link
-if ((long)extra==-1)
-    extra=NULL;
-if (extra!=NULL)
-    {
-    *extra='\0';
-    extra+=6;
-    }
+char *suffix=NULL;
+char *rootLabel = labelRoot(label,&suffix);
 
 for(ix=1;ix<count && !found;ix++)
     {
 #define VOCAB_LINK "<A HREF='hgEncodeVocab?ra=/usr/local/apache/cgi-bin/%s&term=\"%s\"' title='%s details' TARGET=ucscVocab>%s</A>"
     if(sameString(vocabType,words[ix])) // controlledVocabulary setting matches tag so all labels are linked
         {
         int sz=strlen(VOCAB_LINK)+strlen(words[0])+strlen(words[ix])+2*strlen(label) + 2;
         char *link=needMem(sz);
-        safef(link,sz,VOCAB_LINK,words[0],words[ix],labelRoot,labelRoot);
-        if(extra)
-            safecat(link,sz,extra);
+        safef(link,sz,VOCAB_LINK,words[0],words[ix],rootLabel,rootLabel);
+        if(suffix)
+            safecat(link,sz,suffix);
         freeMem(words[0]);
         return link;
         }
     else if(countChars(words[ix],'=') == 1 && childTdb != NULL) // The name of a trackDb setting follows and will be the controlled vocab term
@@ -4879,11 +4901,11 @@
                 {
                 char *encodedTerm = cgiEncode(cvTerm);
                 int sz=strlen(VOCAB_LINK)+strlen(words[0])+strlen(encodedTerm)+2*strlen(label) + 2;
                 char *link=needMem(sz);
-                safef(link,sz,VOCAB_LINK,words[0],encodedTerm,cvTerm,labelRoot);
-                if(extra)
-                    safecat(link,sz,extra);
+                safef(link,sz,VOCAB_LINK,words[0],encodedTerm,cvTerm,rootLabel);
+                if(suffix)
+                    safecat(link,sz,suffix);
                 freeMem(words[0]);
                 freeMem(cvTerm);
                 freeMem(encodedTerm);
                 return link;
@@ -4891,8 +4913,9 @@
             }
         }
     }
 freeMem(words[0]);
+freeMem(rootLabel);
 return cloneString(label);
 }
 
 #define PM_BUTTON_UC "<IMG height=18 width=18 onclick=\"return (matSetMatrixCheckBoxes(%s%s%s%s%s%s) == false);\" id='btn_%s' src='../images/%s'>"
@@ -5026,8 +5049,82 @@
 else if (left && dimensionY && childTdb != NULL)
     printf("<TH ALIGN=RIGHT nowrap>%s</TH>\n",labelWithVocabLink(parentTdb,childTdb,dimensionY->tag,dimensionY->values[ixY]));
 }
 
+static int displayNonXYdimensions(struct cart *cart, struct trackDb *parentTdb)
+/* This will walk through all declared nonX&Y dimensions (X and Y is the 2D matrix of CBs. */
+{
+int count=0,ix;
+for(ix=0;ix<26;ix++)
+    {
+    char dimLetter = 'A' + ix;
+    if(dimLetter == 'X' || dimLetter == 'Y')
+        continue;
+
+    members_t *dim = subgroupMembersGetByDimension(parentTdb,dimLetter);
+    if(dim==NULL)
+        continue;
+    if(dim->count<1)
+        {
+        subgroupMembersFree(&dim);
+        continue;
+        }
+    count++;
+    int cells[dim->count];         // The Z dimension is a separate 1D matrix
+    struct trackDb *tdbs[dim->count];
+    memset(cells, 0, sizeof(cells));
+    memset(tdbs,  0, sizeof(tdbs));
+
+    int aIx;
+    struct trackDb *subtrack;
+    for (subtrack = parentTdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
+        {
+        char *value;
+        if(subgroupFind(subtrack,dim->tag,&value))
+            {
+            aIx = stringArrayIx(value,dim->names,dim->count);
+            cells[aIx]++;
+            tdbs[aIx] = subtrack;
+            subgroupFree(&value);
+            }
+        }
+
+    if(count==1) // First time set up a table
+        puts("<BR><TABLE>");
+    printf("<TR><TH valign=top align='right'>&nbsp;&nbsp;<B><EM>%s</EM></B>:</TH>",dim->title);
+    char settingName[32];
+    safef(settingName,sizeof(settingName),"dimension%cchecked",dimLetter);
+    char *dimCheckedDefaults = trackDbSettingOrDefault(parentTdb,settingName,"");
+    boolean alreadySet=FALSE;
+    for(aIx=0;aIx<dim->count;aIx++)
+        {
+        if(tdbs[aIx] != NULL && cells[aIx]>0)
+            {
+            printf("<TH align=left nowrap>");
+            char objName[SMALLBUF];
+            char javascript[JBUFSIZE];
+            safef(objName, sizeof(objName), "%s.mat_%s_dim%c_cb",parentTdb->tableName,dim->names[aIx],dimLetter);
+            alreadySet=(NULL!=findWordByDelimiter(dim->names[aIx],',',dimCheckedDefaults));
+            alreadySet=cartUsualBoolean(cart,objName,alreadySet);
+            safef(javascript,sizeof(javascript),"onclick='matCbClick(this);' class=\"matCB abc %s\"",dim->names[aIx]);
+            // TODO Set classes properly (if needed!!!)  The classes could be
+            // a) matCB to keep the list of all
+            // b) dimZ or some other designator but the current dimZ would work
+            // c) a new one (subgroup tag?) to break dimZs into subsets.  But how to recognize the subsets?  Will js need to pick by name?
+            cgiMakeCheckBoxJS(objName,alreadySet,javascript);
+            printf("%s",labelWithVocabLink(parentTdb,tdbs[aIx],dim->tag,dim->values[aIx]));
+            puts("</TH>");
+            }
+        }
+    puts("</TR>");
+    subgroupMembersFree(&dim);
+    }
+if(count>0)
+    puts("</TABLE>");
+return count;
+}
+
+
 static boolean hCompositeUiByMatrix(char *db, struct cart *cart, struct trackDb *parentTdb, char *formName)
 /* UI for composite tracks: matrix of checkboxes. */
 {
 //int ix;
@@ -5037,35 +5134,28 @@
 
 if(!dimensionsExist(parentTdb))
     return FALSE;
 
-int ixX,ixY,ixZ;
+int ixX,ixY;
 members_t *dimensionX = subgroupMembersGetByDimension(parentTdb,'X');
 members_t *dimensionY = subgroupMembersGetByDimension(parentTdb,'Y');
-members_t *dimensionZ = subgroupMembersGetByDimension(parentTdb,'Z');
-if(dimensionX == NULL && dimensionY == NULL && dimensionZ == NULL) // Must be an X, Y or Z dimension
+if(dimensionX == NULL && dimensionY == NULL) // Must be an X or Y dimension
     return FALSE;
 
 // use array of char determine all the cells (in X,Y,Z dimensions) that are actually populated
 char *value;
 int sizeOfX = dimensionX?dimensionX->count:1;
 int sizeOfY = dimensionY?dimensionY->count:1;
-int sizeOfZ = dimensionZ?dimensionZ->count:1;
 int cells[sizeOfX][sizeOfY]; // There needs to be atleast one element in dimension
-int cellsZ[sizeOfX];         // The Z dimension is a separate 1D matrix
 struct trackDb *tdbsX[sizeOfX]; // Representative subtracks
 struct trackDb *tdbsY[sizeOfY];
-struct trackDb *tdbsZ[sizeOfZ];
 memset(cells, 0, sizeof(cells));
-memset(cellsZ, 0, sizeof(cellsZ));
 memset(tdbsX, 0, sizeof(tdbsX));
 memset(tdbsY, 0, sizeof(tdbsY));
-memset(tdbsZ, 0, sizeof(tdbsZ));
 for (subtrack = parentTdb->subtracks; subtrack != NULL; subtrack = subtrack->next)
     {
     ixX = (dimensionX ? -1 : 0 );
     ixY = (dimensionY ? -1 : 0 );
-    ixZ = (dimensionZ ? -1 : 0 );
     if(dimensionX && subgroupFind(subtrack,dimensionX->tag,&value))
         {
         ixX = stringArrayIx(value,dimensionX->names,dimensionX->count);
         tdbsX[ixX] = subtrack;
@@ -5076,60 +5166,28 @@
         ixY = stringArrayIx(value,dimensionY->names,dimensionY->count);
         tdbsY[ixY] = subtrack;
         subgroupFree(&value);
         }
-    if(dimensionZ && subgroupFind(subtrack,dimensionZ->tag,&value))
-        {
-        ixZ = stringArrayIx(value,dimensionZ->names,dimensionZ->count);
-        tdbsZ[ixZ] = subtrack;
-        subgroupFree(&value);
-        }
     if(ixX > -1 && ixY > -1)
         cells[ixX][ixY]++;
-    if(ixZ > -1)
-        cellsZ[ixZ]++;
     }
 
 //puts("<B>Select subtracks by characterization:</B><BR>");
 printf("<B>Select subtracks by ");
 if(dimensionX && !dimensionY)
     safef(javascript, sizeof(javascript), "%s:</B>",dimensionX->title);
 else if(!dimensionX && dimensionY)
     safef(javascript, sizeof(javascript), "%s:</B>",dimensionY->title);
-else if(dimensionX && dimensionY && !dimensionZ)
-    safef(javascript, sizeof(javascript), "%s and %s:</B>",dimensionX->title,dimensionY->title);
-else //if(dimensionX && dimensionY && dimensionZ)
-    safef(javascript, sizeof(javascript), "%s, %s and %s:</B>",dimensionZ->title,dimensionX->title,dimensionY->title);
+else
+    safef(javascript, sizeof(javascript), "multiple variables:</B>");
 puts(strLower(javascript));
 
 if(!subgroupingExists(parentTdb,"view"))
     puts("(<A HREF=\"../goldenPath/help/multiView.html\" title='Help on views' TARGET=_BLANK>help</A>)\n");
 
 puts("<BR>\n");
 
-if(dimensionZ)
-    {
-    printf("<BR><TABLE>\n");
-    printf("<TR><TH valign=top align=left colspan=2 rowspan=20>&nbsp;&nbsp;<B><EM>%s</EM></B>:",dimensionZ->title);
-    char *dimZdefaults = trackDbSettingOrDefault(parentTdb,"dimensionZchecked","");
-    boolean alreadySet=FALSE;
-    for(ixZ=0;ixZ<sizeOfZ;ixZ++)
-        {
-        if(tdbsZ[ixZ] != NULL && cellsZ[ixZ]>0)
-            {
-            printf("<TH align=left nowrap>");
-            safef(objName, sizeof(objName), "%s.mat_%s_dimZ_cb",parentTdb->tableName,dimensionZ->names[ixZ]);
-            alreadySet=(NULL!=findWordByDelimiter(dimensionZ->names[ixZ],',',dimZdefaults));
-            alreadySet=cartUsualBoolean(cart,objName,alreadySet);
-            struct dyString *dyJS = dyStringCreate("onclick='matCbClick(this);'");
-            dyStringPrintf(dyJS, " class=\"matCB dimZ %s\"",dimensionZ->names[ixZ]);
-            cgiMakeCheckBoxJS(objName,alreadySet,dyStringCannibalize(&dyJS));// dimZ is set by cart variable, while X and Y are set by subtrack sets
-            printf("%s",labelWithVocabLink(parentTdb,tdbsZ[ixZ],dimensionZ->tag,dimensionZ->values[ixZ]));
-            puts("</TH>");
-            }
-        }
-    puts("</TD></TR></TABLE>");
-    }
+displayNonXYdimensions(cart,parentTdb);
 
 printf("<TABLE class='greenBox' bgcolor='%s' borderColor='%s'>\n",COLOR_BG_DEFAULT,COLOR_BG_DEFAULT);
 
 matrixXheadings(parentTdb,dimensionX,dimensionY,tdbsX,TRUE);
@@ -5194,9 +5252,8 @@
 puts("</TD></TR></TABLE>");
 
 subgroupMembersFree(&dimensionX);
 subgroupMembersFree(&dimensionY);
-subgroupMembersFree(&dimensionZ);
 puts("<BR>\n");
 
 return TRUE;
 }