5bfb32fdfc639f9bffb5435044cef164bdde0b28 kent Sun Dec 12 08:02:13 2021 -0800 Implementing barChartMerge trackDb option for faceted bar charts. diff --git src/hg/lib/tablesTables.c src/hg/lib/tablesTables.c index dff300a..7612139 100644 --- src/hg/lib/tablesTables.c +++ src/hg/lib/tablesTables.c @@ -155,31 +155,33 @@ static void showTableFilterControlRow(struct fieldedTable *table, struct slName *visibleFields, struct cart *cart, char *varPrefix, int maxLenField, struct hash *suggestHash) /* Assuming we are in table already drow control row. * The suggestHash is keyed by field name. If something is there we'll assume * it's value is slName list of suggestion values */ { /* Include javascript and style we need */ printf("<link rel='stylesheet' href='//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css'>\n"); printf("<script src='https://code.jquery.com/ui/1.12.1/jquery-ui.js'></script>\n"); printf("<tr>"); struct slName *el; for (el = visibleFields; el != NULL; el = el->next) { char *field = el->name; - int fieldIx = fieldedTableMustFindFieldIx(table, field); + int fieldIx = fieldedTableFindFieldIx(table, field); + if (fieldIx >= 0) + { char varName[256]; safef(varName, sizeof(varName), "%s_f_%s", varPrefix, field); printf("<td>"); /* Approximate size of input control in characters */ int size = fieldedTableMaxColChars(table, fieldIx); if (size > maxLenField) size = maxLenField; /* Print input control getting previous value from cart. Set an id= * so auto-suggest can find this control. */ char *oldVal = cartUsualString(cart, varName, ""); printf("<INPUT TYPE=TEXT NAME=\"%s\" id=\"%s\" SIZE=%d", varName, varName, size+1); if (isEmpty(oldVal)) @@ -189,158 +191,172 @@ /* Write out javascript to reset page number to 1 if filter changes */ resetPageNumberOnChange(varName, varPrefix); /* Set up the auto-suggest list for this filter */ if (suggestHash != NULL) { struct slName *suggestList = hashFindVal(suggestHash, field); if (suggestList != NULL) { printSuggestScript(varName, suggestList); } } printf("</td>\n"); } + } printf("</TR>"); } static void showTableSortingLabelRow(struct fieldedTable *table, struct slName *visibleFields, struct cart *cart, char *varPrefix, char *returnUrl) /* Put up the label row with sorting fields attached. ALso actually sort table. */ { /* Get order var */ char orderVar[256]; safef(orderVar, sizeof(orderVar), "%s_order", varPrefix); char *orderFields = cartUsualString(cart, orderVar, ""); char pageVar[64]; safef(pageVar, sizeof(pageVar), "%s_page", varPrefix); /* Print column labels */ struct slName *vis; for (vis = visibleFields; vis != NULL; vis = vis->next) { + if (fieldedTableFindFieldIx(table, vis->name) != -1) + { printf("<td>"); printf("<A class=\"topbar\" HREF=\""); printf("%s", returnUrl); printf("&%s=1", pageVar); printf("&%s=", orderVar); char *field = vis->name; if (!isEmpty(orderFields) && sameString(orderFields, field)) printf("-"); printf("%s", field); printf("\">"); printf("%s", field); if (!isEmpty(orderFields)) { char *s = orderFields; boolean isRev = (s[0] == '-'); if (isRev) ++s; if (sameString(field, s)) { if (isRev) printf("↑"); else printf("↓"); } } printf("</A>"); printf("</td>\n"); } + } /* Sort on field */ if (!isEmpty(orderFields)) { boolean doReverse = FALSE; char *field = orderFields; if (field[0] == '-') { field += 1; doReverse = TRUE; } fieldedTableSortOnField(table, field, doReverse); } } static void showTableDataRows(struct fieldedTable *table, struct slName *visibleFields, int pageSize, int maxLenField, struct hash *tagOutputWrappers, void *wrapperContext) /* Render data rows into HTML */ { /* Look up visible fields in table */ int visFieldCount = slCount(visibleFields); int visIx[visFieldCount]; int i; struct slName *el = visibleFields;; for (i=0; i<visFieldCount; ++i, el = el->next) - visIx[i] = fieldedTableMustFindFieldIx(table, el->name); + visIx[i] = fieldedTableFindFieldIx(table, el->name); /* Figure out numerical ones */ int count = 0; struct fieldedRow *row; boolean isNum[visFieldCount]; for (i=0; i<visFieldCount; ++i) + { + int vix = visIx[i]; + if (vix >= 0) isNum[i] = fieldedTableColumnIsNumeric(table, visIx[i]); + else + isNum[i] = FALSE; + } for (row = table->rowList; row != NULL; row = row->next) { if (++count > pageSize) break; printf("<TR>\n"); int fieldIx = 0; int i; for (i=0; i<visFieldCount; ++i) { fieldIx = visIx[i]; + if (fieldIx >= 0) + { char shortVal[maxLenField+1]; char *longVal = emptyForNull(row->row[fieldIx]); char *val = longVal; int valLen = strlen(val); if (maxLenField > 0 && maxLenField < valLen) { if (valLen > maxLenField) { memcpy(shortVal, val, maxLenField-3); shortVal[maxLenField-3] = 0; strcat(shortVal, "..."); val = shortVal; } } - if (isNum[fieldIx]) // vacuous, but left it just in case we want to do different stuff to numbers later + if (isNum[fieldIx]) // vacuous, but left it just in case we want + // to do different stuff to numbers later printf("<td>"); else printf("<td>"); boolean printed = FALSE; if (tagOutputWrappers != NULL && !isEmpty(val)) { char *field = table->fields[fieldIx]; webTableOutputWrapperType *printer = hashFindVal(tagOutputWrappers, field); if (printer != NULL) { printer(table, row, field, longVal, val, wrapperContext); printed = TRUE; } } if (!printed) printf("%s", val); printf("</td>\n"); } + } printf("</TR>\n"); } } static void showTablePaging(struct fieldedTable *table, struct cart *cart, char *varPrefix, struct fieldedTableSegment *largerContext, int pageSize) /* If larger context exists and is bigger than current display, then draw paging controls. */ { /* Handle paging if any */ if (largerContext != NULL) // Need to page? { if (pageSize < largerContext->tableSize) { int curPage = largerContext->tableOffset/pageSize; int totalPages = (largerContext->tableSize + pageSize - 1)/pageSize; @@ -397,107 +413,111 @@ "event.target.closest('form').submit();\n" , varPrefix, totalPages); } } } } void webFilteredFieldedTable(struct cart *cart, struct fieldedTable *table, char *visibleFieldList, char *returnUrl, char *varPrefix, int maxLenField, struct hash *tagOutputWrappers, void *wrapperContext, boolean withFilters, char *pluralInstructions, int pageSize, int facetUsualSize, struct fieldedTableSegment *largerContext, struct hash *suggestHash, struct facetField **ffArray, char *visibleFacetList, - void (*addFunc)(int) ) + void (*addFunc)(int), boolean facetMergeOk ) /* Show a fielded table that can be sorted by clicking on column labels and optionally * that includes a row of filter controls above the labels . * The maxLenField is maximum character length of field before truncation with ... * Pass in 0 for no max. */ { if (strchr(returnUrl, '?') == NULL) errAbort("Expecting returnUrl to include ? in showFieldedTable\nIt's %s", returnUrl); if (pluralInstructions != NULL) showTableFilterInstructionsEtc(table, pluralInstructions, largerContext, addFunc, visibleFacetList, varPrefix); if (visibleFacetList) { // Show top bar with quick-deselects for selected facet values // as well a clear restriction button that cleans out _filter cart var. struct dyString *facetBar = dyStringNew(1024); char filterVar[256]; safef(filterVar, sizeof(filterVar), "%s_filter", varPrefix); char *where = cartUsualString(cart, filterVar, ""); boolean gotSelected = FALSE; struct slName *visList = slNameListFromComma(visibleFacetList); struct slName *vis; for (vis = visList; vis != NULL; vis = vis->next) { - int f = fieldedTableMustFindFieldIx(table, vis->name); - struct facetField *field = ffArray[f]; + int fIx = fieldedTableFindFieldIx(table, vis->name); + if (fIx >= 0) + { + struct facetField *field = ffArray[fIx]; if (!field->allSelected) { gotSelected = TRUE; htmlDyStringPrintf(facetBar, "<span class='card facet-card' style='display: inline-block;'><span class='card-body'>\n"); htmlDyStringPrintf(facetBar, "<dt style='display: inline-block;'>\n"); - htmlDyStringPrintf(facetBar, "<h6 class='card-title'>%s</h6></dt>\n", field->fieldName); + htmlDyStringPrintf(facetBar, "<h6 class='card-title'>%s</h6></dt>\n", + field->fieldName); struct facetVal *val; // Sort values alphabetically // Make a copy to not disturb the original order struct facetVal *valListCopy = facetsClone(field->valList); slSort(&valListCopy, facetValCmp); for (val = valListCopy; val; val=val->next) { boolean specificallySelected = (val->selected && !field->allSelected); if (specificallySelected) { char *op = "remove"; - htmlDyStringPrintf(facetBar, "<dd class=\"facet\" style='display: inline-block;'>\n"); - htmlDyStringPrintf(facetBar, "<input type=checkbox value=%s class=ttFsCheckBox %s> ", + htmlDyStringPrintf(facetBar, + "<dd class=\"facet\" style='display: inline-block;'>\n"); + htmlDyStringPrintf(facetBar, + "<input type=checkbox value=%s class=ttFsCheckBox %s> ", specificallySelected ? "true" : "false", specificallySelected ? "checked" : ""); htmlDyStringPrintf(facetBar, "<a href='%s" "&%s_facet_op=%s|url|" "&%s_facet_fieldName=%s|url|" "&%s_facet_fieldVal=%s|url|" "&%s_page=1'" ">", returnUrl, varPrefix, op, varPrefix, field->fieldName, varPrefix, val->val, varPrefix ); htmlDyStringPrintf(facetBar, "%s (%d)</a>", naForEmpty(val->val), val->selectCount); htmlDyStringPrintf(facetBar, "</dd>\n"); } } slFreeList(&valListCopy); htmlDyStringPrintf(facetBar, "</span></span>\n"); - } - + } } if (!isEmpty(where) || gotSelected) { printf("<div>\n"); } if (!isEmpty(where)) { // left column printf("Restricting files to where %s. ", where); printf("  "); printf("<input class='btn btn-secondary' type='button' id='clearRestrictionButton' VALUE=\"Clear Restriction\">"); @@ -535,67 +555,106 @@ dyStringFree(&facetBar); } printf("<div class='row'>\n"); // parent container if (visibleFacetList) { // left column printf("<div class='col-xs-6 col-sm-4 col-md-4 col-lg-3 col-xl-3'>\n"); struct slName *visList = slNameListFromComma(visibleFacetList); struct slName *vis; for (vis = visList; vis != NULL; vis = vis->next) { + char *fieldName = vis->name; char selfId[256]; - safef(selfId, sizeof(selfId), "%s_self_a_%s", varPrefix, vis->name); + safef(selfId, sizeof(selfId), "%s_self_a_%s", varPrefix, fieldName); subChar(selfId, ' ', '_'); - int f = fieldedTableMustFindFieldIx(table, vis->name); - struct facetField *field = ffArray[f]; + /* Work on facet field label line */ htmlPrintf("<div id=\"%s\" class='card facet-card'><div class='card-body'>\n", selfId); - htmlPrintf("<h6 class='card-title'>%s</h6><dl>\n", field->fieldName); - struct facetVal *val; + htmlPrintf("<h6 class='card-title'>%s",vis->name); + + int f = fieldedTableFindFieldIx(table, fieldName); + char *op = "unmerge"; + struct facetField *field = NULL; + if (f >= 0) + { + field = ffArray[f]; + if (!field->isMerged) + op = "merge"; + } + + /* Write merge/unmerge link and number of categories */ + if (facetMergeOk) + { + htmlPrintf("<span style='float:right'>"); + htmlPrintf("<a class='btn btn-secondary' href='%s" + "&%s_facet_op=%s|none|" + "&%s_facet_fieldName=%s|url|" + "&%s_facet_fieldVal=%s|url|" + "&%s_page=1' " + ">", + returnUrl, varPrefix, op, varPrefix, fieldName, + varPrefix, "", varPrefix); + htmlPrintf("%s", op); + if (field != NULL && sameString(op, "merge")) + { + int selectedFieldCount = facetFieldCountSelected(field); + htmlPrintf(" %d", selectedFieldCount); + } + htmlPrintf("</a></span>"); + } + + /* CLose up facet field label line */ + htmlPrintf("</h6><dl>\n"); + + if (field != NULL) + { + struct facetVal *val; if (!field->allSelected) // add reset facet link { char *op = "reset"; htmlPrintf("<dd><a class='btn btn-secondary' href='%s" "&%s_facet_op=%s|url|" "&%s_facet_fieldName=%s|url|" "&%s_facet_fieldVal=%s|url|" "&%s_page=1' " ">%s</a></dd>\n", returnUrl, varPrefix, op, varPrefix, field->fieldName, varPrefix, "", varPrefix, "Clear" ); } int valuesShown = 0; int valuesNotShown = 0; if (field->showAllValues) // Sort alphabetically if they want all values { slSort(&field->valList, facetValCmp); } int extraAnchorPeriod = 15; int extraAnchorPos = 0; for (val = field->valList; val; val=val->next) { boolean specificallySelected = (val->selected && !field->allSelected); - if ((val->selectCount > 0 && (field->showAllValues || valuesShown < facetUsualSize)) + if ((val->selectCount > 0 && + (field->showAllValues || valuesShown < facetUsualSize) && + !field->isMerged) || specificallySelected) { ++valuesShown; ++extraAnchorPos; char *op = "add"; if (specificallySelected) op = "remove"; printf("<dd class=\"facet\""); if (extraAnchorPos >= extraAnchorPeriod) { safef(selfId, sizeof(selfId), "%s_self_a_%s_%s", varPrefix, vis->name, val->val); subChar(selfId, ' ', '_'); printf(" id=\"%s\"", selfId); extraAnchorPos= 0; @@ -611,61 +670,62 @@ "&%s_page=1#%s' " ">", returnUrl, varPrefix, op, varPrefix, field->fieldName, varPrefix, val->val, varPrefix, selfId ); htmlPrintf("%s (%d)</a>", naForEmpty(val->val), val->selectCount); printf("</dd>\n"); } else if (val->selectCount > 0) { ++valuesNotShown; } } // show "See More" link when facet has lots of values - if (valuesNotShown > 0) + if (valuesNotShown > 0 && !field->isMerged) { char *op = "showAllValues"; htmlPrintf("<dd><a href='%s" "&%s_facet_op=%s|url|" "&%s_facet_fieldName=%s|url|" "&%s_facet_fieldVal=%s|url|" "&%s_page=1#%s' " ">See %d More</a></dd>\n", returnUrl, varPrefix, op, varPrefix, field->fieldName, varPrefix, "", varPrefix, selfId, valuesNotShown ); } // show "See Fewer" link when facet has lots of values if (field->showAllValues && valuesShown >= facetUsualSize) { safef(selfId, sizeof(selfId), "%s_self_a_%s", varPrefix, vis->name); subChar(selfId, ' ', '_'); char *op = "showSomeValues"; htmlPrintf("<dd><a href='%s" "&%s_facet_op=%s|url|" "&%s_facet_fieldName=%s|url|" "&%s_facet_fieldVal=%s|url|" "&%s_page=1#%s' " ">%s</a></dd>\n", returnUrl, varPrefix, op, varPrefix, field->fieldName, varPrefix, "", varPrefix, selfId, "See Fewer" ); } + } htmlPrintf("</div></div>\n"); } printf("</div>\n"); // Clicking a checkbox is actually a click on the following link jsInlineF( "$(function () {\n" " $('.ttFsCheckBox').click(function() {\n" " this.nextSibling.nextSibling.click();\n" " });\n" "});\n"); } // start right column, if there are two columns if (visibleFacetList) printf("<div class='col-xs-6 col-sm-8 col-md-8 col-lg-9 col-xl-9'>\n"); @@ -700,31 +760,31 @@ printf("</div>\n"); //close parent container } void webSortableFieldedTable(struct cart *cart, struct fieldedTable *table, char *returnUrl, char *varPrefix, int maxLenField, struct hash *tagOutputWrappers, void *wrapperContext) /* Display all of table including a sortable label row. The tagOutputWrappers * is an optional way to enrich output of specific columns of the table. It is keyed * by column name and has for values functions of type webTableOutputWrapperType. */ { webFilteredFieldedTable(cart, table, NULL, returnUrl, varPrefix, maxLenField, tagOutputWrappers, wrapperContext, FALSE, NULL, slCount(table->rowList), - 0, NULL, NULL, NULL, NULL, NULL); + 0, NULL, NULL, NULL, NULL, NULL, FALSE); } void webTableBuildQuery(struct cart *cart, char *from, char *initialWhere, char *varPrefix, char *fields, boolean withFilters, struct dyString **retQuery, struct dyString **retWhere) /* Construct select, from and where clauses in query, keeping an additional copy of where * Returns the SQL query and the SQL where expression as two dyStrings (need to be freed) */ { struct dyString *query = dyStringNew(0); struct dyString *where = dyStringNew(0); struct slName *field, *fieldList = commaSepToSlNames(fields); boolean gotWhere = FALSE; sqlDyStringPrintf(query, "select %-s from %-s", sqlCkIl(fields), sqlCkIl(from)); if (!isEmpty(initialWhere)) @@ -911,23 +971,23 @@ { int lastPage = (context.tableSize-1)/pageSize; if (page > lastPage) page = lastPage; context.tableOffset = page * pageSize; } if (!visibleFacetList) { sqlDyStringPrintf(query, " limit %d offset %d", pageSize, context.tableOffset); table = fieldedTableFromDbQuery(conn, query->string); } webFilteredFieldedTable(cart, table, fields, returnUrl, varPrefix, maxFieldWidth, tagOutWrappers, wrapperContext, withFilters, pluralInstructions, - pageSize, facetUsualSize, &context, suggestHash, ffArray, visibleFacetList, addFunc); + pageSize, facetUsualSize, &context, suggestHash, ffArray, visibleFacetList, addFunc, FALSE); fieldedTableFree(&table); dyStringFree(&fusedFields); dyStringFree(&query); dyStringFree(&where); }