diff --git a/package.json b/package.json index f39b91d6..ab7a7e1d 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@nightingale-elements/nightingale-navigation": "5.6.0", "@nightingale-elements/nightingale-sequence": "5.6.0", "@nightingale-elements/nightingale-sequence-heatmap": "5.6.2", - "@nightingale-elements/nightingale-structure": "5.8.0", + "@nightingale-elements/nightingale-structure": "5.10.2", "@nightingale-elements/nightingale-track-canvas": "5.6.0", "@nightingale-elements/nightingale-variation-canvas": "^5.10.1", "color-hash": "2.0.2", diff --git a/src/protvista-uniprot-structure.ts b/src/protvista-uniprot-structure.ts index c2a6e520..64b29451 100644 --- a/src/protvista-uniprot-structure.ts +++ b/src/protvista-uniprot-structure.ts @@ -26,7 +26,6 @@ const PDBLinks = [ { name: 'PDBe', link: 'https://www.ebi.ac.uk/pdbe-srv/view/entry/' }, { name: 'RCSB-PDB', link: 'https://www.rcsb.org/structure/' }, { name: 'PDBj', link: 'https://pdbj.org/mine/summary/' }, - { name: 'PDBsum', link: 'https://www.ebi.ac.uk/pdbsum/' }, ]; const alphaFoldLinkUrl = 'https://alphafold.ebi.ac.uk/search/text/'; const foldseekUrl = `https://search.foldseek.com/search`; @@ -34,7 +33,6 @@ const uniprotKBUrl = 'https://www.uniprot.org/uniprotkb/'; // Excluded source from 3d-beacons is PDBe as we fetch them separately from UniProt const providersFrom3DBeacons = [ - 'AlphaFold DB', 'SWISS-MODEL', 'ModelArchive', 'PED', @@ -45,18 +43,6 @@ const providersFrom3DBeacons = [ 'levylab', ]; -const sourceMethods = new Map([ - ['AlphaFold DB', 'Predicted'], - ['SWISS-MODEL', 'Modeling'], - ['ModelArchive', 'Modeling'], - ['PED', 'Modeling'], - ['SASBDB', 'SAS'], - ['isoform.io', 'Predicted'], - ['AlphaFill', 'Predicted'], - ['HEGELAB', 'Modeling'], - ['levylab', 'Modeling'], -]); - type UniProtKBData = { uniProtKBCrossReferences: UniProtKBCrossReference[]; sequence: Sequence; @@ -140,6 +126,7 @@ export type ProcessedStructureData = { isoformId?: string; isoformIsCanonical?: boolean; afPrediction?: boolean; // Flag to differentiate the structure source as AlphaFold prediction API vs 3DBeacons AlphaFold + oligomericState?: string; }; type IsoformIdSequence = Array<{ @@ -189,7 +176,7 @@ const processPDBData = (data: UniProtKBData): ProcessedStructureData[] => method, resolution: !resolution || resolution === '-' ? undefined : resolution, - downloadUrl: `https://www.ebi.ac.uk/pdbe/entry-files/download/pdb${id.toLowerCase()}.ent`, + downloadUrl: `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}_updated.cif`, chain, positions, protvistaFeatureId: id, @@ -203,29 +190,48 @@ const processAFData = ( data: AlphaFoldPayload, isoforms?: IsoformIdSequence, canonicalSequence?: string -): ProcessedStructureData[] => - data +): ProcessedStructureData[] => { + const uniqueData = [ + ...new Map(data.map((d) => [d.modelEntityId, d])).values(), + ]; + + return uniqueData .map((d) => { const isoformMatch = isoforms?.find( ({ sequence }) => d.sequence === sequence ); + let chain = d.chainId; + const oligomericState = d.isComplex + ? `${d.assemblyType}${d.oligomericState}` + : 'Monomer'; + + if (d.isComplex && oligomericState === 'Homodimer') { + chain = data.filter(({ modelEntityId }) => modelEntityId === d.modelEntityId).flatMap(({ chainId }) => chainId).sort().join(', '); + } return { id: d.modelEntityId, source: 'AlphaFold DB', - method: 'Predicted', positions: `${d.sequenceStart}-${d.sequenceEnd}`, protvistaFeatureId: d.modelEntityId, downloadUrl: d.pdbUrl, amAnnotationsUrl: d.amAnnotationsUrl, - isoformId: isoformMatch?.isoformId, - isoformIsCanonical: isoformMatch + isoformId: !d.isComplex ? isoformMatch?.isoformId : undefined, + isoformIsCanonical: !d.isComplex && isoformMatch ? isoformMatch.sequence === canonicalSequence : undefined, afPrediction: true, + oligomericState, + chain }; }) - .sort((a, b) => getIsoformNum(a.id) - getIsoformNum(b.id)); + .sort((a, b) => getIsoformNum(a.id) - getIsoformNum(b.id)) + .sort((a, b) => { + const aMonomer = a.oligomericState === 'Monomer' ? 0 : 1; + const bMonomer = b.oligomericState === 'Monomer' ? 0 : 1; + return aMonomer - bMonomer; + }); +}; const process3DBeaconsData = ( data: BeaconsData, @@ -263,7 +269,6 @@ const process3DBeaconsData = ( structures?.map(({ summary }) => ({ id: summary.model_identifier, source: summary.provider, - method: sourceMethods.get(summary.provider), positions: summary.uniprot_start && summary.uniprot_end ? `${summary.uniprot_start}-${summary.uniprot_end}` @@ -275,8 +280,9 @@ const process3DBeaconsData = ( ? 'https://www.isoform.io/home' : summary.model_page_url, chain: - summary.entities?.flatMap((entity) => entity.chain_ids).join(', ') || + summary.entities?.flatMap((entity) => entity.identifier_category === 'UNIPROT' ? entity.chain_ids : []).join(', ') || undefined, + oligomericState: summary.oligomeric_state || undefined, })) || [] ); }; @@ -514,7 +520,7 @@ class ProtvistaUniprotStructure extends LitElement { // AlphaMissense predictions are only available in AF predictions endpoint const alphaFoldUrl = this.accession && !this.checksum - ? `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}` + ? `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}?include_complexes=true` : ''; // exclude_provider accepts only value hence 'pdbe' as majority of the models are from there if querying by accession const beaconsUrl = @@ -566,21 +572,7 @@ class ProtvistaUniprotStructure extends LitElement { // TODO: return if no data at all // if (!payload) return; - const beaconsAFData = beaconsData.filter( - ({ source }) => source === 'AlphaFold DB' - ); - const beaconsNonAFData = beaconsData.filter( - ({ source }) => source !== 'AlphaFold DB' - ); - - const uniqueAFData = [ - ...new Map( - // The order of the spread is important as we want to prioritise AF data from AF predictions API over 3DBeacons - [...beaconsAFData, ...afData].map((obj) => [obj.id, obj]) - ).values(), - ]; - - const data = [...pdbData, ...uniqueAFData, ...beaconsNonAFData]; + const data = [...pdbData, ...afData, ...beaconsData]; this.data = data; this.columns = this.getColumns(); diff --git a/yarn.lock b/yarn.lock index d22d41d1..1fc1c3de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -485,13 +485,14 @@ "@nightingale-elements/nightingale-new-core" "^5.6.0" d3 "7.9.0" -"@nightingale-elements/nightingale-structure@5.8.0": - version "5.8.0" - resolved "https://registry.yarnpkg.com/@nightingale-elements/nightingale-structure/-/nightingale-structure-5.8.0.tgz#55e69196a05206fa6cb8507a7e97836f2c3765b9" - integrity sha512-HdInDB0hh7d7LSyAOUJwEYgoBNffR/kvOU1p8I9ezMccpQ4YYf6E/Fj3xUd9NxWfULNfGk9AF/Fsg43qC6HzHw== +"@nightingale-elements/nightingale-structure@5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@nightingale-elements/nightingale-structure/-/nightingale-structure-5.10.2.tgz#84f019b51f856441efde742f6b961d5971e8492a" + integrity sha512-udJKAR6cDJHWpSlM8rnCcYojPxVJwtmmZ2d+qVGtidAdEiJ9WxqJkXmMtkUmn+zkFpKylHWoXlmzlPGHPK6XhQ== dependencies: "@nightingale-elements/nightingale-new-core" "^5.6.0" d3 "7.9.0" + dompurify "^3.0.0" molstar "3.44.0" "@nightingale-elements/nightingale-track-canvas@5.6.0": @@ -1066,7 +1067,7 @@ resolved "https://registry.yarnpkg.com/@types/swagger-ui-dist/-/swagger-ui-dist-3.30.4.tgz#2d74bc67bcae443d2daf912cb803b4c274d46514" integrity sha512-FeOBc7uj4/lAIh4jkBzorvmNoUU9JgSccyDIRo0E9MJw9KQfSxlwpHCyKGnU9kfV5N5dEdfpY8wm7to3nSwTmA== -"@types/trusted-types@^2.0.2": +"@types/trusted-types@^2.0.2", "@types/trusted-types@^2.0.7": version "2.0.7" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== @@ -2781,6 +2782,13 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" +dompurify@^3.0.0: + version "3.4.11" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.11.tgz#29c8ba496475f279ef4015784068452fb14a0680" + integrity sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"