Skip to content

Commit d545d9d

Browse files
committed
Only generate tracks for available gff fields
1 parent 5f757a2 commit d545d9d

File tree

7 files changed

+464
-317
lines changed

7 files changed

+464
-317
lines changed

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export default tseslint.config(
7070
'@typescript-eslint/no-unsafe-assignment': 'off',
7171
'@typescript-eslint/restrict-template-expressions': 'off',
7272
'@typescript-eslint/no-empty-function': 'off',
73+
'@typescript-eslint/no-deprecated': 'off',
7374

7475
'unicorn/no-array-for-each': 'off',
7576
'unicorn/no-null': 'off',

src/LaunchProteinView/components/AlphaFoldDBSearch.tsx

Lines changed: 135 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,12 @@ const AlphaFoldDBSearch = observer(function ({
187187
connectedViewId: view.id,
188188
},
189189
],
190-
displayName: `Protein view ${uniprotId} - ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
190+
displayName: [
191+
'Protein view',
192+
uniprotId,
193+
getGeneDisplayName(feature),
194+
getTranscriptDisplayName(selectedTranscript),
195+
].join(' - '),
191196
})
192197
handleClose()
193198
}}
@@ -201,113 +206,140 @@ const AlphaFoldDBSearch = observer(function ({
201206
}
202207
onClick={() => {
203208
if (uniprotId && isSessionWithAddTracks(session)) {
204-
session.addTemporaryAssembly?.({
205-
name: uniprotId,
206-
sequence: {
207-
type: 'ReferenceSequenceTrack',
208-
trackId: `${uniprotId}-ReferenceSequenceTrack`,
209-
sequenceType: 'pep',
210-
adapter: {
211-
type: 'UnindexedFastaAdapter',
212-
rewriteRefNames: "jexl:split(refName,'|')[1]",
213-
fastaLocation: {
214-
uri: `https://rest.uniprot.org/uniprotkb/${uniprotId}.fasta`,
209+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
210+
;(async () => {
211+
try {
212+
session.addTemporaryAssembly?.({
213+
name: uniprotId,
214+
sequence: {
215+
type: 'ReferenceSequenceTrack',
216+
trackId: `${uniprotId}-ReferenceSequenceTrack`,
217+
sequenceType: 'pep',
218+
adapter: {
219+
type: 'UnindexedFastaAdapter',
220+
rewriteRefNames: "jexl:split(refName,'|')[1]",
221+
fastaLocation: {
222+
uri: `https://rest.uniprot.org/uniprotkb/${uniprotId}.fasta`,
223+
},
224+
},
215225
},
216-
},
217-
},
218-
})
219-
;[
220-
'Alternative sequence',
221-
'Beta strand',
222-
'Binding site',
223-
'Chain',
224-
'Compositional bias',
225-
'Cross-link',
226-
'Disulfide bond',
227-
'Domain',
228-
'Glycosylation',
229-
'Helix',
230-
'Modified residue',
231-
'Motif',
232-
'Mutagenesis',
233-
'Natural variant',
234-
'Peptide',
235-
'Region',
236-
'Sequence conflict',
237-
'Signal peptide',
238-
'Site',
239-
'Topological domain',
240-
'Transmembrane',
241-
'Turn',
242-
].forEach(type => {
243-
const s = `${uniprotId}-${type}`
244-
session.addTrackConf({
245-
type: 'FeatureTrack',
246-
trackId: s,
247-
name: type,
248-
adapter: {
249-
type: 'Gff3Adapter',
250-
gffLocation: {
251-
uri: `https://rest.uniprot.org/uniprotkb/${uniprotId}.gff`,
226+
})
227+
const url = `https://rest.uniprot.org/uniprotkb/${uniprotId}.gff`
228+
const res = await fetch(url)
229+
if (!res.ok) {
230+
throw new Error(`HTTP ${res.status} fetching ${url}`)
231+
}
232+
const data = await res.text()
233+
234+
const types = [
235+
...new Set(
236+
data
237+
.split('\n')
238+
.filter(f => !f.startsWith('#'))
239+
.map(f => f.trim())
240+
.filter(f => !!f)
241+
.map(f => f.split('\t')[2]),
242+
),
243+
]
244+
types.forEach(type => {
245+
const s = `${uniprotId}-${type}`
246+
session.addTrackConf({
247+
type: 'FeatureTrack',
248+
trackId: s,
249+
name: type,
250+
adapter: {
251+
type: 'Gff3Adapter',
252+
gffLocation: {
253+
uri: `https://rest.uniprot.org/uniprotkb/${uniprotId}.gff`,
254+
},
255+
},
256+
assemblyNames: [uniprotId],
257+
displays: [
258+
{
259+
displayId: `${type}-LinearBasicDisplay`,
260+
type: 'LinearBasicDisplay',
261+
jexlFilters: [`get(feature,'type')=='${type}'`],
262+
},
263+
],
264+
})
265+
})
266+
session.addTrackConf({
267+
type: 'FeatureTrack',
268+
trackId: 'Antigen',
269+
name: 'Antigen',
270+
adapter: {
271+
type: 'Gff3Adapter',
272+
gffLocation: {
273+
uri: `https://www.ebi.ac.uk/proteins/api/antigen/${uniprotId}?format=gff`,
274+
},
252275
},
253-
},
254-
assemblyNames: [uniprotId],
255-
displays: [
256-
{
257-
displayId: `${type}-LinearBasicDisplay`,
258-
type: 'LinearBasicDisplay',
259-
jexlFilters: [`get(feature,'type')=='${type}'`],
276+
assemblyNames: [uniprotId],
277+
})
278+
session.addTrackConf({
279+
type: 'FeatureTrack',
280+
trackId: 'Variation',
281+
name: 'Variation',
282+
adapter: {
283+
type: 'UniProtVariationAdapter',
284+
location: {
285+
uri: `https://www.ebi.ac.uk/proteins/api/variation/${uniprotId}.json`,
286+
},
260287
},
261-
],
262-
})
263-
})
264-
session.addTrackConf({
265-
type: 'QuantitativeTrack',
266-
trackId: 'AlphaFold confidence',
267-
name: 'AlphaFold confidence',
268-
adapter: {
269-
type: 'AlphaFoldConfidenceAdapter',
270-
location: {
271-
uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-confidence_v4.json`,
272-
locationType: 'UriLocation',
273-
},
274-
},
275-
assemblyNames: [uniprotId],
276-
})
277-
session.addTrackConf({
278-
type: 'MultiQuantitativeTrack',
279-
trackId: 'AlphaMissense scores',
280-
name: 'AlphaMissense scores',
281-
assemblyNames: [uniprotId],
282-
adapter: {
283-
type: 'AlphaMissensePathogenicityAdapter',
284-
location: {
285-
locationType: 'UriLocation',
286-
uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-aa-substitutions.csv`,
287-
},
288-
},
289-
displays: [
290-
{
291-
type: 'MultiLinearWiggleDisplay',
292-
displayId: 'AlphaMissense scores-MultiLinearWiggleDisplay',
293-
defaultRendering: 'multirowdensity',
294-
renderers: {
295-
MultiDensityRenderer: {
296-
type: 'MultiDensityRenderer',
297-
bicolorPivotValue: 0.5,
288+
assemblyNames: [uniprotId],
289+
})
290+
session.addTrackConf({
291+
type: 'QuantitativeTrack',
292+
trackId: 'AlphaFold confidence',
293+
name: 'AlphaFold confidence',
294+
adapter: {
295+
type: 'AlphaFoldConfidenceAdapter',
296+
location: {
297+
uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-confidence_v4.json`,
298298
},
299299
},
300-
},
301-
],
302-
})
303-
const view = session.addView('LinearGenomeView', {
304-
type: 'LinearGenomeView',
305-
displayName: `Protein view ${uniprotId} ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
306-
}) as LinearGenomeViewModel
307-
view.navToLocString(uniprotId, uniprotId).catch((e: unknown) => {
308-
console.error(e)
309-
session.notifyError(`${e}`, e)
310-
})
300+
assemblyNames: [uniprotId],
301+
})
302+
session.addTrackConf({
303+
type: 'MultiQuantitativeTrack',
304+
trackId: 'AlphaMissense scores',
305+
name: 'AlphaMissense scores',
306+
assemblyNames: [uniprotId],
307+
adapter: {
308+
type: 'AlphaMissensePathogenicityAdapter',
309+
location: {
310+
uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-aa-substitutions.csv`,
311+
},
312+
},
313+
displays: [
314+
{
315+
type: 'MultiLinearWiggleDisplay',
316+
displayId:
317+
'AlphaMissense scores-MultiLinearWiggleDisplay',
318+
defaultRendering: 'multirowdensity',
319+
renderers: {
320+
MultiDensityRenderer: {
321+
type: 'MultiDensityRenderer',
322+
bicolorPivotValue: 0.5,
323+
},
324+
},
325+
},
326+
],
327+
})
328+
const view = session.addView('LinearGenomeView', {
329+
type: 'LinearGenomeView',
330+
displayName: [
331+
'Protein view',
332+
uniprotId,
333+
getGeneDisplayName(feature),
334+
getTranscriptDisplayName(selectedTranscript),
335+
].join(' - '),
336+
}) as LinearGenomeViewModel
337+
await view.navToLocString(uniprotId, uniprotId)
338+
} catch (e) {
339+
console.error(e)
340+
session.notifyError(`${e}`, e)
341+
}
342+
})()
311343
}
312344
handleClose()
313345
}}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {
2+
BaseFeatureDataAdapter,
3+
BaseOptions,
4+
} from '@jbrowse/core/data_adapters/BaseAdapter'
5+
import { openLocation } from '@jbrowse/core/util/io'
6+
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
7+
import {
8+
Region,
9+
Feature,
10+
doesIntersect2,
11+
SimpleFeature,
12+
} from '@jbrowse/core/util'
13+
14+
interface UniProtVariantFeature {
15+
begin: string
16+
end: string
17+
wildType: string
18+
mutatedType: string
19+
xrefs: {
20+
name: string
21+
id: string
22+
url: string
23+
alternativeUrl: string
24+
}[]
25+
predictions?: {
26+
score: number
27+
}[]
28+
descriptions?: {
29+
value: string
30+
}[]
31+
populationFrequencies?: {
32+
frequency?: number
33+
}[]
34+
}
35+
36+
export default class UniProtVariationAdapter extends BaseFeatureDataAdapter {
37+
public static capabilities = ['getFeatures', 'getRefNames']
38+
39+
public feats:
40+
| Promise<{ uniqueId: string; start: number; end: number }[]>
41+
| undefined
42+
43+
private async loadDataP() {
44+
const { features } = JSON.parse(
45+
await openLocation(this.getConf('location')).readFile('utf8'),
46+
) as { features: UniProtVariantFeature[] }
47+
48+
const scoreField = this.getConf('scoreField')
49+
50+
return features.map(({ begin, end, ...rest }, idx) => ({
51+
...rest,
52+
uniqueId: `feat-${idx}`,
53+
start: +begin,
54+
end: +end + 1,
55+
score:
56+
scoreField === 'population_frequency'
57+
? rest.populationFrequencies?.[0]?.frequency
58+
: scoreField === 'variant_impact_score'
59+
? rest.predictions?.[0]?.score
60+
: undefined,
61+
description: rest.descriptions?.map(d => d.value).join(','),
62+
name: [
63+
rest.mutatedType
64+
? `${rest.wildType}->${rest.mutatedType}`
65+
: `${rest.wildType}->del`,
66+
],
67+
}))
68+
}
69+
70+
private async loadData(_opts: BaseOptions = {}) {
71+
if (!this.feats) {
72+
this.feats = this.loadDataP().catch((e: unknown) => {
73+
this.feats = undefined
74+
throw e
75+
})
76+
}
77+
78+
return this.feats
79+
}
80+
81+
public async getRefNames(_opts: BaseOptions = {}) {
82+
return []
83+
}
84+
85+
public getFeatures(query: Region, opts: BaseOptions = {}) {
86+
return ObservableCreate<Feature>(async observer => {
87+
const { start, end, refName } = query
88+
const data = await this.loadData()
89+
for (const f of data) {
90+
if (doesIntersect2(f.start, f.end, start, end)) {
91+
observer.next(new SimpleFeature({ ...f, refName }))
92+
}
93+
}
94+
observer.complete()
95+
}, opts.signal)
96+
}
97+
98+
public freeResources(): void {}
99+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ConfigurationSchema } from '@jbrowse/core/configuration'
2+
3+
/**
4+
* #config UniProtVariationAdapter
5+
*/
6+
function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
7+
8+
const UniProtVariationAdapter = ConfigurationSchema(
9+
'UniProtVariationAdapter',
10+
{
11+
/**
12+
* #slot
13+
*/
14+
location: {
15+
type: 'fileLocation',
16+
defaultValue: { uri: '/path/to/my.bed.gz', locationType: 'UriLocation' },
17+
},
18+
scoreField: {
19+
type: 'string',
20+
defaultValue: '',
21+
},
22+
},
23+
{ explicitlyTyped: true },
24+
)
25+
export default UniProtVariationAdapter

0 commit comments

Comments
 (0)