fecc0bfffd30dccc9998aa0c2e5a6064d487bbb1 chmalee Fri Apr 10 15:39:54 2020 -0700 Initial work on merging multiple bigBed items that span the window, refs #25133 diff --git src/hg/hgTracks/bigBedTrack.c src/hg/hgTracks/bigBedTrack.c index 07606ff..8130474 100644 --- src/hg/hgTracks/bigBedTrack.c +++ src/hg/hgTracks/bigBedTrack.c @@ -379,30 +379,45 @@ freeMem(rest); return field; } void bigBedAddLinkedFeaturesFromExt(struct track *track, char *chrom, int start, int end, int scoreMin, int scoreMax, boolean useItemRgb, int fieldCount, struct linkedFeatures **pLfList, int maxItems) /* Read in items in chrom:start-end from bigBed file named in track->bbiFileName, convert * them to linkedFeatures, and add to head of list. */ { struct lm *lm = lmInit(0); struct trackDb *tdb = track->tdb; struct bigBedInterval *bb, *bbList = bigBedSelectRangeExt(track, chrom, start, end, lm, maxItems); char *mouseOverField = cartOrTdbString(cart, track->tdb, "mouseOverField", NULL); + +// check if this track can merge large items, this setting must be allowed in the trackDb +// stanza for the track, but can be enabled/disabled via trackUi/right click menu so +// we also need to check the cart for the current status +int mergeCount = 0; +boolean doWindowSizeFilter = trackDbSettingOn(track->tdb, MERGESPAN_TDB_SETTING); +if (doWindowSizeFilter) + { + char hasMergedItemsSetting[256]; + safef(hasMergedItemsSetting, sizeof(hasMergedItemsSetting), "%s.%s", track->track, MERGESPAN_CART_SETTING); + if (cartVarExists(cart, hasMergedItemsSetting)) + doWindowSizeFilter = cartInt(cart, hasMergedItemsSetting) == 1; + else // save the cart var so javascript can offer the right toggle + cartSetInt(cart, hasMergedItemsSetting, 1); + } /* protect against temporary network error */ struct bbiFile *bbi = NULL; struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { bbi = fetchBbiForTrack(track); } errCatchEnd(errCatch); if (errCatch->gotError) { track->networkErrMsg = cloneString(errCatch->message->string); track->drawItems = bigDrawWarning; track->totalHeight = bigWarnTotalHeight; return; } @@ -410,94 +425,157 @@ int seqTypeField = 0; if (sameString(track->tdb->type, "bigPsl")) { seqTypeField = bbExtraFieldIndex(bbi, "seqType"); } int mouseOverIdx = bbExtraFieldIndex(bbi, mouseOverField); track->bbiFile = NULL; struct bigBedFilter *filters = bigBedBuildFilters(cart, bbi, track->tdb) ; if (compositeChildHideEmptySubtracks(cart, track->tdb, NULL, NULL)) labelTrackAsHideEmpty(track); +// a fake item that is the union of the items that span the current window +struct linkedFeatures *spannedLf = NULL; unsigned filtered = 0; for (bb = bbList; bb != NULL; bb = bb->next) { struct linkedFeatures *lf = NULL; if (sameString(track->tdb->type, "bigPsl")) { char *seq, *cds; struct psl *psl = pslFromBigPsl(chromName, bb, seqTypeField, &seq, &cds); int sizeMul = pslIsProtein(psl) ? 3 : 1; boolean isXeno = 0; // just affects grayIx boolean nameGetsPos = FALSE; // we want the name to stay the name lf = lfFromPslx(psl, sizeMul, isXeno, nameGetsPos, track); lf->original = psl; if ((seq != NULL) && (lf->orientation == -1)) reverseComplement(seq, strlen(seq)); lf->extra = seq; lf->cds = cds; } else if (sameString(tdb->type, "bigDbSnp")) { // bigDbSnp does not have a score field, but I want to compute the freqSourceIx from // trackDb and settings one time instead of for each item, so I'm overloading scoreMin. int freqSourceIx = scoreMin; lf = lfFromBigDbSnp(tdb, bb, filters, freqSourceIx); } else { char startBuf[16], endBuf[16]; char *bedRow[bbi->fieldCount]; - bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow)); if (bigBedFilterInterval(bedRow, filters)) { struct bed *bed = bedLoadN(bedRow, fieldCount); lf = bedMungToLinkedFeatures(&bed, tdb, fieldCount, scoreMin, scoreMax, useItemRgb); } + if (track->visibility != tvDense && lf && doWindowSizeFilter && bb->start < winStart && bb->end > winEnd) + { + mergeCount++; + struct bed *bed = bedLoadN(bedRow, fieldCount); + struct linkedFeatures *tmp = bedMungToLinkedFeatures(&bed, tdb, fieldCount, + scoreMin, scoreMax, useItemRgb); + if (spannedLf) + { + if (tmp->start < spannedLf->start) + spannedLf->start = tmp->start; + if (tmp->end > spannedLf->end) + spannedLf->end = tmp->end; + if (fieldCount > 9) // average the colors in the merged item + { + struct rgbColor itemColor = colorIxToRgb(lf->filterColor); + struct rgbColor currColor = colorIxToRgb(spannedLf->filterColor); + int r = currColor.r + round((itemColor.r - currColor.r) / mergeCount); + int g = currColor.g + round((itemColor.g - currColor.g) / mergeCount); + int b = currColor.b + round((itemColor.b - currColor.b) / mergeCount); + spannedLf->filterColor = MAKECOLOR_32(r,g,b); + } + } + else + { + // setting the label here protects against the case when only one item would + // have been merged. When this happens we warn in the track longLabel that + // nothing happened and essentially make the spanned item be what the actual + // item would be. If multiple items are merged then the labels and mouseOvers + // will get fixed up later + tmp->label = bigBedMakeLabel(track->tdb, track->labelColumns, bb, chromName); + tmp->mouseOver = restField(bb, mouseOverIdx); + slAddHead(&spannedLf, tmp); + } + continue; // lf will be NULL, but these items aren't "filtered", they're merged + } } if (lf == NULL) { filtered++; continue; } if (lf->label == NULL) lf->label = bigBedMakeLabel(track->tdb, track->labelColumns, bb, chromName); if (sameString(track->tdb->type, "bigGenePred") || startsWith("genePred", track->tdb->type)) { lf->original = genePredFromBigGenePred(chromName, bb); } if (lf->mouseOver == NULL) { char* mouseOver = restField(bb, mouseOverIdx); lf->mouseOver = mouseOver; // leaks some memory, cloneString handles NULL ifself } slAddHead(pLfList, lf); } if (filtered) labelTrackAsFilteredNumber(track, filtered); +if (doWindowSizeFilter) + // add the number of merged items to the track longLabel + { + char labelBuf[256]; + if (mergeCount > 1) + safef(labelBuf, sizeof(labelBuf), " (Merged %d items)", mergeCount); + else + safef(labelBuf, sizeof(labelBuf), " (No Items Merged in window)"); + track->longLabel = catTwoStrings(track->longLabel, labelBuf); + } + +if (spannedLf) + { + // if two or more items were merged together, fix up the label of the special merged item, + // otherwise the label and mouseOver will be the normal bed one + char itemLabelBuf[256], mouseOver[256]; + if (mergeCount > 1) + { + safef(itemLabelBuf, sizeof(itemLabelBuf), "Merged %d items", mergeCount); + safef(mouseOver, sizeof(mouseOver), "Merged %d items. Right-click and select 'show merged items' to expand", mergeCount); + spannedLf->label = cloneString(itemLabelBuf); + spannedLf->name = "mergedItem"; // always, for correct hgc clicks + spannedLf->mouseOver = cloneString(mouseOver); + } + slAddHead(pLfList, spannedLf); + } + lmCleanup(&lm); if (!trackDbSettingClosestToHomeOn(track->tdb, "linkIdInName")) track->itemName = bigLfItemName; bbiFileClose(&bbi); } boolean canDrawBigBedDense(struct track *tg) /* Return TRUE if conditions are such that can do the fast bigBed dense data fetch and * draw. */ { return tg->isBigBed; }