b9d13eb62cfbe9db24ce1925de9efbd099354354 tdreszer Fri Feb 4 17:01:22 2011 -0800 Added support for '*Filter' generic score filter logic to beds diff --git src/hg/lib/hui.c src/hg/lib/hui.c index 2d79f11..68544f4 100644 --- src/hg/lib/hui.c +++ src/hg/lib/hui.c @@ -3459,57 +3459,61 @@ if(notFirst) dyStringPrintf(dyClause, " AND "); dyStringAppend(dyClause, clause); freeMem(clause); notFirst = TRUE; } } if(dyStringLen(dyClause) == 0) { dyStringFree(&dyClause); return NULL; } return dyStringCannibalize(&dyClause); } -void filterBySetCfgUi(struct trackDb *tdb, filterBy_t *filterBySet) +void filterBySetCfgUi(struct trackDb *tdb, filterBy_t *filterBySet, boolean onOneLine) /* Does the UI for a list of filterBy structure */ { if(filterBySet == NULL) return; #define FILTERBY_HELP_LINK "<A HREF=\"../goldenPath/help/multiView.html\" TARGET=ucscHelp>help</A>" int count = slCount(filterBySet); if(count == 1) - puts("<BR><TABLE cellpadding=3><TR valign='top'>"); + puts("<TABLE cellpadding=3><TR valign='top'>"); else - printf("<BR><B>Filter items by:</B> (select multiple categories and items - %s)<TABLE cellpadding=3><TR valign='top'>\n",FILTERBY_HELP_LINK); + printf("<B>Filter items by:</B> (select multiple categories and items - %s)<TABLE cellpadding=3><TR valign='top'>\n",FILTERBY_HELP_LINK); filterBy_t *filterBy = NULL; jsIncludeFile("ui.core.js",NULL); jsIncludeFile("ui.dropdownchecklist.js",NULL); webIncludeResourceFile("ui.dropdownchecklist.css"); int ix=0; for(filterBy = filterBySet;filterBy != NULL; filterBy = filterBy->next) { puts("<TD>"); if(count == 1) - printf("<B>Filter by %s</B> (select multiple items - %s)<BR>\n",filterBy->title,FILTERBY_HELP_LINK); + printf("<B>Filter by %s</B> (select multiple items - %s)",filterBy->title,FILTERBY_HELP_LINK); else - printf("<B>%s</B><BR>\n",filterBy->title); + printf("<B>%s</B>",filterBy->title); + if (onOneLine && count > 1) + printf("<BR>\n"); + else + printf(":\n"); // TODO: Scroll long lists //#define FILTER_COMPOSITE_OPEN_SIZE 16 // TODO: columnCount (Number of filterBoxes per row) should be configurable through tdb setting #define FILTER_BY_FORMAT "<SELECT id='fbc%d' name='%s.filterBy.%s' multiple style='display: none;' class='filterComp filterBy'><BR>\n" printf(FILTER_BY_FORMAT,ix,tdb->track,filterBy->column); ix++; printf("<OPTION%s>All</OPTION>\n",(filterBy->slChoices == NULL || slNameInList(filterBy->slChoices,"All")?" SELECTED":"") ); struct slName *slValue; if(filterBy->useIndex) { int ix=1; for(slValue=filterBy->slValues;slValue!=NULL;slValue=slValue->next,ix++) { char varName[32]; safef(varName, sizeof(varName), "%d",ix); @@ -4677,34 +4681,35 @@ } if(max) { safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX); deMax = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName); if(deMax != NULL) *max = atoi(deMax); } if(min) { safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, (max && deMax? _MIN:"")); // Warning: name changes if max! deMin = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName); if(deMin != NULL) *min = atoi(deMin); } -if(min && limitMin && *min != NO_VALUE && *min < *limitMin) *min = *limitMin; // defaults within range -if(min && limitMax && *min != NO_VALUE && *min > *limitMax) *min = *limitMax; -if(max && limitMax && *max != NO_VALUE && *max > *limitMax) *max = *limitMax; -if(max && limitMin && *max != NO_VALUE && *max < *limitMin) *max = *limitMin; +// Defaulting min and max within limits. Sorry for the horizontal ifs, but stacking the group makes them easier to follow +if (min && limitMin && *limitMin != NO_VALUE && (*min == NO_VALUE || *min < *limitMin)) *min = *limitMin; +if (min && limitMax && *limitMax != NO_VALUE && *min > *limitMax) *min = *limitMax; +if (max && limitMax && *limitMax != NO_VALUE && (*max == NO_VALUE || *max > *limitMax)) *max = *limitMax; +if (max && limitMin && *limitMin != NO_VALUE && *max < *limitMin) *max = *limitMin; } static void getScoreFloatRangeFromCart(struct cart *cart, struct trackDb *tdb, char *scoreName, double *limitMin,double *limitMax,double*min,double*max) /* gets an double score range from the cart, but the limits from trackDb for any of the pointers provided, will return a value found, if found, else it's contents are undisturbed (use NO_VALUE to recognize unavaliable values) */ { char scoreLimitName[128]; char *deMin=NULL,*deMax=NULL; if((limitMin || limitMax) && getScoreLimitsFromTdb(tdb,scoreName,NULL,&deMin,&deMax)) { if(deMin != NULL && limitMin) *limitMin = strtod(deMin,NULL); if(deMax != NULL && limitMax) @@ -4723,173 +4728,213 @@ } if(max) { safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MAX); deMax = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName); if(deMax != NULL) *max = strtod(deMax,NULL); } if(min) { safef(scoreLimitName, sizeof(scoreLimitName), "%s%s", scoreName, _MIN); // name is always {filterName}Min deMin = cartOptionalStringClosestToHome(cart, tdb,FALSE,scoreLimitName); if(deMin != NULL) *min = strtod(deMin,NULL); } -if(min && limitMin && (int)(*min) != NO_VALUE && *min < *limitMin) *min = *limitMin; // defaults within range -if(min && limitMax && (int)(*min) != NO_VALUE && *min > *limitMax) *min = *limitMax; -if(max && limitMax && (int)(*max) != NO_VALUE && *max > *limitMax) *max = *limitMax; -if(max && limitMin && (int)(*max) != NO_VALUE && *max < *limitMin) *max = *limitMin; +// Defaulting min and max within limits. Sorry for the horizontal ifs, but stacking the group makes them easier to follow +if (min && limitMin && (int)(*limitMin) != NO_VALUE && ((int)(*min) == NO_VALUE || *min < *limitMin)) *min = *limitMin; +if (min && limitMax && (int)(*limitMax) != NO_VALUE && *min > *limitMax) *min = *limitMax; +if (max && limitMax && (int)(*limitMax) != NO_VALUE && ((int)(*max) == NO_VALUE || *max > *limitMax)) *max = *limitMax; +if (max && limitMin && (int)(*limitMin) != NO_VALUE && *max < *limitMin) *max = *limitMin; } static boolean showScoreFilter(struct cart *cart, struct trackDb *tdb, boolean *opened, boolean boxed, boolean compositeLevel,char *name, char *title, char *label, - char *scoreName,char *defaults,char *limitsDefault) + char *scoreName, boolean isFloat) /* Shows a score filter control with minimum value and optional range */ { -char *setting = trackDbSettingClosestToHomeOrDefault(tdb, scoreName,defaults);//"0.0"); +char *setting = trackDbSetting(tdb, scoreName); if(setting) { if(*opened == FALSE) { boxed = cfgBeginBoxAndTitle(tdb, boxed, title); puts("<TABLE>"); *opened = TRUE; } printf("<TR><TD align='right'><B>%s:</B><TD align='left'>",label); char varName[256]; char altLabel[256]; safef(varName, sizeof(varName), "%s%s", scoreName, _BY_RANGE); boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, varName); + // NOTE: could determine isFloat = (strchr(setting,'.') != NULL); However, historical trackDb settings of pValueFilter did not always contain '.' + if (isFloat) + { double minLimit=NO_VALUE,maxLimit=NO_VALUE; - colonPairToDoubles(limitsDefault,&minLimit,&maxLimit); double minVal=minLimit,maxVal=maxLimit; colonPairToDoubles(setting,&minVal,&maxVal); getScoreFloatRangeFromCart(cart,tdb,scoreName,&minLimit,&maxLimit,&minVal,&maxVal); safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, _MIN); safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Minimum ":""), label); cgiMakeDoubleVarWithLimits(varName,minVal, altLabel, 0,minLimit, maxLimit); - if(filterByRange) // TODO: Test this range stuff which is not yet used + if(filterByRange) { printf("<TD align='left'>to<TD align='left'>"); safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, _MAX); safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Maximum ":""), label); cgiMakeDoubleVarWithLimits(varName,maxVal, altLabel, 0,minLimit, maxLimit); } safef(altLabel, sizeof(altLabel), "%s", (filterByRange?"": "colspan=3")); if(minLimit != NO_VALUE && maxLimit != NO_VALUE) printf("<TD align='left'%s> (%g to %g)",altLabel,minLimit, maxLimit); else if(minLimit != NO_VALUE) printf("<TD align='left'%s> (minimum %g)",altLabel,minLimit); else if(maxLimit != NO_VALUE) printf("<TD align='left'%s> (maximum %g)",altLabel,maxLimit); else printf("<TD align='left'%s",altLabel); + } + else + { + int minLimit=NO_VALUE,maxLimit=NO_VALUE; + int minVal=minLimit,maxVal=maxLimit; + colonPairToInts(setting,&minVal,&maxVal); + getScoreIntRangeFromCart(cart,tdb,scoreName,&minLimit,&maxLimit,&minVal,&maxVal); + safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, filterByRange ? _MIN:""); + safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Minimum ":""), label); + cgiMakeIntVarWithLimits(varName,minVal, altLabel, 0,minLimit, maxLimit); + if(filterByRange) + { + printf("<TD align='left'>to<TD align='left'>"); + safef(varName, sizeof(varName), "%s.%s%s", name, scoreName, _MAX); + safef(altLabel, sizeof(altLabel), "%s%s", (filterByRange?"Maximum ":""), label); + cgiMakeIntVarWithLimits(varName,maxVal, altLabel, 0,minLimit, maxLimit); + } + safef(altLabel, sizeof(altLabel), "%s", (filterByRange?"": "colspan=3")); + if(minLimit != NO_VALUE && maxLimit != NO_VALUE) + printf("<TD align='left'%s> (%d to %d)",altLabel,minLimit, maxLimit); + else if(minLimit != NO_VALUE) + printf("<TD align='left'%s> (minimum %d)",altLabel,minLimit); + else if(maxLimit != NO_VALUE) + printf("<TD align='left'%s> (maximum %d)",altLabel,maxLimit); + else + printf("<TD align='left'%s",altLabel); + } puts("</TR>"); return TRUE; } return FALSE; } -//#define BED_SHOWALL_SCORE_FILTERS -#ifdef BED_SHOWALL_SCORE_FILTERS -static int scoreFiltersShowAll(struct cart *cart, struct trackDb *tdb, boolean *opened, boolean boxed, +#ifdef ALL_SCORE_FILTERS_LOGIC +static int numericFiltersShowAll(struct cart *cart, struct trackDb *tdb, boolean *opened, boolean boxed, boolean compositeLevel,char *name, char *title) // Shows all *Filter style filters. Note that these are in random order and have no graceful title { int count = 0; struct slName *filterSettings = trackDbSettingsWildMatch(tdb, "*Filter"); if (filterSettings) { puts("<BR>"); struct slName *filter = NULL; + struct slPair *extras = NULL; + char *extraFields = trackDbSetting(tdb, "extraFields"); // TODo: seems like there should be a cleaner way + if (extraFields != NULL) + extras = slPairFromString(extraFields); + while ((filter = slPopHead(&filterSettings)) != NULL) { - if (differentString(filter->name,"noScoreFilter") && differentString(filter->name,"scoreFilter")) + if (differentString(filter->name,"noScoreFilter") && differentString(filter->name,"scoreFilter")) // TODO: scoreFilter could be included { - char label[128]; char *field = cloneString(filter->name); - int ix = strlen(field) - strlen("filter"); + int ix = strlen(field) - strlen("Filter"); assert(ix > 0); field[ix] = '\0'; - // Could lookup extraFields - char *extraFields = trackDbSetting(tdb, "extraFields"); // TODo: seems like there should be a cleaner way - if (extraFields) + // Could lookup extraFields // TODO: Should we be using extra fields? Could this be sorted by the order in extraFields? + if (extras != NULL) { - char *extraField = NULL; - while ((extraField = nextWord(&extraFields)) != NULL) - { - if (startsWith(field,extraField)) - { - // Found field so parse label - extraField = cloneFirstWord(extraField); - char *label = strchr(extraField,']'); - if (label == NULL) - { - label = strchr(extraField,'='); - assert(label != NULL); - } + char *foundLabel = slPairFindVal(extras, field); + if (foundLabel != NULL) + { // Found label so replace field freeMem(field); - field = cloneString(label + 1); - freeMem(extraField); - break; - } + field = strchr(foundLabel,']'); + if (field == NULL) + field = cloneString(foundLabel); + else + field = cloneString(field + 1); + strSwapChar(field,'_',' '); } - } + char label[128]; safef(label,sizeof(label),"Minimum %s",field); freeMem(field); - showScoreFilter(cart,tdb,opened,boxed,compositeLevel,name,title,label,filter->name,NULL,NULL);//,"0.0",NULL); + // Determine floating point or integer + char *setting = trackDbSetting(tdb, filter->name); + boolean isFloat = (strchr(setting,'.') != NULL); + showScoreFilter(cart,tdb,opened,boxed,compositeLevel,name,title,label,filter->name,isFloat); count++; } slNameFree(&filter); } } if (count > 0) puts("</TABLE>"); return count; } -#endif///def BED_SHOWALL_SCORE_FILTERS +#endif///def ALL_SCORE_FILTERS_LOGIC void scoreCfgUi(char *db, struct cart *cart, struct trackDb *tdb, char *name, char *title, int maxScore, boolean boxed) /* Put up UI for filtering bed track based on a score */ { char option[256]; boolean compositeLevel = isNameAtCompositeLevel(tdb,name); +boolean skipScoreFilter = FALSE; filterBy_t *filterBySet = filterBySetGet(tdb,cart,name); + +#ifdef ALL_SCORE_FILTERS_LOGIC +// Numeric filters are first +boolean isBoxOpened = FALSE; +if (numericFiltersShowAll(cart, tdb, &isBoxOpened, boxed, compositeLevel, name, title) > 0) + skipScoreFilter = TRUE; +#endif///def ALL_SCORE_FILTERS_LOGIC + +// Add any multi-selects next if(filterBySet != NULL) { - if(!tdbIsComposite(tdb)) + if(!tdbIsComposite(tdb) && cartOptionalString(cart, "ajax") == NULL) jsIncludeFile("hui.js",NULL); - filterBySetCfgUi(tdb,filterBySet); // Note filterBy boxes don't need to be double "boxed" + if (!isBoxOpened) // Note filterBy boxes are not double "boxed", if there are no other filters + printf("<BR>"); + filterBySetCfgUi(tdb,filterBySet,TRUE); filterBySetFree(&filterBySet); - return; // Cannot have both 'filterBy' score and 'scoreFilter' + skipScoreFilter = TRUE; } -#ifdef BED_SHOWALL_SCORE_FILTERS -boolean isBoxOpened = FALSE; -if (scoreFiltersShowAll(cart, tdb, &isBoxOpened, boxed, compositeLevel, name, title) > 0) +// For no good reason scoreFilter is incompatible with filterBy and or numericFilters +// FIXME scoreFilter should be implemented inside numericFilters and is currently specificly excluded to avoid unexpected changes +if (skipScoreFilter) { + #ifdef ALL_SCORE_FILTERS_LOGIC if (isBoxOpened) cfgEndBox(boxed); + #endif///def ALL_SCORE_FILTERS_LOGIC + return; // Cannot have both '*filter' and 'scoreFilter' } -#endif///def BED_SHOWALL_SCORE_FILTERS - boolean scoreFilterOk = (trackDbSettingClosestToHome(tdb, NO_SCORE_FILTER) == NULL); boolean glvlScoreMin = (trackDbSettingClosestToHome(tdb, GRAY_LEVEL_SCORE_MIN) != NULL); if (! (scoreFilterOk || glvlScoreMin)) return; boxed = cfgBeginBoxAndTitle(tdb, boxed, title); if (scoreFilterOk) { int minLimit=0,maxLimit=maxScore,minVal=0,maxVal=maxScore; getScoreIntRangeFromCart(cart,tdb,SCORE_FILTER,&minLimit,&maxLimit,&minVal,&maxVal); boolean filterByRange = trackDbSettingClosestToHomeOn(tdb, SCORE_FILTER _BY_RANGE); if (filterByRange) @@ -5190,38 +5235,75 @@ { if((int)max == NO_VALUE || ((int)maxLimit != NO_VALUE && maxLimit == max)) dyStringPrintf(extraWhere, "%s(%s >= %g)", (*and?" and ":""),field,min); // min only else if((int)min == NO_VALUE || ((int)minLimit != NO_VALUE && minLimit == min)) dyStringPrintf(extraWhere, "%s(%s <= %g)", (*and?" and ":""),field,max); // max only else dyStringPrintf(extraWhere, "%s(%s BETWEEN %g and %g)", (*and?" and ":""),field,min,max); // both min and max #endif//ndef FILTER_ASSUMES_RANGE_AT_LIMITS_IS_VALID_FILTER *and=TRUE; } } //if(dyStringLen(extraWhere)) warn("SELECT FROM %s WHERE %s",tdb->table,dyStringContents(extraWhere)); return extraWhere; } +#ifdef ALL_SCORE_FILTERS_LOGIC +struct dyString *dyAddAllScoreFilters(struct cart *cart, struct trackDb *tdb, struct dyString *extraWhere,boolean *and) +/* creates the where clause condition to gather together all random double filters + Filters are expected to follow + {fiterName}: trackDb min or min:max - default value(s); + {filterName}Min or {filterName}: min (user supplied) cart variable; + {filterName}Max: max (user supplied) cart variable; + {filterName}Limits: trackDb allowed range "0.0:10.0" Optional + uses: defaultLimits: function param if no tdb limits settings found) + The 'and' param and dyString in/out allows stringing multiple where clauses together */ +{ +struct slName *filterSettings = trackDbSettingsWildMatch(tdb, "*Filter"); +if (filterSettings) + { + struct slName *filter = NULL; + while ((filter = slPopHead(&filterSettings)) != NULL) + { + if (differentString(filter->name,"noScoreFilter") && differentString(filter->name,"scoreFilter")) // TODO: scoreFilter could be included + { + char *field = cloneString(filter->name); + int ix = strlen(field) - strlen("filter"); + assert(ix > 0); + field[ix] = '\0'; + char *setting = trackDbSetting(tdb, filter->name); + // How to determine float or int ? If actual tracDb setting has decimal places, then float! + if (strchr(setting,'.') == NULL) + extraWhere = dyAddFilterAsInt(cart,tdb,extraWhere,filter->name,"0:1000",field,and); + else + extraWhere = dyAddFilterAsDouble(cart,tdb,extraWhere,filter->name,NULL,field,and); + } + slNameFree(&filter); + } + } +return extraWhere; +} +#endif///def ALL_SCORE_FILTERS_LOGIC + void encodePeakCfgUi(struct cart *cart, struct trackDb *tdb, char *name, char *title, boolean boxed) /* Put up UI for filtering wgEnocde peaks based on score, Pval and Qval */ { boolean compositeLevel = isNameAtCompositeLevel(tdb,name); boolean opened = FALSE; -showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum Q-Value (-log 10)",QVALUE_FILTER,NULL,NULL);//,"0.0",NULL); -showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum P-Value (-log 10)",PVALUE_FILTER,NULL,NULL);//,"0.0",NULL); -showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum Signal value", SIGNAL_FILTER,NULL,NULL);//,"0.0",NULL); +showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum Q-Value (-log 10)",QVALUE_FILTER,TRUE); +showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum P-Value (-log 10)",PVALUE_FILTER,TRUE); +showScoreFilter(cart,tdb,&opened,boxed,compositeLevel,name,title,"Minimum Signal value", SIGNAL_FILTER,TRUE); char *setting = trackDbSettingClosestToHomeOrDefault(tdb, SCORE_FILTER,NULL);//"0:1000"); if(setting) { if(!opened) { boxed = cfgBeginBoxAndTitle(tdb, boxed, title); puts("<TABLE>"); opened = TRUE; } char varName[256]; int minLimit=0,maxLimit=1000,minVal=0,maxVal=NO_VALUE; colonPairToInts(setting,&minVal,&maxVal); getScoreIntRangeFromCart(cart,tdb,SCORE_FILTER,&minLimit,&maxLimit,&minVal,&maxVal); if(maxVal != NO_VALUE) @@ -5294,31 +5376,32 @@ nmdDefault = cartUsualBoolean(cart,varName, FALSE); // TODO: var name (hgt prefix) needs changing before ClosesToHome can be used printf("<p><b>Filter out NMD targets.</b>"); cgiMakeCheckBox(varName, nmdDefault); } if(!sameString(tdb->track, "tigrGeneIndex") && !sameString(tdb->track, "ensGeneNonCoding") && !sameString(tdb->track, "encodeGencodeRaceFrags")) baseColorDropLists(cart, tdb, name); if (cartOptionalString(cart, "ajax") == NULL) { filterBy_t *filterBySet = filterBySetGet(tdb,cart,name); if(filterBySet != NULL) { - filterBySetCfgUi(tdb,filterBySet); + printf("<BR>"); + filterBySetCfgUi(tdb,filterBySet,FALSE); filterBySetFree(&filterBySet); } } cfgEndBox(boxed); } static boolean isSpeciesOn(struct cart *cart, struct trackDb *tdb, char *species, char *option, int optionSize, boolean defaultState) /* check the cart to see if species is turned off or on (default is defaultState) */ { boolean ret = defaultState; safef(option, optionSize, "%s.%s", tdb->track, species); /* see if this is a simple multiz (not composite track) */ char *s = cartOptionalString(cart, option); if (s != NULL)