diff --git a/package.json b/package.json index 63b9680..997ce95 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "data-loader": "^2.9.1", "file-saver": "^2.0.2", "lit-element": "^2.2.0", + "protvista-coloured-sequence": "3.8.10", "protvista-datatable": "3.8.10", "protvista-feature-adapter": "3.8.12", "protvista-filter": "3.8.4", diff --git a/src/config.json b/src/config.json index 7480821..b807c1e 100644 --- a/src/config.json +++ b/src/config.json @@ -1,5 +1,27 @@ { "categories": [ + { + "name": "ALPHAFOLD_CONFIDENCE", + "label": "AlphaFold", + "trackType": "protvista-coloured-sequence", + "scale": "H:90,M:70,L:50,D:0", + "color-range": "#ff7d45:0,#ffdb13:50,#65cbf3:70,#0053d6:90,#0053d6:100", + "tracks": [ + { + "name": "alphafold_confidence", + "label": "AlphaFold Confidence", + "labelUrl": "https://alphafold.ebi.ac.uk/entry/{accession}", + "trackType": "protvista-coloured-sequence", + "data": [ + { + "adapter": "protvista-alphafold-confidence-adapter", + "url": "https://alphafold.ebi.ac.uk/api/prediction/{accession}" + } + ], + "tooltip": "AlphaFold prediction confidence" + } + ] + }, { "name": "DOMAINS_AND_SITES", "label": "Domains & sites", diff --git a/src/protvista-alphafold-confidence.ts b/src/protvista-alphafold-confidence.ts new file mode 100644 index 0000000..78137de --- /dev/null +++ b/src/protvista-alphafold-confidence.ts @@ -0,0 +1,49 @@ +type AlphafoldPayload = Array<{ + entryId: string; + gene: string; + uniprotAccession: string; + uniprotId: string; + uniprotDescription: string; + taxId: number; + organismScientificName: string; + uniprotStart: number; + uniprotEnd: number; + uniprotSequence: string; + modelCreatedDate: string; + latestVersion: number; + allVersions: number[]; + cifUrl: string; + bcifUrl: string; + pdbUrl: string; + paeImageUrl: string; + paeDocUrl: string; +}>; + +type AlphafoldConfidencePayload = { + residueNumber: Array; + confidenceScore: Array; + confidenceCategory: Array; +}; + +export const getConfidenceURLFromPayload = (data: AlphafoldPayload) => { + const cifURL = data?.[0]?.cifUrl; + return cifURL?.length + ? cifURL.replace('-model', '-confidence').replace('.cif', '.json') + : null; +}; + +const loadConfidence = async ( + url: string +): Promise => { + try { + return (await fetch(url)).json(); + } catch (e) { + console.error(`Couldn't load AlphaFold confidence`, e); + } +}; + +export const transformData = async (data: AlphafoldPayload) => { + const confidenceUrl = getConfidenceURLFromPayload(data); + const confidenceData = await loadConfidence(confidenceUrl); + return confidenceData.confidenceCategory.join(''); +}; diff --git a/src/protvista-uniprot.ts b/src/protvista-uniprot.ts index e15af34..1ec5017 100644 --- a/src/protvista-uniprot.ts +++ b/src/protvista-uniprot.ts @@ -5,6 +5,7 @@ import { frame } from 'timing-functions'; import ProtvistaNavigation from 'protvista-navigation'; import ProtvistaTooltip from 'protvista-tooltip'; import ProtvistaTrackConfig from 'protvista-track'; +import ProtvistaColouredSequenceConfig from 'protvista-coloured-sequence'; import ProtvistaInterproTrack from 'protvista-interpro-track'; import ProtvistaSequence from 'protvista-sequence'; import ProtvistaVariation from 'protvista-variation'; @@ -20,6 +21,7 @@ import { transformData as _transformDataStructureAdapter } from 'protvista-struc import { transformData as _transformDataVariationAdapter } from 'protvista-variation-adapter'; import { transformData as _transformDataInterproAdapter } from 'protvista-interpro-adapter'; import { transformData as _transformDataProteomicsPTMApdapter } from './protvista-ptm-exchange'; +import { transformData as _transformDataAlphaFoldConfidenceAdapter } from './protvista-alphafold-confidence'; import defaultConfig from './config.json'; import _ProtvistaUniprotStructure from './protvista-uniprot-structure'; @@ -37,7 +39,11 @@ export const transformDataProteomicsAdapter = _transformDataProteomicsAdapter; export const transformDataStructureAdapter = _transformDataStructureAdapter; export const transformDataVariationAdapter = _transformDataVariationAdapter; export const transformDataInterproAdapter = _transformDataInterproAdapter; -export const transformDataProteomicsPTMApdapter = _transformDataProteomicsPTMApdapter; +export const transformDataProteomicsPTMApdapter = + _transformDataProteomicsPTMApdapter; +export const transformDataAlphaFoldConfidenceAdapter = + _transformDataAlphaFoldConfidenceAdapter; + export const filterConfig = _filterConfig; export const colorConfig = _colorConfig; export const ProtvistaUniprotStructure = _ProtvistaUniprotStructure; @@ -49,18 +55,22 @@ const adapters = { 'protvista-proteomics-adapter': transformDataProteomicsAdapter, 'protvista-structure-adapter': transformDataStructureAdapter, 'protvista-variation-adapter': transformDataVariationAdapter, - 'protvista-proteomics-ptm-adapter': transformDataProteomicsPTMApdapter + 'protvista-proteomics-ptm-adapter': transformDataProteomicsPTMApdapter, + 'protvista-alphafold-confidence-adapter': + transformDataAlphaFoldConfidenceAdapter, }; type TrackType = | 'protvista-track' | 'protvista-variation' | 'protvista-variation-graph' - | 'protvista-interpro-track'; + | 'protvista-interpro-track' + | 'protvista-coloured-sequence'; type ProtvistaTrackConfig = { name: string; label: string; + labelUrl?: string; filter: string; trackType: TrackType; data: { @@ -74,7 +84,9 @@ type ProtvistaTrackConfig = { tooltip: string; color?: string; shape?: string; //TODO: eventually replace with list + scale?: string; filterComponent?: 'protvista-filter'; + 'color-range'?: string; }; type ProtvistaCategory = { @@ -84,6 +96,8 @@ type ProtvistaCategory = { tracks: ProtvistaTrackConfig[]; color?: string; shape?: string; //TODO: eventually replace with list + scale?: string; + 'color-range'?: string; }; export type DownloadConfig = { @@ -149,6 +163,10 @@ class ProtvistaUniprot extends LitElement { loadComponent('protvista-navigation', ProtvistaNavigation); loadComponent('protvista-tooltip', ProtvistaTooltip); loadComponent('protvista-track', ProtvistaTrackConfig); + loadComponent( + 'protvista-coloured-sequence', + ProtvistaColouredSequenceConfig + ); loadComponent('protvista-interpro-track', ProtvistaInterproTrack); loadComponent('protvista-sequence', ProtvistaSequence); loadComponent('protvista-variation', ProtvistaVariation); @@ -189,49 +207,48 @@ class ProtvistaUniprot extends LitElement { // Now iterate over tracks and categories, transforming the data // and assigning it as adequate - this.config.categories.map( - ({ name: categoryName, tracks, trackType }) => { - // const categoryData: any = []; - const categoryData = tracks.map( - ({ data: dataConfig, name: trackName, filter }) => { - const { url, adapter } = dataConfig[0]; // TODO handle array - const trackData = this.rawData[url] || []; - - if ( - !trackData || - (adapter === 'protvista-variation-adapter' && - trackData.length === 0) - ) { - return; - } - // 1. Convert data - const transformedData = adapter - ? adapters[adapter](trackData) - : trackData; - - // 2. Filter raw data if filter is specified - const filteredData = - Array.isArray(transformedData) && filter - ? transformedData.filter( - ({ type }: { type?: string }) => type === filter - ) - : transformedData; - if (!filteredData) { - return; - } - - // 3. Assign track data - this.data[`${categoryName}-${trackName}`] = filteredData; - - return filteredData; + for (const { name: categoryName, tracks, trackType } of this.config + .categories) { + const categoryData = await Promise.all( + tracks.map(async ({ data: dataConfig, name: trackName, filter }) => { + const { url, adapter } = dataConfig[0]; // TODO handle array + const trackData = this.rawData[url] || []; + + if ( + !trackData || + (adapter === 'protvista-variation-adapter' && + trackData.length === 0) + ) { + return; } - ); - this.data[categoryName] = - trackType === 'protvista-variation-graph' - ? categoryData[0] - : categoryData.flat(); - } - ); + // 1. Convert data + const transformedData = adapter + ? await adapters[adapter](trackData) + : trackData; + + // 2. Filter raw data if filter is specified + const filteredData = + Array.isArray(transformedData) && filter + ? transformedData.filter( + ({ type }: { type?: string }) => type === filter + ) + : transformedData; + if (!filteredData) { + return; + } + + // 3. Assign track data + this.data[`${categoryName}-${trackName}`] = filteredData; + + return filteredData; + }) + ); + this.data[categoryName] = + trackType === 'protvista-variation-graph' || + trackType === 'protvista-coloured-sequence' + ? categoryData[0] + : categoryData.flat(); + } } this.loading = false; this.requestUpdate(); // Why? @@ -244,7 +261,9 @@ class ProtvistaUniprot extends LitElement { `track-${id}` ); // set data if it hasn't changed - if (element && element.data !== data) element.data = data; + if (element && element.data !== data) { + element.data = data; + } const currentCategory = this.config?.categories.find( ({ name }) => name === id ); @@ -454,10 +473,12 @@ class ProtvistaUniprot extends LitElement { > ${category.label} -
@@ -484,14 +507,27 @@ class ProtvistaUniprot extends LitElement { ? html`
- ${track.filterComponent - ? this.getFilterComponent( - `${category.name}-${track.name}` - ) - : track.label} + ${(track.filterComponent && + this.getFilterComponent( + `${category.name}-${track.name}` + )) || + (track.labelUrl && + html`${track.label}`) || + track.label}
${this.getTrack( @@ -499,7 +535,9 @@ class ProtvistaUniprot extends LitElement { 'non-overlapping', track.color || category.color, track.shape || category.shape, - `${category.name}-${track.name}` + `${category.name}-${track.name}`, + track.scale || category.scale, + track['color-range'] || category['color-range'] )}
@@ -529,7 +567,9 @@ class ProtvistaUniprot extends LitElement { 'non-overlapping', category.color, category.shape, - `${category.name}-${item.accession}` + `${category.name}-${item.accession}`, + category.scale, + category['color-range'] )} @@ -613,7 +653,15 @@ class ProtvistaUniprot extends LitElement { `; } - getTrack(trackType: TrackType, layout = '', color = '', shape = '', id = '') { + getTrack( + trackType: TrackType, + layout = '', + color = '', + shape = '', + id = '', + scale = '', + colorRange = '' + ) { // lit-html doesn't allow to have dynamic tag names, hence the switch/case // with repeated code switch (trackType) { @@ -662,6 +710,19 @@ class ProtvistaUniprot extends LitElement { > `; + case 'protvista-coloured-sequence': + return html` + + + `; default: console.warn('No Matching ProtvistaTrack Found.'); break; diff --git a/src/styles/protvista-styles.ts b/src/styles/protvista-styles.ts index b9bf442..cc49702 100644 --- a/src/styles/protvista-styles.ts +++ b/src/styles/protvista-styles.ts @@ -9,6 +9,11 @@ export default css` width: 80vw; } + .track-content__coloured-sequence { + display: flex; + align-items: center; + } + .nav-container, .category__track { display: flex; diff --git a/yarn.lock b/yarn.lock index d85e6c9..16aec74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6904,6 +6904,14 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.4" +protvista-coloured-sequence@3.8.10: + version "3.8.10" + resolved "https://registry.yarnpkg.com/protvista-coloured-sequence/-/protvista-coloured-sequence-3.8.10.tgz#22cf5f2fab4f424ca9637c3f4d4f468e819e9766" + integrity sha512-DeYHun64sjWDSCwo9s9LUGKS+qEIbuDRu/WLti5o+D5O3T+hWLZQ3dacFnuMoOw6n40Or2Ode0sjSq1mI4/Kjw== + dependencies: + protvista-sequence "^3.8.10" + protvista-utils "^3.8.10" + protvista-datatable@3.8.10: version "3.8.10" resolved "https://registry.yarnpkg.com/protvista-datatable/-/protvista-datatable-3.8.10.tgz#6f3f2885f55c8cb8e6609728dd4a000a1105916f" @@ -6975,7 +6983,7 @@ protvista-proteomics-adapter@3.8.12: protvista-feature-adapter "^3.8.12" uuid "^8.3.1" -protvista-sequence@3.8.10: +protvista-sequence@3.8.10, protvista-sequence@^3.8.10: version "3.8.10" resolved "https://registry.yarnpkg.com/protvista-sequence/-/protvista-sequence-3.8.10.tgz#d9ab87f0abe4b1d5cb138e90febc242e879af238" integrity sha512-5B7ef0KkXXN4y6j9yeAdPvbpPKB3Cc85vo5y+yS8u2dt21mcLqKGxEnEhZsP0GhKwH4kuCP511BTLww30h/lXw==