protvista-uniprot 4.3.7 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/protvista-uniprot.mjs +18835 -18905
- package/dist/protvista-uniprot.mjs.map +1 -1
- package/dist/types/protvista-uniprot-structure.d.ts +7 -3
- package/dist/types/tooltips/ptm-tooltip.d.ts +0 -2
- package/package.json +3 -3
- package/src/adapters/alphamissense-pathogenicity-adapter.ts +12 -30
- package/src/adapters/proteomics-adapter.ts +56 -11
- package/src/adapters/ptm-exchange-adapter.ts +31 -43
- package/src/config.ts +2 -63
- package/src/protvista-uniprot-structure.ts +173 -34
- package/src/protvista-uniprot.ts +0 -6
- package/src/tooltips/feature-tooltip.ts +7 -18
- package/src/tooltips/ptm-tooltip.ts +5 -65
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { LitElement, TemplateResult } from 'lit';
|
|
2
2
|
type ProcessedStructureData = {
|
|
3
3
|
id: string;
|
|
4
|
-
source:
|
|
5
|
-
method
|
|
4
|
+
source: string;
|
|
5
|
+
method?: string;
|
|
6
6
|
resolution?: string;
|
|
7
7
|
chain?: string;
|
|
8
8
|
positions?: string;
|
|
9
9
|
downloadLink?: string;
|
|
10
|
+
sourceDBLink?: string;
|
|
10
11
|
protvistaFeatureId: string;
|
|
11
12
|
};
|
|
12
13
|
declare class ProtvistaUniprotStructure extends LitElement {
|
|
@@ -17,6 +18,7 @@ declare class ProtvistaUniprotStructure extends LitElement {
|
|
|
17
18
|
colorTheme?: string;
|
|
18
19
|
private loading?;
|
|
19
20
|
private alphamissenseAvailable?;
|
|
21
|
+
private modelUrl;
|
|
20
22
|
constructor();
|
|
21
23
|
static get properties(): {
|
|
22
24
|
accession: {
|
|
@@ -43,8 +45,10 @@ declare class ProtvistaUniprotStructure extends LitElement {
|
|
|
43
45
|
updated(): void;
|
|
44
46
|
addStyles(): void;
|
|
45
47
|
removeStyles(): void;
|
|
46
|
-
onTableRowClick({ id }: {
|
|
48
|
+
onTableRowClick({ id, source, downloadLink, }: {
|
|
47
49
|
id: string;
|
|
50
|
+
source?: string;
|
|
51
|
+
downloadLink?: string;
|
|
48
52
|
}): void;
|
|
49
53
|
get cssStyle(): import('lit').CSSResult;
|
|
50
54
|
/**
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { PTM } from '../adapters/ptm-exchange-adapter';
|
|
2
2
|
export declare const phosphorylate: (aa: string) => string;
|
|
3
3
|
export declare const sumoylate: (aa: string) => string;
|
|
4
|
-
export declare const ubiquitinate: (aa: string) => string;
|
|
5
|
-
export declare const acetylate: (aa: string) => string;
|
|
6
4
|
declare const formatTooltip: (title: string, ptms: PTM[], aa: string, confidenceScore: string) => string;
|
|
7
5
|
export default formatTooltip;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protvista-uniprot",
|
|
3
3
|
"description": "ProtVista tool for the UniProt website",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.4.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
7
|
"src"
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"@nightingale-elements/nightingale-manager": "5.6.0",
|
|
37
37
|
"@nightingale-elements/nightingale-navigation": "5.6.0",
|
|
38
38
|
"@nightingale-elements/nightingale-sequence": "5.6.0",
|
|
39
|
-
"@nightingale-elements/nightingale-sequence-heatmap": "5.6.
|
|
40
|
-
"@nightingale-elements/nightingale-structure": "5.
|
|
39
|
+
"@nightingale-elements/nightingale-sequence-heatmap": "5.6.0",
|
|
40
|
+
"@nightingale-elements/nightingale-structure": "5.7.0",
|
|
41
41
|
"@nightingale-elements/nightingale-track-canvas": "5.6.0",
|
|
42
42
|
"@nightingale-elements/nightingale-variation": "5.6.0",
|
|
43
43
|
"color-hash": "2.0.2",
|
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
import { AlphafoldPayload } from './types/alphafold';
|
|
2
2
|
|
|
3
|
-
// from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const likelyAmbiguous = 0.5895;
|
|
10
|
-
const likelyPathogenic = 0.7264;
|
|
11
|
-
const pathogenic = 0.8632;
|
|
12
|
-
const certainlyPathogenic = 1;
|
|
3
|
+
// from example data
|
|
4
|
+
// benign: [0.0448,0.3397]: x < 0.34
|
|
5
|
+
// ambiguous: [0.34,0.564]: 0.34 <= x <= 0.564
|
|
6
|
+
// pathogenic: [0.5646,0.9999]: 0.564 < x
|
|
7
|
+
const benign = 0.34;
|
|
8
|
+
const pathogenic = 0.564;
|
|
13
9
|
|
|
14
10
|
export const rowSplitter = /\s*\n\s*/;
|
|
15
11
|
export const cellSplitter = /^(.)(\d+)(.),(.+),(\w+)$/;
|
|
16
12
|
|
|
17
|
-
const pathogenicityCategories = [
|
|
18
|
-
{ min: certainlyBenign, max: benign, code: 'H' },
|
|
19
|
-
{ min: benign, max: veryLikelyBenign, code: 'V' },
|
|
20
|
-
{ min: veryLikelyBenign, max: likelyBenign, code: 'L' },
|
|
21
|
-
{ min: likelyBenign, max: ambiguous, code: 'A' },
|
|
22
|
-
{ min: ambiguous, max: likelyAmbiguous, code: 'l' },
|
|
23
|
-
{ min: likelyAmbiguous, max: likelyPathogenic, code: 'h' },
|
|
24
|
-
{ min: likelyPathogenic, max: pathogenic, code: 'p' },
|
|
25
|
-
{ min: pathogenic, max: certainlyPathogenic, code: 'P' },
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const getPathogenicityCode = (score) => {
|
|
29
|
-
for (const { min, max, code } of pathogenicityCategories) {
|
|
30
|
-
if (score >= min && score < max) {
|
|
31
|
-
return code;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
13
|
type Row = {
|
|
37
14
|
wildType: string;
|
|
38
15
|
position: number;
|
|
@@ -71,6 +48,7 @@ const parseCSV = (rawText: string): string => {
|
|
|
71
48
|
|
|
72
49
|
const out = [];
|
|
73
50
|
for (const position of positions) {
|
|
51
|
+
let letter = 'A';
|
|
74
52
|
// maximum
|
|
75
53
|
// const value = Math.max(
|
|
76
54
|
// ...position.map((variation) => variation.pathogenicityScore)
|
|
@@ -81,7 +59,11 @@ const parseCSV = (rawText: string): string => {
|
|
|
81
59
|
(acc, variation) => acc + +variation.pathogenicityScore,
|
|
82
60
|
0
|
|
83
61
|
) / position.length;
|
|
84
|
-
|
|
62
|
+
if (value > pathogenic) {
|
|
63
|
+
letter = 'P';
|
|
64
|
+
} else if (value < benign) {
|
|
65
|
+
letter = 'B';
|
|
66
|
+
}
|
|
85
67
|
out.push(letter);
|
|
86
68
|
}
|
|
87
69
|
|
|
@@ -13,19 +13,64 @@ const transformData = (data) => {
|
|
|
13
13
|
let adaptedData = [];
|
|
14
14
|
|
|
15
15
|
if (data && data.length !== 0) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
feature
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
/* Important: The PTM map is a temporary patch until multiple modifications are shown in the peptide. At this point, only 'phospho' sites are of interest.
|
|
17
|
+
Once they are available in the data, there is no need for the below merging */
|
|
18
|
+
|
|
19
|
+
// To merge PTM data present in same residue in a same length peptide, have a map [key: start-end-phospho site 1-... phosphosite n, value: corresponding feature elements]
|
|
20
|
+
const ptmMap: Record<string, any> = {};
|
|
21
|
+
data.features.forEach((feature) => {
|
|
22
|
+
let ft = `${feature.begin}-${feature.end}`;
|
|
23
|
+
if (feature.ptms) {
|
|
24
|
+
feature.ptms.forEach((ptm) => {
|
|
25
|
+
ft += `-${ptm.position}`;
|
|
26
|
+
});
|
|
27
|
+
ptmMap[ft] = ft in ptmMap ? [...ptmMap[ft], feature] : [feature];
|
|
28
|
+
}
|
|
27
29
|
});
|
|
28
30
|
|
|
31
|
+
// The else part alone is enough if the PTM information need not be merged.
|
|
32
|
+
if (Object.keys(ptmMap).length) {
|
|
33
|
+
adaptedData = Object.values(ptmMap).map((features) => {
|
|
34
|
+
// Only the dbReferences have to be merged as the rest is all the same
|
|
35
|
+
const mergedDbReferences = [];
|
|
36
|
+
features.forEach((feature) => {
|
|
37
|
+
feature.ptms.forEach((ptm) => {
|
|
38
|
+
ptm.dbReferences.forEach((dbReference) => {
|
|
39
|
+
mergedDbReferences.push(dbReference);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const mergedFeatures = {
|
|
45
|
+
type: features[0].type,
|
|
46
|
+
begin: features[0].begin,
|
|
47
|
+
end: features[0].end,
|
|
48
|
+
xrefs: features[0].xrefs,
|
|
49
|
+
evidences: features[0].evidences,
|
|
50
|
+
peptide: features[0].peptide,
|
|
51
|
+
unique: features[0].unique,
|
|
52
|
+
residuesToHighlight: features[0].ptms.map((ptm) => ({
|
|
53
|
+
name: ptm.name,
|
|
54
|
+
position: ptm.position,
|
|
55
|
+
sources: ptm.sources,
|
|
56
|
+
dbReferences: mergedDbReferences,
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return Object.assign(
|
|
61
|
+
mergedFeatures,
|
|
62
|
+
proteomicsTrackProperties(mergedFeatures, data.taxid)
|
|
63
|
+
);
|
|
64
|
+
}, []);
|
|
65
|
+
} else {
|
|
66
|
+
adaptedData = data.features.map((feature) => {
|
|
67
|
+
return Object.assign(
|
|
68
|
+
feature,
|
|
69
|
+
proteomicsTrackProperties(feature, data.taxid)
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
29
74
|
adaptedData = renameProperties(adaptedData);
|
|
30
75
|
}
|
|
31
76
|
return adaptedData;
|
|
@@ -59,50 +59,38 @@ const convertPtmExchangePtms = (
|
|
|
59
59
|
aa: string,
|
|
60
60
|
absolutePosition: number
|
|
61
61
|
) => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
dbReferences?.map(({ properties }) => properties['Confidence score'])
|
|
75
|
-
)
|
|
62
|
+
const confidenceScores = new Set(
|
|
63
|
+
ptms.flatMap(({ dbReferences }) =>
|
|
64
|
+
dbReferences?.map(({ properties }) => properties['Confidence score'])
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
let confidenceScore: string | null = null;
|
|
68
|
+
|
|
69
|
+
if (!confidenceScores.size) {
|
|
70
|
+
console.log('PTM has no confidence score');
|
|
71
|
+
} else if (confidenceScores.size > 1) {
|
|
72
|
+
console.error(
|
|
73
|
+
`PTM has a mixture of confidence scores: ${Array.from(confidenceScores)}`
|
|
76
74
|
);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.error(
|
|
81
|
-
`PTMeXchange PTM has a mixture of confidence scores: ${Array.from(
|
|
82
|
-
confidenceScores
|
|
83
|
-
)}`
|
|
84
|
-
);
|
|
85
|
-
} else {
|
|
86
|
-
[confidenceScore] = confidenceScores;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
75
|
+
} else {
|
|
76
|
+
[confidenceScore] = confidenceScores;
|
|
77
|
+
}
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
});
|
|
79
|
+
return {
|
|
80
|
+
source: 'PTMeXchange',
|
|
81
|
+
type: 'MOD_RES_LS',
|
|
82
|
+
start: absolutePosition,
|
|
83
|
+
end: absolutePosition,
|
|
84
|
+
shape: 'triangle',
|
|
85
|
+
tooltipContent: formatTooltip(
|
|
86
|
+
`MOD_RES_LS ${absolutePosition}-${absolutePosition}`,
|
|
87
|
+
ptms,
|
|
88
|
+
aa,
|
|
89
|
+
confidenceScore
|
|
90
|
+
),
|
|
91
|
+
color:
|
|
92
|
+
(confidenceScore && ConfidenceScoreColors[confidenceScore]) || 'black',
|
|
93
|
+
};
|
|
106
94
|
};
|
|
107
95
|
|
|
108
96
|
const transformData = (data: ProteomicsPtm) => {
|
|
@@ -142,7 +130,7 @@ const transformData = (data: ProteomicsPtm) => {
|
|
|
142
130
|
return Object.entries(absolutePositionToPtms).map(
|
|
143
131
|
([absolutePosition, { ptms, aa }]) =>
|
|
144
132
|
convertPtmExchangePtms(ptms, aa, +absolutePosition)
|
|
145
|
-
)
|
|
133
|
+
);
|
|
146
134
|
}
|
|
147
135
|
}
|
|
148
136
|
return [];
|
package/src/config.ts
CHANGED
|
@@ -815,8 +815,8 @@ const config: ProtvistaConfig = {
|
|
|
815
815
|
name: 'ALPHAMISSENSE_PATHOGENICITY',
|
|
816
816
|
label: 'AlphaMissense',
|
|
817
817
|
trackType: 'nightingale-colored-sequence',
|
|
818
|
-
scale: '
|
|
819
|
-
'color-range': '#
|
|
818
|
+
scale: 'P:100,A:50,B:0',
|
|
819
|
+
'color-range': '#9a131a:100,#a8a9ad:50,#3d5493:0',
|
|
820
820
|
tracks: [
|
|
821
821
|
{
|
|
822
822
|
name: 'alphamissense_pathogenicity',
|
|
@@ -851,67 +851,6 @@ const config: ProtvistaConfig = {
|
|
|
851
851
|
},
|
|
852
852
|
],
|
|
853
853
|
},
|
|
854
|
-
{
|
|
855
|
-
name: 'STRUCTURAL',
|
|
856
|
-
label: 'Structural features',
|
|
857
|
-
trackType: 'nightingale-track-canvas',
|
|
858
|
-
tracks: [
|
|
859
|
-
{
|
|
860
|
-
name: 'helix',
|
|
861
|
-
label: 'Helix',
|
|
862
|
-
filter: 'HELIX',
|
|
863
|
-
trackType: 'nightingale-track-canvas',
|
|
864
|
-
data: [
|
|
865
|
-
{
|
|
866
|
-
adapter: 'feature-adapter',
|
|
867
|
-
url: `${proteinsApiServices.features}{accession}`,
|
|
868
|
-
},
|
|
869
|
-
],
|
|
870
|
-
tooltip: 'The positions of experimentally determined helical regions',
|
|
871
|
-
},
|
|
872
|
-
{
|
|
873
|
-
name: 'strand',
|
|
874
|
-
label: 'Beta strand',
|
|
875
|
-
filter: 'STRAND',
|
|
876
|
-
trackType: 'nightingale-track-canvas',
|
|
877
|
-
data: [
|
|
878
|
-
{
|
|
879
|
-
adapter: 'feature-adapter',
|
|
880
|
-
url: `${proteinsApiServices.features}{accession}`,
|
|
881
|
-
},
|
|
882
|
-
],
|
|
883
|
-
tooltip: 'The positions of experimentally determined beta strands',
|
|
884
|
-
},
|
|
885
|
-
{
|
|
886
|
-
name: 'turn',
|
|
887
|
-
label: 'Turn',
|
|
888
|
-
filter: 'TURN',
|
|
889
|
-
trackType: 'nightingale-track-canvas',
|
|
890
|
-
data: [
|
|
891
|
-
{
|
|
892
|
-
adapter: 'feature-adapter',
|
|
893
|
-
url: `${proteinsApiServices.features}{accession}`,
|
|
894
|
-
},
|
|
895
|
-
],
|
|
896
|
-
tooltip:
|
|
897
|
-
'The positions of experimentally determined hydrogen-bonded turns',
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
name: 'coiled',
|
|
901
|
-
label: 'Coiled coil',
|
|
902
|
-
filter: 'COILED',
|
|
903
|
-
trackType: 'nightingale-track-canvas',
|
|
904
|
-
data: [
|
|
905
|
-
{
|
|
906
|
-
adapter: 'feature-adapter',
|
|
907
|
-
url: `${proteinsApiServices.features}{accession}`,
|
|
908
|
-
},
|
|
909
|
-
],
|
|
910
|
-
tooltip:
|
|
911
|
-
'Coiled coils are built by two or more alpha-helices that wind around each other to form a supercoil',
|
|
912
|
-
},
|
|
913
|
-
],
|
|
914
|
-
},
|
|
915
854
|
],
|
|
916
855
|
};
|
|
917
856
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { LitElement, html, svg, TemplateResult, css, nothing } from 'lit';
|
|
2
|
-
import { customElement } from 'lit/decorators.js';
|
|
2
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
3
3
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
4
4
|
import NightingaleStructure, {
|
|
5
5
|
PredictionData,
|
|
6
|
-
StructureData,
|
|
7
6
|
} from '@nightingale-elements/nightingale-structure';
|
|
8
7
|
import ProtvistaDatatable from 'protvista-datatable';
|
|
9
8
|
import { fetchAll, loadComponent } from './utils';
|
|
@@ -21,26 +20,111 @@ const PDBLinks = [
|
|
|
21
20
|
const alphaFoldLink = 'https://alphafold.ebi.ac.uk/entry/';
|
|
22
21
|
const foldseekLink = `https://search.foldseek.com/search`;
|
|
23
22
|
|
|
23
|
+
// Excluded sources from 3d-beacons are PDBe and AlphaFold models as we fetch them separately from their respective API's
|
|
24
|
+
const providersFrom3DBeacons = [
|
|
25
|
+
'SWISS-MODEL',
|
|
26
|
+
'ModelArchive',
|
|
27
|
+
'PED',
|
|
28
|
+
'SASBDB',
|
|
29
|
+
'isoform.io',
|
|
30
|
+
'AlphaFill',
|
|
31
|
+
'HEGELAB',
|
|
32
|
+
'levylab',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
type UniProtKBData = {
|
|
36
|
+
uniProtKBCrossReferences: UniProtKBCrossReference[];
|
|
37
|
+
sequence: Sequence;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type UniProtKBCrossReference = {
|
|
41
|
+
database: string;
|
|
42
|
+
id: string;
|
|
43
|
+
properties: Record<string, string>[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type Sequence = {
|
|
47
|
+
value: string;
|
|
48
|
+
length: number;
|
|
49
|
+
molWeight: number;
|
|
50
|
+
crc64: string;
|
|
51
|
+
md5: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type BeaconsData = {
|
|
55
|
+
uniprot_entry: {
|
|
56
|
+
ac: string;
|
|
57
|
+
id: string;
|
|
58
|
+
uniprot_checksum: string;
|
|
59
|
+
sequence_length: number;
|
|
60
|
+
segment_start: number;
|
|
61
|
+
segment_end: number;
|
|
62
|
+
};
|
|
63
|
+
structures: {
|
|
64
|
+
summary: {
|
|
65
|
+
model_identifier: string;
|
|
66
|
+
model_category: string;
|
|
67
|
+
model_url: string;
|
|
68
|
+
model_format: string;
|
|
69
|
+
model_type: string | null;
|
|
70
|
+
model_page_url: string;
|
|
71
|
+
provider: string;
|
|
72
|
+
number_of_conformers: number | null;
|
|
73
|
+
ensemble_sample_url: string | null;
|
|
74
|
+
ensemble_sample_format: string | null;
|
|
75
|
+
created: string;
|
|
76
|
+
sequence_identity: number;
|
|
77
|
+
uniprot_start: number;
|
|
78
|
+
uniprot_end: number;
|
|
79
|
+
coverage: number;
|
|
80
|
+
experimental_method: string | null;
|
|
81
|
+
resolution: number | null;
|
|
82
|
+
confidence_type: string;
|
|
83
|
+
confidence_version: string | null;
|
|
84
|
+
confidence_avg_local_score: number;
|
|
85
|
+
oligomeric_state: string | null;
|
|
86
|
+
preferred_assembly_id: string | null;
|
|
87
|
+
entities: {
|
|
88
|
+
entity_type: string;
|
|
89
|
+
entity_poly_type: string;
|
|
90
|
+
identifier: string;
|
|
91
|
+
identifier_category: string;
|
|
92
|
+
description: string;
|
|
93
|
+
chain_ids: string[];
|
|
94
|
+
}[];
|
|
95
|
+
};
|
|
96
|
+
}[];
|
|
97
|
+
};
|
|
98
|
+
|
|
24
99
|
type ProcessedStructureData = {
|
|
25
100
|
id: string;
|
|
26
|
-
source:
|
|
27
|
-
method
|
|
101
|
+
source: string;
|
|
102
|
+
method?: string;
|
|
28
103
|
resolution?: string;
|
|
29
104
|
chain?: string;
|
|
30
105
|
positions?: string;
|
|
31
106
|
downloadLink?: string;
|
|
107
|
+
sourceDBLink?: string;
|
|
32
108
|
protvistaFeatureId: string;
|
|
33
109
|
};
|
|
34
110
|
|
|
35
|
-
const processPDBData = (data:
|
|
36
|
-
data.
|
|
37
|
-
.filter((xref) => xref.
|
|
111
|
+
const processPDBData = (data: UniProtKBData): ProcessedStructureData[] =>
|
|
112
|
+
data.uniProtKBCrossReferences
|
|
113
|
+
.filter((xref) => xref.database === 'PDB')
|
|
38
114
|
.sort((refA, refB) => refA.id.localeCompare(refB.id))
|
|
39
115
|
.map(({ id, properties }) => {
|
|
40
116
|
if (!properties) {
|
|
41
117
|
return;
|
|
42
118
|
}
|
|
43
|
-
|
|
119
|
+
|
|
120
|
+
const propertyMap = properties.reduce((acc, item) => {
|
|
121
|
+
acc[item.key] = item.value;
|
|
122
|
+
return acc;
|
|
123
|
+
}, {} as Record<string, string>);
|
|
124
|
+
|
|
125
|
+
const method = propertyMap['Method'];
|
|
126
|
+
const resolution = propertyMap['Resolution'];
|
|
127
|
+
const chains = propertyMap['Chains'];
|
|
44
128
|
|
|
45
129
|
let chain;
|
|
46
130
|
let positions;
|
|
@@ -79,6 +163,20 @@ const processAFData = (data: PredictionData[]): ProcessedStructureData[] =>
|
|
|
79
163
|
downloadLink: d.pdbUrl,
|
|
80
164
|
}));
|
|
81
165
|
|
|
166
|
+
const process3DBeaconsData = (data: BeaconsData): ProcessedStructureData[] => {
|
|
167
|
+
const otherStructures = data.structures.filter(({ summary }) =>
|
|
168
|
+
providersFrom3DBeacons.includes(summary.provider)
|
|
169
|
+
);
|
|
170
|
+
return otherStructures.map(({ summary }) => ({
|
|
171
|
+
id: summary['model_identifier'],
|
|
172
|
+
source: summary.provider,
|
|
173
|
+
positions: `${summary['uniprot_start']}-${summary['uniprot_end']}`,
|
|
174
|
+
protvistaFeatureId: summary['model_identifier'],
|
|
175
|
+
downloadLink: summary['model_url'],
|
|
176
|
+
sourceDBLink: summary['model_page_url'],
|
|
177
|
+
}));
|
|
178
|
+
};
|
|
179
|
+
|
|
82
180
|
const AFMetaInfo = html`
|
|
83
181
|
<strong>Model Confidence:</strong>
|
|
84
182
|
<ul class="no-bullet">
|
|
@@ -151,6 +249,9 @@ class ProtvistaUniprotStructure extends LitElement {
|
|
|
151
249
|
private loading?: boolean;
|
|
152
250
|
private alphamissenseAvailable?: boolean;
|
|
153
251
|
|
|
252
|
+
@state()
|
|
253
|
+
private modelUrl = '';
|
|
254
|
+
|
|
154
255
|
constructor() {
|
|
155
256
|
super();
|
|
156
257
|
loadComponent('nightingale-structure', NightingaleStructure);
|
|
@@ -176,22 +277,34 @@ class ProtvistaUniprotStructure extends LitElement {
|
|
|
176
277
|
async connectedCallback() {
|
|
177
278
|
super.connectedCallback();
|
|
178
279
|
if (!this.accession) return;
|
|
179
|
-
// https://www.ebi.ac.uk/pdbe/api/mappings/best_structures/${this.accession}
|
|
180
|
-
const pdbUrl = `https://www.ebi.ac.uk/proteins/api/proteins/${this.accession}`;
|
|
181
|
-
const alphaFoldURl = `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}`;
|
|
182
280
|
|
|
183
|
-
|
|
281
|
+
// We are showing PDBe models returned by UniProt's API as there is inconsistency between UniProt's recognised ones and 3d-beacons.
|
|
282
|
+
const pdbUrl = `https://rest.uniprot.org/uniprotkb/${this.accession}`;
|
|
283
|
+
const alphaFoldUrl = `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}`;
|
|
284
|
+
// exclude_provider accepts only value hence 'pdbe' as majority of the models are from there.
|
|
285
|
+
const beaconsUrl = `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${this.accession}.json?exclude_provider=pdbe`;
|
|
184
286
|
|
|
287
|
+
const rawData = await fetchAll([pdbUrl, alphaFoldUrl, beaconsUrl]);
|
|
185
288
|
this.loading = false;
|
|
289
|
+
|
|
290
|
+
const pdbData = processPDBData(rawData[pdbUrl] || []);
|
|
291
|
+
let afData = [];
|
|
292
|
+
// Check if AF sequence matches UniProt sequence
|
|
293
|
+
if (
|
|
294
|
+
rawData[pdbUrl].sequence?.value === rawData[alphaFoldUrl][0]?.sequence
|
|
295
|
+
) {
|
|
296
|
+
afData = processAFData(rawData[alphaFoldUrl] || []);
|
|
297
|
+
}
|
|
298
|
+
const beaconsData = process3DBeaconsData(rawData[beaconsUrl] || []);
|
|
299
|
+
|
|
186
300
|
// TODO: return if no data at all
|
|
187
301
|
// if (!payload) return;
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const data = [...pdbData, ...afData];
|
|
302
|
+
|
|
303
|
+
const data = [...pdbData, ...afData, ...beaconsData];
|
|
191
304
|
if (!data || !data.length) return;
|
|
192
305
|
|
|
193
306
|
this.data = data;
|
|
194
|
-
this.alphamissenseAvailable = rawData[
|
|
307
|
+
this.alphamissenseAvailable = rawData[alphaFoldUrl].some(
|
|
195
308
|
(data) => data.amAnnotationsUrl
|
|
196
309
|
);
|
|
197
310
|
}
|
|
@@ -233,12 +346,25 @@ class ProtvistaUniprotStructure extends LitElement {
|
|
|
233
346
|
}
|
|
234
347
|
}
|
|
235
348
|
|
|
236
|
-
onTableRowClick({
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
349
|
+
onTableRowClick({
|
|
350
|
+
id,
|
|
351
|
+
source,
|
|
352
|
+
downloadLink,
|
|
353
|
+
}: {
|
|
354
|
+
id: string;
|
|
355
|
+
source?: string;
|
|
356
|
+
downloadLink?: string;
|
|
357
|
+
}) {
|
|
358
|
+
if (providersFrom3DBeacons.includes(source)) {
|
|
359
|
+
this.modelUrl = downloadLink;
|
|
360
|
+
this.structureId = undefined;
|
|
240
361
|
} else {
|
|
241
|
-
this.
|
|
362
|
+
this.structureId = id;
|
|
363
|
+
if (this.structureId.startsWith('AF-')) {
|
|
364
|
+
this.metaInfo = AFMetaInfo;
|
|
365
|
+
} else {
|
|
366
|
+
this.metaInfo = undefined;
|
|
367
|
+
}
|
|
242
368
|
}
|
|
243
369
|
}
|
|
244
370
|
|
|
@@ -358,7 +484,10 @@ class ProtvistaUniprotStructure extends LitElement {
|
|
|
358
484
|
protein-accession=${this.accession}
|
|
359
485
|
color-theme=${this.colorTheme}
|
|
360
486
|
></nightingale-structure>`
|
|
361
|
-
: html
|
|
487
|
+
: html`<nightingale-structure
|
|
488
|
+
model-url=${this.modelUrl}
|
|
489
|
+
color-theme=${this.colorTheme}
|
|
490
|
+
></nightingale-structure>`}
|
|
362
491
|
</div>
|
|
363
492
|
<div class="protvista-uniprot-structure__table">
|
|
364
493
|
${this.data && this.data.length
|
|
@@ -386,9 +515,11 @@ class ProtvistaUniprotStructure extends LitElement {
|
|
|
386
515
|
chain,
|
|
387
516
|
positions,
|
|
388
517
|
downloadLink,
|
|
518
|
+
sourceDBLink,
|
|
389
519
|
}) => html`<tr
|
|
390
520
|
data-id="${id}"
|
|
391
|
-
@click="${() =>
|
|
521
|
+
@click="${() =>
|
|
522
|
+
this.onTableRowClick({ id, source, downloadLink })}"
|
|
392
523
|
>
|
|
393
524
|
<td data-filter="source" data-filter-value="${source}">
|
|
394
525
|
<strong>${source}</strong>
|
|
@@ -415,23 +546,31 @@ class ProtvistaUniprotStructure extends LitElement {
|
|
|
415
546
|
(prev, curr) => html` ${prev} · ${curr} `
|
|
416
547
|
)}
|
|
417
548
|
`
|
|
418
|
-
:
|
|
549
|
+
: ``}
|
|
550
|
+
${source === 'AlphaFold'
|
|
551
|
+
? html`<a href="${alphaFoldLink}${this.accession}"
|
|
419
552
|
>AlphaFold</a
|
|
420
|
-
>`
|
|
553
|
+
>`
|
|
554
|
+
: ``}
|
|
555
|
+
${sourceDBLink
|
|
556
|
+
? html`<a href="${sourceDBLink}">${source}</a>`
|
|
557
|
+
: ``}
|
|
421
558
|
</td>
|
|
422
559
|
<td>
|
|
423
560
|
${downloadLink
|
|
424
561
|
? html`<a
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
·
|
|
430
|
-
${foldseekURL(
|
|
431
|
-
source === 'PDB' ? id : this.accession,
|
|
432
|
-
source === 'PDB' ? 'PDB' : 'AlphaFoldDB'
|
|
433
|
-
)}`
|
|
562
|
+
href="${downloadLink}"
|
|
563
|
+
class="download-link"
|
|
564
|
+
>${svg`${unsafeHTML(downloadIcon)}`}</a
|
|
565
|
+
> `
|
|
434
566
|
: ''}
|
|
567
|
+
${source === 'PDB' || source === 'AlphaFold'
|
|
568
|
+
? html`·
|
|
569
|
+
${foldseekURL(
|
|
570
|
+
source === 'PDB' ? id : this.accession,
|
|
571
|
+
source === 'PDB' ? 'PDB' : 'AlphaFoldDB'
|
|
572
|
+
)}`
|
|
573
|
+
: ``}
|
|
435
574
|
</td>
|
|
436
575
|
</tr>`
|
|
437
576
|
)}
|