diff --git a/packages/core/data_adapters/BaseAdapter/BaseFeatureDataAdapter.ts b/packages/core/data_adapters/BaseAdapter/BaseFeatureDataAdapter.ts index 19b452cec5..82bb6aa0da 100644 --- a/packages/core/data_adapters/BaseAdapter/BaseFeatureDataAdapter.ts +++ b/packages/core/data_adapters/BaseAdapter/BaseFeatureDataAdapter.ts @@ -253,4 +253,19 @@ export abstract class BaseFeatureDataAdapter extends BaseAdapter { } return this.getRegionFeatureDensityStats(regions[0]!, opts) } + + async getSources( + regions: Region[], + ): Promise<{ name: string; color?: string; [key: string]: unknown }[]> { + const features = await firstValueFrom( + this.getFeaturesInMultipleRegions(regions).pipe(toArray()), + ) + const sources = new Set() + for (const f of features) { + sources.add(f.get('source')) + } + return [...sources].map(source => ({ + name: source, + })) + } } diff --git a/plugins/bed/src/BedAdapter/BedAdapter.ts b/plugins/bed/src/BedAdapter/BedAdapter.ts index 5925ed7d88..8fb2dba8d9 100644 --- a/plugins/bed/src/BedAdapter/BedAdapter.ts +++ b/plugins/bed/src/BedAdapter/BedAdapter.ts @@ -5,7 +5,12 @@ import { } from '@jbrowse/core/data_adapters/BaseAdapter' import { openLocation } from '@jbrowse/core/util/io' import { ObservableCreate } from '@jbrowse/core/util/rxjs' -import { Region, Feature, fetchAndMaybeUnzip } from '@jbrowse/core/util' +import { + Region, + Feature, + fetchAndMaybeUnzip, + SimpleFeature, +} from '@jbrowse/core/util' import IntervalTree from '@flatten-js/interval-tree' // locals @@ -123,17 +128,19 @@ export default class BedAdapter extends BaseFeatureDataAdapter { const names = await this.getNames() const intervalTree = new IntervalTree() - const ret = lines.map((f, i) => { + const ret = lines.map((line, i) => { const uniqueId = `${this.id}-${refName}-${i}` - return featureData( - f, - colRef, - colStart, - colEnd, - scoreColumn, - parser, - uniqueId, - names, + return new SimpleFeature( + featureData({ + line, + colRef, + colStart, + colEnd, + scoreColumn, + parser, + uniqueId, + names, + }), ) }) diff --git a/plugins/bed/src/BedAdapter/__snapshots__/BedAdapter.test.ts.snap b/plugins/bed/src/BedAdapter/__snapshots__/BedAdapter.test.ts.snap index 77084237f0..93e4f0241d 100644 --- a/plugins/bed/src/BedAdapter/__snapshots__/BedAdapter.test.ts.snap +++ b/plugins/bed/src/BedAdapter/__snapshots__/BedAdapter.test.ts.snap @@ -129,8 +129,15 @@ exports[`adapter can fetch features bed with autosql 1`] = ` "Variant_Type": "SNP", "alcohol_history": "--", "alcohol_intensity": "--", + "blockCount": 1, + "blockSizes": [ + 1, + ], "bmi": "--", "case_id": "09454ed6-64bc-4a35-af44-7c4344623d45", + "chromStarts": [ + 0, + ], "cigarettes_per_day": "--", "days_to_death": "--", "dbSNP_RS": "novel", @@ -160,6 +167,8 @@ exports[`adapter can fetch features bed with autosql 1`] = ` "uniqueId": "test-ctgA-0-0", }, ], + "thickEnd": 1815757, + "thickStart": 1815756, "type": undefined, "uniqueId": "test-ctgA-0", "weight": "--", diff --git a/plugins/bed/src/BedTabixAdapter/BedTabixAdapter.ts b/plugins/bed/src/BedTabixAdapter/BedTabixAdapter.ts index c6a4edd182..86b119bafc 100644 --- a/plugins/bed/src/BedTabixAdapter/BedTabixAdapter.ts +++ b/plugins/bed/src/BedTabixAdapter/BedTabixAdapter.ts @@ -5,7 +5,12 @@ import { } from '@jbrowse/core/data_adapters/BaseAdapter' import { openLocation } from '@jbrowse/core/util/io' import { ObservableCreate } from '@jbrowse/core/util/rxjs' -import { FileLocation, Region, Feature } from '@jbrowse/core/util' +import { + FileLocation, + Region, + Feature, + SimpleFeature, +} from '@jbrowse/core/util' import { TabixIndexedFile } from '@gmod/tabix' import PluginManager from '@jbrowse/core/PluginManager' import { AnyConfigurationModel } from '@jbrowse/core/configuration' @@ -78,22 +83,21 @@ export default class BedTabixAdapter extends BaseFeatureDataAdapter { const colRef = columnNumbers.ref - 1 const colStart = columnNumbers.start - 1 const colEnd = columnNumbers.end - 1 - // colSame handles special case for tabix where a single column is both - // the start and end, this is assumed to be covering the base at this - // position (e.g. tabix -s 1 -b 2 -e 2) begin and end are same const names = await this.getNames() await this.bed.getLines(query.refName, query.start, query.end, { lineCallback: (line, fileOffset) => { observer.next( - featureData( - line, - colRef, - colStart, - colEnd, - this.scoreColumn, - this.parser, - `${this.id}-${fileOffset}`, - names, + new SimpleFeature( + featureData({ + line, + colRef, + colStart, + colEnd, + scoreColumn: this.scoreColumn, + parser: this.parser, + uniqueId: `${this.id}-${fileOffset}`, + names, + }), ), ) }, diff --git a/plugins/bed/src/BedTabixAdapter/__snapshots__/BedTabixAdapter.test.ts.snap b/plugins/bed/src/BedTabixAdapter/__snapshots__/BedTabixAdapter.test.ts.snap index f9f5cdeed9..db1c9c3943 100644 --- a/plugins/bed/src/BedTabixAdapter/__snapshots__/BedTabixAdapter.test.ts.snap +++ b/plugins/bed/src/BedTabixAdapter/__snapshots__/BedTabixAdapter.test.ts.snap @@ -129,8 +129,15 @@ exports[`adapter can fetch features bed with autosql 1`] = ` "Variant_Type": "SNP", "alcohol_history": "--", "alcohol_intensity": "--", + "blockCount": 1, + "blockSizes": [ + 1, + ], "bmi": "--", "case_id": "09454ed6-64bc-4a35-af44-7c4344623d45", + "chromStarts": [ + 0, + ], "cigarettes_per_day": "--", "days_to_death": "--", "dbSNP_RS": "novel", @@ -160,6 +167,8 @@ exports[`adapter can fetch features bed with autosql 1`] = ` "uniqueId": "test-52986-0", }, ], + "thickEnd": 1815757, + "thickStart": 1815756, "type": undefined, "uniqueId": "test-52986", "weight": "--", diff --git a/plugins/bed/src/BigBedAdapter/BigBedAdapter.ts b/plugins/bed/src/BigBedAdapter/BigBedAdapter.ts index a10c55f1c9..0490c2fffd 100644 --- a/plugins/bed/src/BigBedAdapter/BigBedAdapter.ts +++ b/plugins/bed/src/BigBedAdapter/BigBedAdapter.ts @@ -13,18 +13,12 @@ import { min, Feature, SimpleFeature, - SimpleFeatureSerializedNoId, + SimpleFeatureSerialized, } from '@jbrowse/core/util' import { firstValueFrom, Observer, toArray } from 'rxjs' // locals -import { - isUcscProcessedTranscript, - ucscProcessedTranscript, - makeRepeatTrackDescription, - makeBlocks, - arrayify, -} from '../util' +import { featureData2 } from '../util' export default class BigBedAdapter extends BaseFeatureDataAdapter { private cachedP?: Promise<{ @@ -148,19 +142,14 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter { } } - const parentAggregation = {} as Record< - string, - SimpleFeatureSerializedNoId[] - > + const parentAggregation = {} as Record if (feats.some(f => f.uniqueId === undefined)) { throw new Error('found uniqueId undefined') } for (const feat of feats) { - const data = parser.parseLine( - `${query.refName}\t${feat.start}\t${feat.end}\t${feat.rest}`, - { uniqueId: feat.uniqueId! }, - ) + const line = `${query.refName}\t${feat.start}\t${feat.end}\t${feat.rest}` + const data = parser.parseLine(line, { uniqueId: feat.uniqueId! }) const aggr = data[aggregateField] if (!parentAggregation[aggr]) { @@ -183,89 +172,27 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter { strand, ...rest } = data - const chromStarts = arrayify(chromStarts2) - const blockStarts = arrayify(blockStarts2) - const blockSizes = arrayify(blockSizes2) - const score = scoreColumn ? +data[scoreColumn] : +score2 - const subfeatures = makeBlocks({ - chromStarts, - blockStarts, - blockSizes, - blockCount, + const f = featureData2({ + ...rest, + scoreColumn, + line, + parser, uniqueId, - refName: query.refName, start: feat.start, + end: feat.end, + refName: query.refName, }) - - if ( - isUcscProcessedTranscript({ - strand, - blockCount, - thickStart, - description, - }) - ) { - const f = ucscProcessedTranscript({ - ...rest, - strand, - uniqueId, - type, - start: feat.start, - end: feat.end, - refName: query.refName, - score, - description, - chromStarts: chromStarts!, - blockSizes: blockSizes!, - blockCount, - thickStart, - thickEnd, - subfeatures, - }) - if (aggr) { - parentAggregation[aggr].push(f) - } else { - if ( - doesIntersect2( - f.start, - f.end, - originalQuery.start, - originalQuery.end, - ) - ) { - observer.next( - new SimpleFeature({ - id: `${this.id}-${uniqueId}`, - data: f, - }), - ) - } - } + if (aggr) { + parentAggregation[aggr].push(f) } else { if ( - doesIntersect2( - feat.start, - feat.end, - originalQuery.start, - originalQuery.end, - ) + doesIntersect2(f.start, f.end, originalQuery.start, originalQuery.end) ) { observer.next( new SimpleFeature({ id: `${this.id}-${uniqueId}`, - data: { - ...rest, - ...makeRepeatTrackDescription(description), - start: feat.start, - end: feat.end, - strand, - uniqueId, - type, - score, - refName: query.refName, - subfeatures, - }, + data: f, }), ) } diff --git a/plugins/bed/src/generateBedMethylFeature.ts b/plugins/bed/src/generateBedMethylFeature.ts new file mode 100644 index 0000000000..960e0abad3 --- /dev/null +++ b/plugins/bed/src/generateBedMethylFeature.ts @@ -0,0 +1,67 @@ +export function isBedMethylFeature({ + splitLine, + start, + end, +}: { + splitLine: string[] + start: number + end: number +}) { + return +(splitLine[6] || 0) === start && +(splitLine[7] || 0) === end +} +export function generateBedMethylFeature({ + line, + uniqueId, + refName, + start, + end, +}: { + line: string + uniqueId: string + refName: string + start: number + end: number +}) { + // see + // https://github.com/nanoporetech/modkit?tab=readme-ov-file#description-of-bedmethyl-output + const [ + , + , + , + code, + , + strand, + , + , + color, + n_valid_cov, + fraction_modified, + n_mod, + n_canonical, + n_other_mod, + n_delete, + n_fail, + n_diff, + n_nocall, + ] = line.split('\t') + return { + uniqueId, + refName, + start, + end, + code, + score: fraction_modified, + strand, + color, + source: code, + n_valid_cov, + fraction_modified, + n_mod, + n_canonical, + n_other_mod, + n_delete, + n_fail, + n_diff, + n_nocall, + } +} diff --git a/plugins/bed/src/generateRepeatMaskerFeature.ts b/plugins/bed/src/generateRepeatMaskerFeature.ts new file mode 100644 index 0000000000..a543e3b419 --- /dev/null +++ b/plugins/bed/src/generateRepeatMaskerFeature.ts @@ -0,0 +1,71 @@ +export function isRepeatMaskerDescriptionField(desc?: string): desc is string { + const ret = desc?.trim().split(' ') + return [0, 1, 2, 3, 5, 6].every(s => + ret?.[s] !== undefined ? !Number.isNaN(+ret[s]) : false, + ) +} + +function makeRepeatTrackDescription(description?: string) { + if (isRepeatMaskerDescriptionField(description)) { + const [ + bitsw_score, + percent_div, + percent_del, + percent_ins, + query_chr, + query_begin, + query_end, + query_remaining, + orientation, + matching_repeat_name, + matching_repeat_class, + matching_repeat_begin, + matching_repeat_end, + matching_repeat_remaining, + repeat_id, + ] = description.trim().split(' ') + return { + bitsw_score, + percent_div, + percent_del, + percent_ins, + query_chr, + query_begin, + query_end, + query_remaining, + orientation, + matching_repeat_name, + matching_repeat_class, + matching_repeat_begin, + matching_repeat_end, + matching_repeat_remaining, + repeat_id, + } + } + return { description } +} + +export function generateRepeatMaskerFeature({ + uniqueId, + refName, + start, + end, + description, + ...rest +}: { + uniqueId: string + refName: string + start: number + end: number + description: string + [key: string]: unknown +}) { + return { + ...rest, + ...makeRepeatTrackDescription(description), + uniqueId, + refName, + start, + end, + } +} diff --git a/plugins/bed/src/generateUcscTranscript.ts b/plugins/bed/src/generateUcscTranscript.ts new file mode 100644 index 0000000000..3739859bcb --- /dev/null +++ b/plugins/bed/src/generateUcscTranscript.ts @@ -0,0 +1,133 @@ +import { MinimalFeature, TranscriptFeat } from './types' + +export function isUcscTranscript({ + thickStart, + blockCount, + strand, +}: { + thickStart?: number + blockCount?: number + strand?: number +}) { + return thickStart && blockCount && strand !== 0 +} + +export function generateUcscTranscript(data: TranscriptFeat) { + const { + strand = 0, + chrom: _1, + chromStart: _2, + chromEnd: _3, + chromStarts, + blockStarts, + blockSizes, + uniqueId, + ...rest + } = data + const { + subfeatures: oldSubfeatures, + thickStart, + thickEnd, + blockCount, + refName, + ...rest2 + } = rest + + const subfeatures: MinimalFeature[] = [] + const feats = oldSubfeatures + .filter(child => child.type === 'block') + .sort((a, b) => a.start - b.start) + + for (const block of feats) { + const start = block.start + const end = block.end + if (thickStart >= end) { + // left-side UTR + subfeatures.push({ + type: `${strand > 0 ? 'five' : 'three'}_prime_UTR`, + start, + end, + refName, + }) + } else if (thickStart > start && thickStart < end && thickEnd >= end) { + // UTR | CDS + subfeatures.push( + { + type: `${strand > 0 ? 'five' : 'three'}_prime_UTR`, + start, + end: thickStart, + refName, + }, + { + type: 'CDS', + start: thickStart, + end, + refName, + }, + ) + } else if (thickStart <= start && thickEnd >= end) { + // CDS + subfeatures.push({ + type: 'CDS', + start, + end, + refName, + }) + } else if (thickStart > start && thickStart < end && thickEnd < end) { + // UTR | CDS | UTR + subfeatures.push( + { + type: `${strand > 0 ? 'five' : 'three'}_prime_UTR`, + start, + end: thickStart, + refName, + }, + { + type: 'CDS', + start: thickStart, + end: thickEnd, + refName, + }, + { + type: `${strand > 0 ? 'three' : 'five'}_prime_UTR`, + start: thickEnd, + end, + refName, + }, + ) + } else if (thickStart <= start && thickEnd > start && thickEnd < end) { + // CDS | UTR + subfeatures.push( + { + type: 'CDS', + start, + end: thickEnd, + refName, + }, + { + type: `${strand > 0 ? 'three' : 'five'}_prime_UTR`, + start: thickEnd, + end, + refName, + }, + ) + } else if (thickEnd <= start) { + // right-side UTR + subfeatures.push({ + type: `${strand > 0 ? 'three' : 'five'}_prime_UTR`, + start, + end, + refName, + }) + } + } + + return { + ...rest2, + uniqueId, + strand, + type: 'mRNA', + refName, + subfeatures, + } +} diff --git a/plugins/bed/src/types.ts b/plugins/bed/src/types.ts new file mode 100644 index 0000000000..ac50cce8e2 --- /dev/null +++ b/plugins/bed/src/types.ts @@ -0,0 +1,19 @@ +export interface MinimalFeature { + type: string + start: number + end: number + refName: string + [key: string]: unknown +} + +export interface TranscriptFeat extends MinimalFeature { + uniqueId: string + thickStart: number + thickEnd: number + blockCount: number + blockSizes: number[] + chromStarts: number[] + refName: string + strand?: number + subfeatures: MinimalFeature[] +} diff --git a/plugins/bed/src/util.ts b/plugins/bed/src/util.ts index c9ae29ed7e..b078a8dbab 100644 --- a/plugins/bed/src/util.ts +++ b/plugins/bed/src/util.ts @@ -1,136 +1,26 @@ import BED from '@gmod/bed' -import { SimpleFeature } from '@jbrowse/core/util' +import { SimpleFeatureSerialized } from '@jbrowse/core/util' +import { + generateBedMethylFeature, + isBedMethylFeature, +} from './generateBedMethylFeature' +import { + generateUcscTranscript, + isUcscTranscript, +} from './generateUcscTranscript' +import { + generateRepeatMaskerFeature, + isRepeatMaskerDescriptionField, +} from './generateRepeatMaskerFeature' -export interface MinimalFeature { - type: string - start: number - end: number - refName: string - [key: string]: unknown -} -export interface TranscriptFeat extends MinimalFeature { - thickStart: number - thickEnd: number - blockCount: number - blockSizes: number[] - chromStarts: number[] - refName: string - strand?: number - subfeatures: MinimalFeature[] -} - -export function ucscProcessedTranscript(feature: TranscriptFeat) { - const { - subfeatures: oldSubfeatures, - thickStart, - thickEnd, - blockCount, - blockSizes, - chromStarts, - refName, - strand = 0, - ...rest - } = feature - - if (!thickStart || !thickEnd || !strand) { - return feature +function stringToStrand(f: string) { + if (f === '-1') { + return -1 + } else if (f === '+') { + return 1 + } else { + return 0 } - - const subfeatures: MinimalFeature[] = [] - oldSubfeatures - .filter(child => child.type === 'block') - .sort((a, b) => a.start - b.start) - .forEach(block => { - const start = block.start - const end = block.end - if (thickStart >= end) { - // left-side UTR - const prime = strand > 0 ? 'five' : 'three' - subfeatures.push({ - type: `${prime}_prime_UTR`, - start, - end, - refName, - }) - } else if (thickStart > start && thickStart < end && thickEnd >= end) { - // UTR | CDS - const prime = strand > 0 ? 'five' : 'three' - subfeatures.push( - { - type: `${prime}_prime_UTR`, - start, - end: thickStart, - refName, - }, - { - type: 'CDS', - start: thickStart, - end, - refName, - }, - ) - } else if (thickStart <= start && thickEnd >= end) { - // CDS - subfeatures.push({ - type: 'CDS', - start, - end, - refName, - }) - } else if (thickStart > start && thickStart < end && thickEnd < end) { - // UTR | CDS | UTR - const leftPrime = strand > 0 ? 'five' : 'three' - const rightPrime = strand > 0 ? 'three' : 'five' - subfeatures.push( - { - type: `${leftPrime}_prime_UTR`, - start, - end: thickStart, - refName, - }, - { - type: 'CDS', - start: thickStart, - end: thickEnd, - refName, - }, - { - type: `${rightPrime}_prime_UTR`, - start: thickEnd, - end, - refName, - }, - ) - } else if (thickStart <= start && thickEnd > start && thickEnd < end) { - // CDS | UTR - const prime = strand > 0 ? 'three' : 'five' - subfeatures.push( - { - type: 'CDS', - start, - end: thickEnd, - refName, - }, - { - type: `${prime}_prime_UTR`, - start: thickEnd, - end, - refName, - }, - ) - } else if (thickEnd <= start) { - // right-side UTR - const prime = strand > 0 ? 'three' : 'five' - subfeatures.push({ - type: `${prime}_prime_UTR`, - start, - end, - refName, - }) - } - }) - - return { ...rest, strand, type: 'mRNA', refName, subfeatures } } function defaultParser(fields: string[], line: string) { @@ -196,30 +86,67 @@ export function makeBlocks({ } return [] } -export function featureData( - line: string, - colRef: number, - colStart: number, - colEnd: number, - scoreColumn: string, - parser: BED, - uniqueId: string, - names?: string[], -) { - const l = line.split('\t') + +export function featureData({ + line, + colRef, + colStart, + colEnd, + scoreColumn, + parser, + uniqueId, + names, +}: { + line: string + colRef: number + colStart: number + colEnd: number + scoreColumn: string + parser: BED + uniqueId: string + names?: string[] +}) { + const splitLine = line.split('\t') + const refName = splitLine[colRef]! + const start = +splitLine[colStart]! + const end = +splitLine[colEnd]! + (colStart === colEnd ? 1 : 0) + + return featureData2({ + line, + refName, + start, + end, + parser, + uniqueId, + scoreColumn, + names, + }) +} + +export function featureData2({ + line, + refName, + start, + end, + parser, + uniqueId, + scoreColumn, + names, +}: { + line: string + refName: string + start: number + end: number + parser: BED + uniqueId: string + scoreColumn: string + names?: string[] +}): SimpleFeatureSerialized { + const splitLine = line.split('\t') const data = names ? defaultParser(names, line) : parser.parseLine(line, { uniqueId }) - const { - blockCount, - blockSizes, - blockStarts, - chromStarts, - thickStart, - thickEnd, - type, - description, strand: strand2, score: score2, chrom: _1, @@ -228,77 +155,83 @@ export function featureData( ...rest } = data - const refName = l[colRef]! - const start = +l[colStart]! - const colSame = colStart === colEnd ? 1 : 0 - const end = +l[colEnd]! + colSame + const { + chromStarts, + blockSizes, + blockStarts, + type, + blockCount, + thickStart, + thickEnd, + description, + ...rest2 + } = rest const score = scoreColumn ? +data[scoreColumn] : score2 ? +score2 : undefined - const strand = - typeof strand2 === 'string' - ? strand2 === '-' - ? -1 - : strand2 === '+' - ? 1 - : 0 - : strand2 + const strand = typeof strand2 === 'string' ? stringToStrand(strand2) : strand2 - const f = { - ...rest, - ...makeRepeatTrackDescription(description), - type, - score, + const subfeatures = makeBlocks({ start, - end, - strand, - refName, uniqueId, - subfeatures: makeBlocks({ + refName, + chromStarts, + blockCount, + blockSizes, + blockStarts, + }) + + if (isBedMethylFeature({ splitLine, start, end })) { + return generateBedMethylFeature({ + line, + uniqueId, + refName, start, + end, + }) + } else if (isRepeatMaskerDescriptionField(description)) { + return generateRepeatMaskerFeature({ + ...rest2, uniqueId, + description, + type, + score, + start, + end, + strand, refName, + subfeatures, + }) + } else if (isUcscTranscript({ strand, blockCount, thickStart })) { + return generateUcscTranscript({ + ...rest, + description, chromStarts, - blockCount, + thickStart, + thickEnd, blockSizes, - blockStarts, - }), - } - return new SimpleFeature({ - id: uniqueId, - data: isUcscProcessedTranscript({ - strand, blockCount, - thickStart, - description, + type, + score, + start, + end, + strand, + refName, + uniqueId, + subfeatures, }) - ? ucscProcessedTranscript({ - thickStart: thickStart!, - thickEnd: thickEnd!, - blockCount: blockCount!, - blockSizes: blockSizes!, - chromStarts: chromStarts, - ...f, - }) - : f, - }) -} - -export function isUcscProcessedTranscript({ - thickStart, - blockCount, - strand, - description, -}: { - thickStart?: number - blockCount?: number - strand?: number - description?: string -}) { - return ( - thickStart && - blockCount && - strand !== 0 && - !isRepeatMaskerDescriptionField(description) - ) + } else { + return { + ...rest, + uniqueId, + description, + type, + score, + start, + end, + strand, + refName, + subfeatures, + } + } } export function arrayify(f?: string | number[]) { @@ -308,51 +241,3 @@ export function arrayify(f?: string | number[]) { : f : undefined } - -function isRepeatMaskerDescriptionField( - description?: string, -): description is string { - const ret = description?.trim().split(' ') - return [0, 1, 2, 3, 5, 6].every(s => - ret?.[s] !== undefined ? !Number.isNaN(+ret[s]) : false, - ) -} -export function makeRepeatTrackDescription(description?: string) { - if (isRepeatMaskerDescriptionField(description)) { - const [ - bitsw_score, - percent_div, - percent_del, - percent_ins, - query_chr, - query_begin, - query_end, - query_remaining, - orientation, - matching_repeat_name, - matching_repeat_class, - matching_repeat_begin, - matching_repeat_end, - matching_repeat_remaining, - repeat_id, - ] = description.trim().split(' ') - return { - bitsw_score, - percent_div, - percent_del, - percent_ins, - query_chr, - query_begin, - query_end, - query_remaining, - orientation, - matching_repeat_name, - matching_repeat_class, - matching_repeat_begin, - matching_repeat_end, - matching_repeat_remaining, - repeat_id, - } - } - return { description } -} diff --git a/plugins/wiggle/src/LinearWiggleDisplay/model.ts b/plugins/wiggle/src/LinearWiggleDisplay/model.ts index a6953ea512..459b89ede8 100644 --- a/plugins/wiggle/src/LinearWiggleDisplay/model.ts +++ b/plugins/wiggle/src/LinearWiggleDisplay/model.ts @@ -150,8 +150,10 @@ function stateModelFactory( * #getter */ get needsScalebar() { - const { rendererTypeName: type } = self - return type === 'XYPlotRenderer' || type === 'LinePlotRenderer' + return ( + self.rendererTypeName === 'XYPlotRenderer' || + self.rendererTypeName === 'LinePlotRenderer' + ) }, /** * #getter diff --git a/plugins/wiggle/src/MultiLinearWiggleDisplay/model.ts b/plugins/wiggle/src/MultiLinearWiggleDisplay/model.ts index 04dc9e972a..af724f5161 100644 --- a/plugins/wiggle/src/MultiLinearWiggleDisplay/model.ts +++ b/plugins/wiggle/src/MultiLinearWiggleDisplay/model.ts @@ -1,6 +1,5 @@ import { lazy } from 'react' -import { addDisposer, isAlive, types, Instance } from 'mobx-state-tree' -import { autorun } from 'mobx' +import { isAlive, types, Instance } from 'mobx-state-tree' import { axisPropsFromTickScale } from 'react-d3-axis-mod' import deepEqual from 'fast-deep-equal' @@ -15,7 +14,6 @@ import { AnyReactComponentType, getContainingView, } from '@jbrowse/core/util' -import { getRpcSessionId } from '@jbrowse/core/util/tracks' import { set1 as colors } from '@jbrowse/core/ui/colors' import PluginManager from '@jbrowse/core/PluginManager' import { @@ -24,7 +22,7 @@ import { } from '@jbrowse/plugin-linear-genome-view' // locals -import { getScale, YSCALEBAR_LABEL_OFFSET } from '../util' +import { getScale, Source, YSCALEBAR_LABEL_OFFSET } from '../util' import SharedWiggleMixin from '../shared/SharedWiggleMixin' const randomColor = () => @@ -43,12 +41,6 @@ const rendererTypes = new Map([ ['multirowline', 'MultiRowLineRenderer'], ]) -interface Source { - name: string - color?: string - group?: string -} - /** * #stateModel MultiLinearWiggleDisplay * extends @@ -468,29 +460,22 @@ export function stateModelFactory( afterAttach() { // eslint-disable-next-line @typescript-eslint/no-floating-promises ;(async () => { - const { getQuantitativeStatsAutorun } = await import( - '../getQuantitativeStatsAutorun' - ) - getQuantitativeStatsAutorun(self) - addDisposer( - self, - autorun(async () => { - const { rpcManager } = getSession(self) - const { adapterConfig } = self - const sessionId = getRpcSessionId(self) - const sources = (await rpcManager.call( - sessionId, - 'MultiWiggleGetSources', - { - sessionId, - adapterConfig, - }, - )) as Source[] - if (isAlive(self)) { - self.setSources(sources) - } - }), - ) + try { + const [ + { getMultiWiggleSourcesAutorun }, + { getQuantitativeStatsAutorun }, + ] = await Promise.all([ + import('../getMultiWiggleSourcesAutorun'), + import('../getQuantitativeStatsAutorun'), + ]) + getQuantitativeStatsAutorun(self) + getMultiWiggleSourcesAutorun(self) + } catch (e) { + if (isAlive(self)) { + console.error(e) + getSession(self).notifyError(`${e}`, e) + } + } })() }, diff --git a/plugins/wiggle/src/MultiRowXYPlotRenderer/configSchema.ts b/plugins/wiggle/src/MultiRowXYPlotRenderer/configSchema.ts index 89728f56e2..3772e3fc0b 100644 --- a/plugins/wiggle/src/MultiRowXYPlotRenderer/configSchema.ts +++ b/plugins/wiggle/src/MultiRowXYPlotRenderer/configSchema.ts @@ -41,7 +41,7 @@ const configSchema = ConfigurationSchema( */ minSize: { type: 'number', - defaultValue: 0, + defaultValue: 0.7, }, }, { diff --git a/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts b/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts index a33695d280..886a88232a 100644 --- a/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts +++ b/plugins/wiggle/src/MultiWiggleAdapter/MultiWiggleAdapter.ts @@ -108,12 +108,14 @@ export default class MultiWiggleAdapter extends BaseFeatureDataAdapter { // always render bigwig instead of calculating a feature density for it async getMultiRegionFeatureDensityStats(_regions: Region[]) { - return { featureDensity: 0 } + return { + featureDensity: 0, + } } // in another adapter type, this could be dynamic depending on region or // something, but it is static for this particular multi-wiggle adapter type - async getSources() { + async getSources(_regions: Region[]) { const adapters = await this.getAdapters() return adapters.map(({ dataAdapter, source, name, ...rest }) => ({ name: source, diff --git a/plugins/wiggle/src/MultiXYPlotRenderer/configSchema.ts b/plugins/wiggle/src/MultiXYPlotRenderer/configSchema.ts index b798be9450..ae44134321 100644 --- a/plugins/wiggle/src/MultiXYPlotRenderer/configSchema.ts +++ b/plugins/wiggle/src/MultiXYPlotRenderer/configSchema.ts @@ -40,7 +40,7 @@ const configSchema = ConfigurationSchema( */ minSize: { type: 'number', - defaultValue: 0, + defaultValue: 0.7, }, }, { diff --git a/plugins/wiggle/src/XYPlotRenderer/configSchema.ts b/plugins/wiggle/src/XYPlotRenderer/configSchema.ts index 161be6627f..f6c151ba36 100644 --- a/plugins/wiggle/src/XYPlotRenderer/configSchema.ts +++ b/plugins/wiggle/src/XYPlotRenderer/configSchema.ts @@ -40,7 +40,7 @@ const configSchema = ConfigurationSchema( */ minSize: { type: 'number', - defaultValue: 0, + defaultValue: 0.7, }, }, { diff --git a/plugins/wiggle/src/getMultiWiggleSourcesAutorun.ts b/plugins/wiggle/src/getMultiWiggleSourcesAutorun.ts new file mode 100644 index 0000000000..b07f1ac19b --- /dev/null +++ b/plugins/wiggle/src/getMultiWiggleSourcesAutorun.ts @@ -0,0 +1,61 @@ +import { autorun } from 'mobx' +import { addDisposer, isAlive } from 'mobx-state-tree' +// jbrowse +import { + getContainingView, + getSession, + isAbortException, +} from '@jbrowse/core/util' +import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view' +import { AnyConfigurationModel } from '@jbrowse/core/configuration' +import { getRpcSessionId } from '@jbrowse/core/util/tracks' + +export interface Source { + name: string + color?: string + group?: string +} + +export function getMultiWiggleSourcesAutorun(self: { + quantitativeStatsReady: boolean + configuration: AnyConfigurationModel + adapterConfig: AnyConfigurationModel + autoscaleType: string + adapterProps: () => Record + setStatsLoading: (aborter: AbortController) => void + setError: (error: unknown) => void + setMessage: (str: string) => void + setSources: (sources: Source[]) => void +}) { + addDisposer( + self, + autorun(async () => { + try { + const { rpcManager } = getSession(self) + const { adapterConfig } = self + const sessionId = getRpcSessionId(self) + const view = getContainingView(self) as LinearGenomeViewModel + if (!view.initialized) { + return + } + const sources = (await rpcManager.call( + sessionId, + 'MultiWiggleGetSources', + { + regions: view.staticBlocks.contentBlocks, + sessionId, + adapterConfig, + }, + )) as Source[] + if (isAlive(self)) { + self.setSources(sources) + } + } catch (e) { + if (!isAbortException(e) && isAlive(self)) { + console.error(e) + getSession(self).notifyError(`${e}`, e) + } + } + }), + ) +} diff --git a/products/jbrowse-web/src/tests/__image_snapshots__/multi-big-wig-test-tsx-open-a-multibigwig-track-1-snap.png b/products/jbrowse-web/src/tests/__image_snapshots__/multi-big-wig-test-tsx-open-a-multibigwig-track-1-snap.png index 2da47052c8..3fb8bf9ac1 100644 Binary files a/products/jbrowse-web/src/tests/__image_snapshots__/multi-big-wig-test-tsx-open-a-multibigwig-track-1-snap.png and b/products/jbrowse-web/src/tests/__image_snapshots__/multi-big-wig-test-tsx-open-a-multibigwig-track-1-snap.png differ diff --git a/test_data/config_demo.json b/test_data/config_demo.json index 43831890d0..840f7f836b 100644 --- a/test_data/config_demo.json +++ b/test_data/config_demo.json @@ -4692,6 +4692,48 @@ } }, "assemblyNames": ["hg38"] + }, + { + "type": "FeatureTrack", + "trackId": "COLO829_tumor.ht_modkit.bed", + "name": "COLO829_tumor.ht_modkit.bed", + "adapter": { + "type": "BedTabixAdapter", + "bedGzLocation": { + "uri": "https://jbrowse.org/genomes/GRCh38/COLO829/COLO829_tumor.ht_modkit.bed.gz", + "locationType": "UriLocation" + }, + "index": { + "location": { + "uri": "https://jbrowse.org/genomes/GRCh38/COLO829/COLO829_tumor.ht_modkit.bed.gz.tbi", + "locationType": "UriLocation" + }, + "indexType": "TBI" + } + }, + "assemblyNames": ["hg38"], + "category": ["Methylation"] + }, + { + "type": "MultiQuantitativeTrack", + "trackId": "COLO829_tumor.ht_modkit.bed_multi", + "name": "COLO829_tumor.ht_modkit.bed (as MultiQuantitativeTrack)", + "adapter": { + "type": "BedTabixAdapter", + "bedGzLocation": { + "uri": "https://jbrowse.org/genomes/GRCh38/COLO829/COLO829_tumor.ht_modkit.bed.gz", + "locationType": "UriLocation" + }, + "index": { + "location": { + "uri": "https://jbrowse.org/genomes/GRCh38/COLO829/COLO829_tumor.ht_modkit.bed.gz.tbi", + "locationType": "UriLocation" + }, + "indexType": "TBI" + } + }, + "assemblyNames": ["hg38"], + "category": ["Methylation"] } ], "connections": [], diff --git a/test_data/hs1.json b/test_data/hs1/config.json similarity index 100% rename from test_data/hs1.json rename to test_data/hs1/config.json