From 616e0a3cd098d2fd614e9fdadde9f6a12858af89 Mon Sep 17 00:00:00 2001 From: Swaathik Date: Fri, 19 Jun 2026 15:26:55 +0100 Subject: [PATCH 1/6] Add/remove properties in structure table --- src/protvista-uniprot-structure.ts | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/protvista-uniprot-structure.ts b/src/protvista-uniprot-structure.ts index c2a6e520..3819ecc2 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`; @@ -45,18 +44,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 +127,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 +177,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, @@ -223,6 +211,7 @@ const processAFData = ( ? isoformMatch.sequence === canonicalSequence : undefined, afPrediction: true, + oligomericState: 'MONOMER', }; }) .sort((a, b) => getIsoformNum(a.id) - getIsoformNum(b.id)); @@ -263,7 +252,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}` @@ -277,6 +265,7 @@ const process3DBeaconsData = ( chain: summary.entities?.flatMap((entity) => entity.chain_ids).join(', ') || undefined, + oligomericState: summary.oligomeric_state || undefined, })) || [] ); }; From a6803a211653b24d5d2808dcb5c986eb37bf0284 Mon Sep 17 00:00:00 2001 From: Swaathik Date: Fri, 19 Jun 2026 16:56:07 +0100 Subject: [PATCH 2/6] Use predictions API for fetching complexes --- src/protvista-uniprot-structure.ts | 60 +++++++++++++++++------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/protvista-uniprot-structure.ts b/src/protvista-uniprot-structure.ts index 3819ecc2..a8dd864e 100644 --- a/src/protvista-uniprot-structure.ts +++ b/src/protvista-uniprot-structure.ts @@ -33,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', @@ -191,30 +190,40 @@ 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 ); - 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: 'MONOMER', + oligomericState: d.isComplex + ? `${d.assemblyType}${d.oligomericState}` + : 'Monomer', }; }) - .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, @@ -503,7 +512,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 = @@ -555,21 +564,22 @@ 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 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(); From fa6bf0f17f9eb22bd934cb4750208f1c265b6960 Mon Sep 17 00:00:00 2001 From: Swaathik Date: Fri, 19 Jun 2026 17:13:49 +0100 Subject: [PATCH 3/6] Show chain ids only if the identifier_category is uniprot --- src/protvista-uniprot-structure.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protvista-uniprot-structure.ts b/src/protvista-uniprot-structure.ts index a8dd864e..190e5c9b 100644 --- a/src/protvista-uniprot-structure.ts +++ b/src/protvista-uniprot-structure.ts @@ -272,7 +272,7 @@ 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, })) || [] From 0ba9a1682b70037646f637d0709b46b50613abe5 Mon Sep 17 00:00:00 2001 From: Swaathik Date: Tue, 23 Jun 2026 13:25:11 +0100 Subject: [PATCH 4/6] Add chain information from AF payload --- src/protvista-uniprot-structure.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/protvista-uniprot-structure.ts b/src/protvista-uniprot-structure.ts index 190e5c9b..ec0f64d2 100644 --- a/src/protvista-uniprot-structure.ts +++ b/src/protvista-uniprot-structure.ts @@ -200,6 +200,15 @@ const processAFData = ( 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', @@ -212,9 +221,8 @@ const processAFData = ( ? isoformMatch.sequence === canonicalSequence : undefined, afPrediction: true, - oligomericState: d.isComplex - ? `${d.assemblyType}${d.oligomericState}` - : 'Monomer', + oligomericState, + chain }; }) .sort((a, b) => getIsoformNum(a.id) - getIsoformNum(b.id)) From c7a6caa4c114ae96beff46058d80d96b59696595 Mon Sep 17 00:00:00 2001 From: Swaathik Date: Thu, 9 Jul 2026 14:00:05 +0100 Subject: [PATCH 5/6] Update nightingale dependency --- package.json | 2 +- yarn.lock | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) 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/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" From 7877911a2f4caed3e60bbcf6d11bc0d8f37918e1 Mon Sep 17 00:00:00 2001 From: Swaathik Date: Fri, 10 Jul 2026 09:44:36 +0100 Subject: [PATCH 6/6] Remove commented code --- src/protvista-uniprot-structure.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/protvista-uniprot-structure.ts b/src/protvista-uniprot-structure.ts index ec0f64d2..64b29451 100644 --- a/src/protvista-uniprot-structure.ts +++ b/src/protvista-uniprot-structure.ts @@ -572,21 +572,6 @@ 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;