be4311c07e14feb728abc6425ee606ffaa611a58 markd Fri Jan 22 06:46:58 2021 -0800 merge with master diff --git src/hg/hgc/barChartClick.c src/hg/hgc/barChartClick.c index f83f479..f0b5e10 100644 --- src/hg/hgc/barChartClick.c +++ src/hg/hgc/barChartClick.c @@ -1,36 +1,40 @@ /* Details pages for barChart tracks */ /* Copyright (C) 2015 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include "common.h" #include "hash.h" #include "hdb.h" #include "hvGfx.h" #include "trashDir.h" #include "hCommon.h" #include "hui.h" #include "asParse.h" #include "hgc.h" #include "trackHub.h" +#include "memgfx.h" +#include "hgColors.h" +#include "fieldedTable.h" #include "barChartBed.h" #include "barChartCategory.h" #include "barChartData.h" #include "barChartSample.h" #include "barChartUi.h" +#include "hgConfig.h" #define EXTRA_FIELDS_SIZE 256 struct barChartItemData /* Measured value for a sample and the sample category at a locus. * Used for barChart track details (boxplot) */ { struct barChartItemData *next; /* Next in singly linked list. */ char *sample; /* Sample identifier */ char *category; /* Sample category (from barChartSample table or barChartSampleUrl file) */ double value; /* Measured value (e.g. expression level) */ }; static struct hash *getTrackCategories(struct trackDb *tdb) /* Get list of categories from trackDb. This may be a subset of those in matrix. @@ -359,30 +363,162 @@ // to help with QAing the change, we add the "oldFonts" CGI parameter so QA can compare // old and new fonts to make sure that things are still readible on mirrors and servers // without the new fonts installed. This only needed during the QA phase bool useOldFonts = cgiBoolean("oldFonts"); /* Exec R in quiet mode, without reading/saving environment or workspace */ dyStringPrintf(cmd, "Rscript --vanilla --slave hgcData/barChartBoxplot.R %s '%s' %s %s %s %s %d", item, units, colorFile, df, pngTn.forHtml, isEmpty(name2) ? "n/a" : name2, useOldFonts); int ret = system(cmd->string); if (ret == 0) printf("<img src = \"%s\" border=1><br>\n", pngTn.forHtml); else warn("Error creating boxplot from sample data with command: %s", cmd->string); } +static double estimateStringWidth(char *s) +/* Get estimate of string width based on a memory font that is about the + * same size as svg will be using. After much research I don't think we + * can get the size from the server, would have to be in Javascript to get + * more precise */ +{ +MgFont *font = mgHelvetica14Font(); +return mgFontStringWidth(font, s); +} + +static double longestLabelSize(struct barChartCategory *categList) +/* Get estimate of longest label in pixels */ +{ +int longest = 0; +struct barChartCategory *categ; +for (categ = categList; categ != NULL; categ = categ->next) + { + int size = estimateStringWidth(categ->label); + if (size > longest) + longest = size; + } +return longest * 1.09; +} + +void deunderbarColumn(struct fieldedTable *ft, char *field) +/* Ununderbar all of a column inside table because space/underbar gets + * so confusing */ +{ +int fieldIx = fieldedTableFindFieldIx(ft, field); +struct fieldedRow *row; +for (row = ft->rowList; row != NULL; row = row->next) + replaceChar(row->row[fieldIx], '_', ' '); +} + +static void printBarChart(struct barChartBed *chart, struct trackDb *tdb, double maxVal, char *metric) +/* Plot bar chart without quartiles or anything fancy just using SVG */ +{ +/* Load up input labels, color, and data */ +struct barChartCategory *categs = barChartUiGetCategories(database, tdb); +int categCount = slCount(categs); +if (categCount != chart->expCount) + { + warn("Problem in %s barchart track. There are %d categories in trackDb and %d in data", + tdb->track, categCount, chart->expCount); + return; + } + +char *statsFile = trackDbSetting(tdb, "barChartStatsUrl"); +struct hash *statsHash = NULL; +int countStatIx = 0; +double statsSize = 0.0; +if (statsFile != NULL) + { + char *required[] = {"cluster", "count", "total"}; + struct fieldedTable *ft = fieldedTableFromTabFile( + statsFile, statsFile, required, ArraySize(required)); + deunderbarColumn(ft, "cluster"); + statsHash = fieldedTableIndex(ft, "cluster"); + countStatIx = fieldedTableFindFieldIx(ft, "count"); + statsSize = 8*(fieldedTableMaxColChars(ft, countStatIx)+1); + } + +/* Some constants that control layout */ +double heightPer=18.0; +double totalWidth=1250.0; +double borderSize = 1.0; + +double headerHeight = heightPer + 2*borderSize; +double innerHeight=heightPer-borderSize; +double labelWidth = longestLabelSize(categs) + 9; // Add some because size is just estimate +if (labelWidth > totalWidth/2) labelWidth = totalWidth/2; // Don't let labels take up more than half +double patchWidth = heightPer; +double labelOffset = patchWidth + 2*borderSize; +double statsOffset = labelOffset + labelWidth; +double barOffset = statsOffset + statsSize; +double statsRightOffset = barOffset - 9; +double barNumLabelWidth = estimateStringWidth(" 1234.000"); +double barMaxWidth = totalWidth-barOffset -barNumLabelWidth ; +double totalHeight = headerHeight + heightPer * categCount + borderSize; + +printf("<svg width=\"%g\" height=\"%g\">\n", totalWidth, totalHeight); + +/* Draw header */ +printf("<rect width=\"%g\" height=\"%g\" style=\"fill:#%s\"/>\n", totalWidth, headerHeight, HG_COL_HEADER); +printf("<text x=\"%g\" y=\"%g\" font-size=\"%g\">%s</text>\n", + labelOffset, innerHeight-1, innerHeight-1, "Sample"); +if (statsSize > 0.0) + printf("<text x=\"%g\" y=\"%g\" font-size=\"%g\" text-anchor=\"end\">%s</text>\n", + statsRightOffset, innerHeight-1, innerHeight-1, "N"); +printf("<text x=\"%g\" y=\"%g\" font-size=\"%g\">%s %s</text>\n", + barOffset, innerHeight-1, innerHeight-1, metric, "Value"); + +/* Set up clipping path for the pesky labels, which may be too long */ +printf("<clipPath id=\"labelClip\"><rect x=\"%g\" y=\"0\" width=\"%g\" height=\"%g\"/></clipPath>\n", + labelOffset, barOffset-labelOffset, totalHeight); + +double yPos = headerHeight; +struct barChartCategory *categ; +int i; +for (i=0, categ=categs; i<categCount; ++i , categ=categ->next, yPos += heightPer) + { + double score = chart->expScores[i]; + double barWidth = 0; + if (maxVal > 0.0) + barWidth = barMaxWidth * score/maxVal; + char *deunder = cloneString(categ->label); + replaceChar(deunder, '_', ' '); + printf("<rect x=\"0\" y=\"%g\" width=\"15\" height=\"%g\" style=\"fill:#%06X\"/>\n", + yPos, innerHeight, categ->color); + printf("<rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" style=\"fill:#%06X\"/>\n", + barOffset, yPos, barWidth, innerHeight, categ->color); + if (i&1) // every other time + printf("<rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" style=\"fill:#%06X\"/>\n", + labelOffset, yPos, labelWidth+statsSize, innerHeight, 0xFFFFFF); + printf("<text x=\"%g\" y=\"%g\" font-size=\"%g\" clip-path=\"url(#labelClip)\"\">%s</text>\n", + labelOffset, yPos+innerHeight-1, innerHeight-1, deunder); + if (statsSize > 0.0) + { + struct fieldedRow *fr = hashFindVal(statsHash, deunder); + if (fr != NULL) + { + printf("<text x=\"%g\" y=\"%g\" font-size=\"%g\" text-anchor=\"end\">%s</text>\n", + statsRightOffset, yPos+innerHeight-1, innerHeight-1, fr->row[countStatIx]); + } + } + printf("<text x=\"%g\" y=\"%g\" font-size=\"%g\">%5.3f</text>\n", + barOffset+barWidth+2, yPos+innerHeight-1, innerHeight-1, score); + } +printf("</svg>"); +} + + struct asColumn *asFindColByIx(struct asObject *as, int ix) /* Find AS column by index */ { struct asColumn *asCol; int i; for (i=0, asCol = as->columnList; asCol != NULL && i<ix; asCol = asCol->next, i++); return asCol; } void doBarChartDetails(struct trackDb *tdb, char *item) /* Details of barChart item */ { int start = cartInt(cart, "o"); int end = cartInt(cart, "t"); struct asObject *as = NULL; @@ -399,61 +535,67 @@ struct asColumn *nameCol = NULL, *name2Col = NULL; //struct asColumn *name2Col; char *nameLabel = NULL, *name2Label = NULL; if (as != NULL) { numColumns = slCount(as->columnList); nameCol = asFindColByIx(as, BARCHART_NAME_COLUMN_IX); name2Col = asFindColByIx(as, BARCHART_NAME2_COLUMN_IX); } nameLabel = trackDbSettingClosestToHomeOrDefault(tdb, "bedNameLabel", nameCol ? nameCol->comment : "Item"); if (trackDbSettingClosestToHomeOrDefault(tdb, "url", NULL) != NULL) printCustomUrl(tdb, item, TRUE); else printf("<b>%s: </b>%s<br>\n", nameLabel, chartItem->name); name2Label = name2Col ? name2Col->comment : "Alternative name"; -if (differentString(chartItem->name2, "")) { +if (differentString(chartItem->name2, "")) + { if (trackDbSettingClosestToHomeOrDefault(tdb, "url2", NULL) != NULL) printOtherCustomUrl(tdb, chartItem->name2, "url2", TRUE); else printf("<b>%s: </b> %s<br>\n", name2Label, chartItem->name2); } int categId; float highLevel = barChartMaxValue(chartItem, &categId); char *units = trackDbSettingClosestToHomeOrDefault(tdb, BAR_CHART_UNIT, "units"); char *metric = trackDbSettingClosestToHomeOrDefault(tdb, BAR_CHART_METRIC, ""); printf("<b>Total all %s values: </b> %0.2f %s<br>\n", metric, barChartTotalValue(chartItem), units); printf("<b>Maximum %s value: </b> %0.2f %s in %s<br>\n", metric, highLevel, units, barChartUiGetCategoryLabelById(categId, database, tdb)); printf("<b>Score: </b> %d<br>\n", chartItem->score); printf("<b>Genomic position: " "</b>%s <a href='%s&db=%s&position=%s%%3A%d-%d'>%s:%d-%d</a><br>\n", database, hgTracksPathAndSettings(), database, chartItem->chrom, chartItem->chromStart+1, chartItem->chromEnd, chartItem->chrom, chartItem->chromStart+1, chartItem->chromEnd); printf("<b>Strand: </b> %s\n", chartItem->strand); // print any remaining extra fields if (numColumns > 0) { extraFieldsPrint(tdb, NULL, extraFields, extraFieldCount); } char *matrixUrl = NULL, *sampleUrl = NULL; struct barChartItemData *vals = getSampleVals(tdb, chartItem, &matrixUrl, &sampleUrl); +puts("<p>"); if (vals != NULL) { // Print boxplot - puts("<p>"); char *df = makeDataFrame(tdb->table, vals); char *colorFile = makeColorFile(tdb); printBoxplot(df, item, chartItem->name2, units, colorFile); printf("<br><a href='%s'>View all data points for %s%s%s%s</a>\n", df, chartItem->name, chartItem->name2 ? " (" : "", chartItem->name2 ? chartItem->name2 : "", chartItem->name2 ? ")" : ""); } +else + { + if (cfgOptionBooleanDefault("svgBarChart", FALSE)) + printBarChart(chartItem, tdb, highLevel, metric); + } puts("<br>"); }