b038f3dafce99493b0ca00305538c6a3dd6f041a jcasper Tue Sep 26 15:58:44 2023 -0700 Initial commit of track decorators, refs #30237 diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c index 638d84e..0050502 100644 --- src/hg/hgTracks/hgTracks.c +++ src/hg/hgTracks/hgTracks.c @@ -67,30 +67,33 @@ #include "customFactory.h" #include "dupTrack.h" #include "genbank.h" #include "bigWarn.h" #include "wigCommon.h" #include "knetUdc.h" #include "hex.h" #include <openssl/sha.h> #include "customComposite.h" #include "chromAlias.h" #include "jsonWrite.h" #include "cds.h" #include "cacheTwoBit.h" #include "cartJson.h" #include "wikiLink.h" +#include "decorator.h" +#include "decoratorUi.h" +#include "mouseOver.h" //#include "bed3Sources.h" /* Other than submit and Submit all these vars should start with hgt. * to avoid weeding things out of other program's namespaces. * Because the browser is a central program, most of its cart * variables are not hgt. qualified. It's a good idea if other * program's unique variables be qualified with a prefix though. */ char *excludeVars[] = { "submit", "Submit", "dirty", "hgt.reset", "hgt.in1", "hgt.in2", "hgt.in3", "hgt.inBase", "hgt.out1", "hgt.out2", "hgt.out3", "hgt.out4", "hgt.left1", "hgt.left2", "hgt.left3", "hgt.right1", "hgt.right2", "hgt.right3", "hgt.dinkLL", "hgt.dinkLR", "hgt.dinkRL", "hgt.dinkRR", "hgt.tui", "hgt.hideAll", "hgt.visAllFromCt", @@ -7663,30 +7666,105 @@ return; struct track *subtrack; for (subtrack = tg->subtracks; subtrack != NULL; subtrack = subtrack->next) { if (!isSubtrackVisible(subtrack)) continue; if (!hashLookup(nonEmptySubtracksHash, trackHubSkipHubName(subtrack->track))) { subtrack->loadItems = dontLoadItems; subtrack->limitedVis = tvHide; subtrack->limitedVisSet = TRUE; } } } + +int tdbHasDecorators(struct track *track) +{ +struct slName *decoratorSettings = trackDbSettingsWildMatch(track->tdb, "decorator.*"); +if (decoratorSettings) + { + slNameFreeList(&decoratorSettings); + return 1; + } +return 0; +} + + +void loadDecorators(struct track *track) +/* Load decorations from a source (file) and put them in a decorator, then add that + * decorator to the list of the track's decorators. Within the new decorator, each + * of the decorations should have been entered into a hash table that is indexed by the + * name of the linked feature. That way when we're drawing the linked feature, we'll + * be able to quickly locate the associated decorations. + * + * Note that this will need amendment when multiple decoration sources are possible. + */ +{ +char *decoratorUrl = trackDbSetting(track->tdb, "decorator.default.bigDataUrl"); +if (!decoratorUrl) + { + errAbort ("Decorator tags are present in track %s, but no decorator file specified (decorator.default.bigDataUrl is missing)", + track->track); + return; + } + +struct bbiFile *bbi = NULL; +struct errCatch *errCatch = errCatchNew(); +if (errCatchStart(errCatch)) + { + if (isValidBigDataUrl(decoratorUrl,TRUE)) + bbi = bigBedFileOpenAlias(decoratorUrl, chromAliasFindAliases); + } +errCatchEnd(errCatch); +if (errCatch->gotError) + { + errAbort ("Network error while attempting to retrieve decorations from %s (track %s) - %s", + decoratorUrl, track->track, dyStringContents(errCatch->message)); + return; + } +errCatchFree(&errCatch); + +struct asObject *decoratorFileAsObj = asParseText(bigBedAutoSqlText(bbi)); +if (!asColumnNamesMatchFirstN(decoratorFileAsObj, decorationAsObj(), DECORATION_NUM_COLS)) + { + errAbort ("Decoration file associated with track %s (%s) does not match the expected format - see decoration.as", + track->track, decoratorUrl); + return; + } + +struct trackDb *decoratorTdb = getTdbForDecorator(track->tdb); +struct bigBedFilter *filters = bigBedBuildFilters(cart, bbi, decoratorTdb); +struct lm *lm = lmInit(0); +struct bigBedInterval *result = bigBedIntervalQuery(bbi, chromName, winStart, winEnd, 0, lm); + +struct mouseOverScheme *mouseScheme = mouseOverSetupForBbi(decoratorTdb, bbi); + +// insert resulting entries into track->decorators, leaving room for having multiple sources applied +// to the same track in the future. +if (track->decoratorGroup == NULL) + track->decoratorGroup = newDecoratorGroup(); + +struct decorator* newDecorators = decoratorListFromBbi(decoratorTdb, chromName, result, filters, bbi->fieldCount, mouseScheme); +track->decoratorGroup->decorators = slCat(track->decoratorGroup->decorators, newDecorators); +for (struct decorator *d = track->decoratorGroup->decorators; d != NULL; d = d->next) + d->group = track->decoratorGroup; +lmCleanup(&lm); +bigBedFileClose(&bbi); +} + static void *remoteParallelLoad(void *threadParam) /* Each thread loads tracks in parallel until all work is done. */ { pthread_t *pthread = threadParam; struct paraFetchData *pfd = NULL; pthread_detach(*pthread); // this thread will never join back with it's progenitor // Canceled threads that might leave locks behind, // so the theads are detached and will be neither joined nor canceled. boolean allDone = FALSE; while(1) { pthread_mutex_lock( &pfdMutex ); if (!pfdList) { allDone = TRUE; @@ -7701,33 +7779,43 @@ return NULL; long thisTime = 0, lastTime = 0; if (measureTiming) lastTime = clock1000(); /* protect against errAbort */ struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { pfd->done = FALSE; checkMaxWindowToDraw(pfd->track); checkHideEmptySubtracks(pfd->track); pfd->track->loadItems(pfd->track); + if (tdbHasDecorators(pfd->track)) + { + loadDecorators(pfd->track); + decoratorMethods(pfd->track); + } pfd->done = TRUE; } errCatchEnd(errCatch); + if (errCatch->gotWarning) + { + // do something intelligent to permit reporting of warnings + // Can't pass it to warn yet - the fancy warnhandlers aren't ready + } if (errCatch->gotError) { pfd->track->networkErrMsg = cloneString(errCatch->message->string); pfd->done = TRUE; } errCatchFree(&errCatch); if (measureTiming) { thisTime = clock1000(); pfd->track->loadTime = thisTime - lastTime; } pthread_mutex_lock( &pfdMutex ); slRemoveEl(&pfdRunning, pfd); // this list will not be huge @@ -8358,30 +8446,31 @@ hPrintf(" "); } hButtonMaybePressed("hgt.toggleRevCmplDisp", "reverse", revCmplDisp ? "Show forward strand at this location - keyboard shortcut: r, then v" : "Show reverse strand at this location - keyboard shortcut: r, then v", NULL, revCmplDisp); hPrintf(" "); hButtonWithOnClick("hgt.setWidth", "resize", "Resize image width to browser window size - keyboard shortcut: r, then s", "hgTracksSetWidth()"); // put the track download interface behind hg.conf control if (cfgOptionBooleanDefault("showDownloadUi", FALSE)) jsInline("var showDownloadButton = true;\n"); } + void doTrackForm(char *psOutput, struct tempName *ideoTn) /* Make the tracks display form with the zoom/scroll buttons and the active * image. If the ideoTn parameter is not NULL, it is filled in if the * ideogram is created. */ { struct group *group; struct track *track; char *freezeName = NULL; boolean hideAll = cgiVarExists("hgt.hideAll"); boolean hideTracks = cgiOptionalString( "hideTracks") != NULL; boolean defaultTracks = cgiVarExists("hgt.reset"); boolean showedRuler = FALSE; boolean showTrackControls = cartUsualBoolean(cart, "trackControlsOnMain", TRUE); boolean multiRegionButtonTop = cfgOptionBooleanDefault(MULTI_REGION_CFG_BUTTON_TOP, TRUE); long thisTime = 0, lastTime = 0; @@ -8642,30 +8731,35 @@ { if (!track->parallelLoading) { if (measureTiming) lastTime = clock1000(); checkMaxWindowToDraw(track); checkHideEmptySubtracks(track); // TODO: Test with multi-window feature checkIfWiggling(cart, track); if (!loadHack) { track->loadItems(track); + if (tdbHasDecorators(track)) + { + loadDecorators(track); + decoratorMethods(track); + } } else { // TEMP HACK GALT REMOVE if (currentWindow == windows) // first window { track->loadItems(track); } else { track->items = track->prevWindow->items; // just point to the previous windows items (faster than loading) // apparently loadItems is setting some other fields that we want, but which ones? track->visibility = track->prevWindow->visibility; track->limitedVis = track->prevWindow->limitedVis; track->limitedVisSet = track->prevWindow->limitedVisSet; @@ -8680,31 +8774,30 @@ { thisTime = clock1000(); track->loadTime = thisTime - lastTime; } } } } if (ptMax > 0) { /* wait for remote parallel load to finish */ remoteParallelLoadWait(atoi(cfgOptionDefault("parallelFetch.timeout", "90"))); // wait up to default 90 seconds. if (measureTiming) measureTime("Waiting for parallel (%d threads for %d tracks) remote data fetch", ptMax, pfdListCount); } - } trackLoadingInProgress = FALSE; setGlobalsFromWindow(windows); // first window // restore globals trackList = windows->trackList; // restore track list // Some loadItems() calls will have already set limitedVis. // Look for lowest limitedVis across all windows // if found, set all windows to same lowest limitedVis for (track = trackList; track != NULL; track = track->next) { setSharedLimitedVisAcrossWindows(track); struct track *sub; for (sub=track->subtracks; sub; sub=sub->next) {