pdbe-molstar 3.5.0 → 3.6.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.
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /** Helper functions to allow superposition of complexes */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Coloring = void 0;
5
+ exports.loadComplexSuperposition = loadComplexSuperposition;
6
+ exports.superposeStructuresByMolstarDefault = superposeStructuresByMolstarDefault;
7
+ const tslib_1 = require("tslib");
8
+ const superposition_sifts_mapping_1 = require("molstar/lib/mol-model/structure/structure/util/superposition-sifts-mapping");
9
+ const commands_1 = require("molstar/lib/mol-plugin/commands");
10
+ const sleep_1 = require("molstar/lib/mol-util/sleep");
11
+ const __1 = require("../..");
12
+ const helpers_1 = require("../../helpers");
13
+ const superposition_1 = require("../../superposition");
14
+ const Coloring = tslib_1.__importStar(require("./coloring"));
15
+ const superpose_by_biggest_chain_1 = require("./superpose-by-biggest-chain");
16
+ exports.Coloring = tslib_1.__importStar(require("./coloring"));
17
+ /** Load a structure, superpose onto the main structure based on Uniprot residue numbers, and optionally apply coloring to show common/additional components. */
18
+ function loadComplexSuperposition(viewer, params) {
19
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
20
+ var _a, _b, _c, _d, _e, _f, _g;
21
+ const { pdbId, assemblyId, animationDuration = 250, coloring, baseComponents, otherComponents, baseMappings, otherMappings, coreColor, unmappedColor, componentColors, method = 'biggest-matched-chain' } = params;
22
+ const baseStructId = __1.PDBeMolstarPlugin.MAIN_STRUCTURE_ID;
23
+ const otherStructId = (_a = params.id) !== null && _a !== void 0 ? _a : `${pdbId}_${assemblyId}`;
24
+ // Apply coloring to base structure
25
+ if (coloring) {
26
+ if (!baseComponents)
27
+ throw new Error('`baseComponents` is required when `coloring` is not `undefined`');
28
+ if (!otherComponents)
29
+ throw new Error('`otherComponents` is required when `coloring` is not `undefined`');
30
+ }
31
+ if (coloring === 'subcomplex') {
32
+ yield Coloring.colorSubcomplex(viewer, { baseStructId, baseComponents: baseComponents, otherComponents: otherComponents, baseMappings, otherMappings, coreColor, componentColors });
33
+ }
34
+ if (coloring === 'supercomplex') {
35
+ yield Coloring.colorSupercomplex(viewer, { baseStructId, baseComponents: baseComponents, otherComponents: otherComponents, baseMappings, otherMappings, coreColor, unmappedColor, componentColors });
36
+ }
37
+ // Load other structure
38
+ const otherStructUrl = (0, helpers_1.getStructureUrl)(viewer.initParams, { pdbId, queryType: 'full' });
39
+ yield viewer.load({ url: otherStructUrl, isBinary: viewer.initParams.encoding === 'bcif', assemblyId, id: otherStructId }, false);
40
+ yield viewer.visual.structureVisibility(otherStructId, false); // hide structure until superposition complete, to avoid flickering
41
+ const baseStruct = (_c = (_b = viewer.getStructure(baseStructId)) === null || _b === void 0 ? void 0 : _b.cell.obj) === null || _c === void 0 ? void 0 : _c.data;
42
+ if (!baseStruct)
43
+ throw new Error('Static structure not loaded');
44
+ const otherStruct = (_e = (_d = viewer.getStructure(otherStructId)) === null || _d === void 0 ? void 0 : _d.cell.obj) === null || _e === void 0 ? void 0 : _e.data;
45
+ if (!otherStruct)
46
+ throw new Error('Mobile structure not loaded');
47
+ // Superpose other structure on base structure
48
+ const { status, superposition } = method === 'biggest-matched-chain' ?
49
+ (0, superpose_by_biggest_chain_1.superposeStructuresByBiggestCommonChain)(baseStruct, otherStruct, baseComponents, otherComponents)
50
+ : superposeStructuresByMolstarDefault(baseStruct, otherStruct);
51
+ if (superposition) {
52
+ yield (0, superposition_1.transform)(viewer.plugin, viewer.getStructure(otherStructId).cell, superposition.bTransform);
53
+ }
54
+ // Apply coloring to other structure
55
+ if (coloring === 'subcomplex') {
56
+ yield Coloring.colorSubcomplex(viewer, { otherStructId, baseComponents: baseComponents, otherComponents: otherComponents, baseMappings, otherMappings, coreColor, componentColors });
57
+ }
58
+ if (coloring === 'supercomplex') {
59
+ yield Coloring.colorSupercomplex(viewer, { otherStructId, baseComponents: baseComponents, otherComponents: otherComponents, baseMappings, otherMappings, coreColor, unmappedColor, componentColors });
60
+ }
61
+ // Adjust camera
62
+ const staticStructRef = viewer.getStructure(baseStructId);
63
+ const mobileStructRef = viewer.getStructure(otherStructId); // it is important to run this after superposition, to get correct coordinates for camera adjustment
64
+ // Wanted to use PluginCommands.Camera.Focus with viewer.plugin.canvas3d?.boundingSphere, but seems to not work properly, so using the following workaround:
65
+ yield commands_1.PluginCommands.Camera.FocusObject(viewer.plugin, {
66
+ targets: [
67
+ { targetRef: staticStructRef === null || staticStructRef === void 0 ? void 0 : staticStructRef.cell.transform.ref, extraRadius: 0.3 },
68
+ { targetRef: (_g = ((_f = mobileStructRef === null || mobileStructRef === void 0 ? void 0 : mobileStructRef.transform) !== null && _f !== void 0 ? _f : mobileStructRef)) === null || _g === void 0 ? void 0 : _g.cell.transform.ref, extraRadius: 0.3 },
69
+ // 0.3 is amount by which bounding sphere algorithm usually underestimates actual visible bounding sphere
70
+ ],
71
+ durationMs: animationDuration,
72
+ });
73
+ yield (0, sleep_1.sleep)(animationDuration); // wait until camera adjustment completed
74
+ // Reveal new structure
75
+ yield viewer.visual.structureVisibility(otherStructId, true);
76
+ return {
77
+ /** Structure identifier of the newly loaded complex structure, to refer to this structure later */
78
+ id: otherStructId,
79
+ /** Status of pairwise superposition ('success' / 'zero-overlap' / 'failed') */
80
+ status,
81
+ /** Superposition RMSD and tranform (if status is 'success') */
82
+ superposition,
83
+ /** Function that deletes the newly loaded complex structure */
84
+ delete: () => viewer.deleteStructure(otherStructId),
85
+ };
86
+ });
87
+ }
88
+ /** Superpose mobile structure onto static structure, based on Uniprot residue numbers. */
89
+ function superposeStructuresByMolstarDefault(staticStruct, mobileStruct) {
90
+ var _a;
91
+ const aln = (0, superposition_sifts_mapping_1.alignAndSuperposeWithSIFTSMapping)([staticStruct, mobileStruct], { traceOnly: true });
92
+ const superposition = (_a = aln.entries.find(e => e.pivot === 0 && e.other === 1)) === null || _a === void 0 ? void 0 : _a.transform;
93
+ if (superposition) {
94
+ return { status: 'success', superposition: Object.assign(Object.assign({}, superposition), { nAlignedElements: Number.NaN }) };
95
+ }
96
+ else if (aln.zeroOverlapPairs.find(e => e[0] === 0 && e[1] === 1)) {
97
+ return { status: 'zero-overlap', superposition: undefined };
98
+ }
99
+ else {
100
+ return { status: 'failed', superposition: undefined };
101
+ }
102
+ }
103
+ /*
104
+ Coloring - subcomplexes:
105
+ - base common -> by entity, lighter
106
+ - base additional -> gray, lighter
107
+ - sub common -> by entity, darker
108
+ - sub additional -> gray, darker (these are all unmapped components, includes antibodies and ligands)
109
+
110
+ -> Colors can be assigned based on base complex and applied to subcomplex
111
+
112
+ Coloring - supercomplexes:
113
+ - base common -> gray, lighter
114
+ - base additional -> unmapped color, lighter (these are all unmapped components, includes antibodies and ligands)
115
+ - super common -> gray, darker
116
+ - super additional mapped -> by entity, darker
117
+ - super additional unmapped -> unmapped color, darker
118
+
119
+ -> Colors can be assigned based on supercomplex complex, consistency between supercomplexes is probably not necessary
120
+
121
+ -> For both subcomplexes and supercomplexes, colors could be assigned based on UniprotID hash -> database-wide consistency but complexes with similar-color components will occur
122
+
123
+ */
@@ -0,0 +1,17 @@
1
+ import { MinimizeRmsd } from 'molstar/lib/mol-math/linear-algebra/3d/minimize-rmsd';
2
+ import { Structure } from 'molstar/lib/mol-model/structure';
3
+ export type SuperpositionResult = {
4
+ status: 'success';
5
+ superposition: MinimizeRmsd.Result & {
6
+ nAlignedElements: number;
7
+ };
8
+ } | {
9
+ status: 'zero-overlap';
10
+ superposition: undefined;
11
+ } | {
12
+ status: 'failed';
13
+ superposition: undefined;
14
+ };
15
+ /** Status of pairwise superposition (success = superposed, zero-overlap = failed to superpose because the two structures have no matchable elements, failed = failed to superpose for other reasons) */
16
+ export type SuperpositionStatus = SuperpositionResult['status'];
17
+ export declare function superposeStructuresByBiggestCommonChain(structA: Structure, structB: Structure, allowedComponentsA: string[] | undefined, allowedComponentsB: string[] | undefined): SuperpositionResult;
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.superposeStructuresByBiggestCommonChain = superposeStructuresByBiggestCommonChain;
4
+ const minimize_rmsd_1 = require("molstar/lib/mol-math/linear-algebra/3d/minimize-rmsd");
5
+ const mmcif_1 = require("molstar/lib/mol-model-formats/structure/mmcif");
6
+ function extractUniprotIndex(structure, allowedAccessions) {
7
+ var _a, _b, _c;
8
+ var _d, _e;
9
+ const allowedAccessionsSet = allowedAccessions ? new Set(allowedAccessions) : undefined;
10
+ const seenUnitInvariantIds = new Set();
11
+ const out = {};
12
+ for (const unit of structure.units) {
13
+ if (seenUnitInvariantIds.has(unit.invariantId))
14
+ continue;
15
+ else
16
+ seenUnitInvariantIds.add(unit.invariantId);
17
+ const src = structure.model.sourceData;
18
+ if (!mmcif_1.MmcifFormat.is(src))
19
+ throw new Error('Source data must be mmCIF/BCIF');
20
+ const h = unit.model.atomicHierarchy;
21
+ const { pdbx_sifts_xref_db_acc, pdbx_sifts_xref_db_name, pdbx_sifts_xref_db_num } = src.data.db.atom_site;
22
+ const atoms = unit.polymerElements;
23
+ const nAtoms = atoms.length;
24
+ for (let i = 0; i < nAtoms; i++) {
25
+ const iAtom = atoms[i];
26
+ const srcIAtom = h.atomSourceIndex.value(iAtom);
27
+ const dbName = pdbx_sifts_xref_db_name.value(srcIAtom);
28
+ if (dbName !== 'UNP')
29
+ continue;
30
+ const dbAcc = pdbx_sifts_xref_db_acc.value(srcIAtom);
31
+ if (allowedAccessionsSet && !allowedAccessionsSet.has(dbAcc))
32
+ continue;
33
+ const dbNum = pdbx_sifts_xref_db_num.value(srcIAtom);
34
+ const structMapping = (_a = out[dbAcc]) !== null && _a !== void 0 ? _a : (out[dbAcc] = {});
35
+ const chainMapping = (_b = structMapping[_d = unit.id]) !== null && _b !== void 0 ? _b : (structMapping[_d] = {
36
+ label_asym_id: h.chains.label_asym_id.value(h.chainAtomSegments.index[atoms[0]]),
37
+ auth_asym_id: h.chains.auth_asym_id.value(h.chainAtomSegments.index[atoms[0]]),
38
+ atomMap: {},
39
+ });
40
+ (_c = (_e = chainMapping.atomMap)[dbNum]) !== null && _c !== void 0 ? _c : (_e[dbNum] = iAtom);
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+ function bestUniprotMatch(a, b) {
46
+ const sortedA = sortAccessionAndChains(a);
47
+ const sortedB = sortAccessionAndChains(b);
48
+ let bestMatch = undefined;
49
+ let bestScore = 0;
50
+ for (const accession of sortedA.accessions) {
51
+ const unitsA = sortedA.units[accession];
52
+ const unitsB = sortedB.units[accession];
53
+ if (!unitsB)
54
+ continue;
55
+ for (const ua of unitsA) {
56
+ if (ua.size <= bestScore)
57
+ break;
58
+ const unitA = a[accession][ua.unitId];
59
+ for (const ub of unitsB) {
60
+ if (ub.size <= bestScore || ua.size <= bestScore)
61
+ break;
62
+ const unitB = b[accession][ub.unitId];
63
+ const score = objectKeyOverlap(unitA.atomMap, unitB.atomMap);
64
+ if (score > bestScore) {
65
+ bestScore = score;
66
+ bestMatch = { accession, unitA: ua.unitId, unitB: ub.unitId, nMatchedElements: score };
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return bestMatch;
72
+ }
73
+ /** Sort units for each accession by decreasing size and sort accessions by decreasing biggest unit size. */
74
+ function sortAccessionAndChains(uniprotIndex) {
75
+ const unitsByAccession = {};
76
+ for (const accession in uniprotIndex) {
77
+ const unitIds = uniprotIndex[accession];
78
+ const units = [];
79
+ for (const unitId in unitIds) {
80
+ const size = Object.keys(unitIds[unitId].atomMap).length;
81
+ units.push({ unitId, size });
82
+ }
83
+ units.sort((a, b) => b.size - a.size);
84
+ unitsByAccession[accession] = units;
85
+ }
86
+ return {
87
+ /** Accessions sorted by decreasing biggest unit size */
88
+ accessions: Object.keys(unitsByAccession).sort((a, b) => unitsByAccession[b][0].size - unitsByAccession[a][0].size),
89
+ /** Units per accession, sorted by decreasing unit size */
90
+ units: unitsByAccession,
91
+ };
92
+ }
93
+ /** Return number of keys common to objects `a` and `b` */
94
+ function objectKeyOverlap(a, b) {
95
+ let overlap = 0;
96
+ for (const key in a) {
97
+ if (key in b) {
98
+ overlap++;
99
+ }
100
+ }
101
+ return overlap;
102
+ }
103
+ function superposeStructuresByBiggestCommonChain(structA, structB, allowedComponentsA, allowedComponentsB) {
104
+ const indexA = extractUniprotIndex(structA, allowedComponentsA);
105
+ const indexB = extractUniprotIndex(structB, allowedComponentsB);
106
+ const bestMatch = bestUniprotMatch(indexA, indexB);
107
+ if (!bestMatch) {
108
+ return { status: 'zero-overlap', superposition: undefined };
109
+ }
110
+ const unitA = structA.unitMap.get(Number(bestMatch.unitA));
111
+ const unitB = structB.unitMap.get(Number(bestMatch.unitB));
112
+ const unitIndexA = indexA[bestMatch.accession][bestMatch.unitA];
113
+ const unitIndexB = indexB[bestMatch.accession][bestMatch.unitB];
114
+ const positionsA = minimize_rmsd_1.MinimizeRmsd.Positions.empty(bestMatch.nMatchedElements);
115
+ const positionsB = minimize_rmsd_1.MinimizeRmsd.Positions.empty(bestMatch.nMatchedElements);
116
+ let i = 0;
117
+ for (const unpNum in unitIndexA.atomMap) {
118
+ const iAtomB = unitIndexB.atomMap[unpNum];
119
+ if (iAtomB === undefined)
120
+ continue;
121
+ const iAtomA = unitIndexA.atomMap[unpNum];
122
+ positionsA.x[i] = unitA.conformation.coordinates.x[iAtomA];
123
+ positionsA.y[i] = unitA.conformation.coordinates.y[iAtomA];
124
+ positionsA.z[i] = unitA.conformation.coordinates.z[iAtomA];
125
+ positionsB.x[i] = unitB.conformation.coordinates.x[iAtomB];
126
+ positionsB.y[i] = unitB.conformation.coordinates.y[iAtomB];
127
+ positionsB.z[i] = unitB.conformation.coordinates.z[iAtomB];
128
+ i++;
129
+ }
130
+ const superposition = minimize_rmsd_1.MinimizeRmsd.compute({ a: positionsA, b: positionsB });
131
+ if (isNaN(superposition.rmsd)) {
132
+ return { status: 'failed', superposition: undefined };
133
+ }
134
+ return { status: 'success', superposition: Object.assign(Object.assign({}, superposition), { nAlignedElements: bestMatch.nMatchedElements }) };
135
+ }
package/lib/helpers.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { ComponentExpressionT } from 'molstar/lib/extensions/mvs/tree/mvs/param-types';
2
+ import { Mat3, Mat4 } from 'molstar/lib/mol-math/linear-algebra';
2
3
  import { Model, Structure } from 'molstar/lib/mol-model/structure';
3
4
  import { BuiltInTrajectoryFormat } from 'molstar/lib/mol-plugin-state/formats/trajectory';
4
5
  import { StructureRef } from 'molstar/lib/mol-plugin-state/manager/structure/hierarchy-state';
5
6
  import { PluginConfigItem } from 'molstar/lib/mol-plugin/config';
6
7
  import { PluginContext } from 'molstar/lib/mol-plugin/context';
8
+ import { PluginLayoutStateProps } from 'molstar/lib/mol-plugin/layout';
7
9
  import { Expression } from 'molstar/lib/mol-script/language/expression';
8
10
  import { Overpaint } from 'molstar/lib/mol-theme/overpaint';
9
11
  import { Color } from 'molstar/lib/mol-util/color';
@@ -192,4 +194,6 @@ export declare namespace PluginConfigUtils {
192
194
  /** Retrieve config values for items in `configItems` from the current plugin config */
193
195
  function getConfigValues<T extends object>(plugin: PluginContext | undefined, configItems: ConfigFor<T>, defaults: T): T;
194
196
  }
197
+ export declare function pluginLayoutStateFromInitParams(initParams: InitParams): PluginLayoutStateProps;
198
+ export declare function getRotationMat4(view: 'front' | 'back' | 'right' | 'left' | 'top' | 'bottom' | Mat3): Mat4;
195
199
  export {};
package/lib/helpers.js CHANGED
@@ -13,13 +13,17 @@ exports.distinct = distinct;
13
13
  exports.groupElements = groupElements;
14
14
  exports.createIndex = createIndex;
15
15
  exports.nonnegativeModulo = nonnegativeModulo;
16
+ exports.pluginLayoutStateFromInitParams = pluginLayoutStateFromInitParams;
17
+ exports.getRotationMat4 = getRotationMat4;
16
18
  const tslib_1 = require("tslib");
17
19
  const prop_1 = require("molstar/lib/extensions/model-archive/quality-assessment/prop");
20
+ const linear_algebra_1 = require("molstar/lib/mol-math/linear-algebra");
18
21
  const structure_1 = require("molstar/lib/mol-model/structure");
19
22
  const query_1 = require("molstar/lib/mol-model/structure/query/query");
20
23
  const transforms_1 = require("molstar/lib/mol-plugin-state/transforms");
21
24
  const transformers_1 = require("molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/transformers");
22
25
  const commands_1 = require("molstar/lib/mol-plugin/commands");
26
+ const layout_1 = require("molstar/lib/mol-plugin/layout");
23
27
  const builder_1 = require("molstar/lib/mol-script/language/builder");
24
28
  const compiler_1 = require("molstar/lib/mol-script/runtime/query/compiler");
25
29
  const mol_state_1 = require("molstar/lib/mol-state");
@@ -197,92 +201,105 @@ var QueryHelper;
197
201
  (function (QueryHelper) {
198
202
  function getQueryObject(params, contextData) {
199
203
  const selections = [];
200
- let siftMappings;
201
- let currentAccession;
202
- params.forEach(param => {
203
- const selection = {};
204
+ /** `undefined` means SIFTSMappingMapping has not been retrieved yet. `null` means SIFTSMappingMapping has been retrieved unsuccessfully. */
205
+ let _siftMappings;
206
+ function getSiftsMappings() {
207
+ var _a;
208
+ if (_siftMappings === undefined)
209
+ _siftMappings = (_a = sifts_mapping_1.SIFTSMapping.Provider.get(contextData.models[0]).value) !== null && _a !== void 0 ? _a : null;
210
+ return _siftMappings;
211
+ }
212
+ for (const param of params) {
213
+ const predicates = {
214
+ entity: [],
215
+ chain: [],
216
+ residue: [],
217
+ atom: [],
218
+ };
204
219
  // entity
205
- if (param.entity_id)
206
- selection['entityTest'] = l => structure_1.StructureProperties.entity.id(l.element) === param.entity_id;
220
+ if (param.entity_id !== undefined) {
221
+ predicates.entity.push(l => structure_1.StructureProperties.entity.id(l.element) === param.entity_id);
222
+ }
207
223
  // chain
208
- if (param.struct_asym_id) {
209
- selection['chainTest'] = l => structure_1.StructureProperties.chain.label_asym_id(l.element) === param.struct_asym_id;
224
+ if (param.struct_asym_id !== undefined) {
225
+ predicates.chain.push(l => structure_1.StructureProperties.chain.label_asym_id(l.element) === param.struct_asym_id);
210
226
  }
211
- else if (param.auth_asym_id) {
212
- selection['chainTest'] = l => structure_1.StructureProperties.chain.auth_asym_id(l.element) === param.auth_asym_id;
227
+ if (param.auth_asym_id !== undefined) {
228
+ predicates.chain.push(l => structure_1.StructureProperties.chain.auth_asym_id(l.element) === param.auth_asym_id);
213
229
  }
214
- // residues
215
- // TODO implement AND-logic here (now { residue_number: 50, label_comp_id: 'PHE' } selects all PHE)
216
- if (param.label_comp_id) {
217
- selection['residueTest'] = l => structure_1.StructureProperties.atom.label_comp_id(l.element) === param.label_comp_id;
230
+ // residue
231
+ if (param.label_comp_id !== undefined) {
232
+ predicates.residue.push(l => structure_1.StructureProperties.atom.label_comp_id(l.element) === param.label_comp_id);
218
233
  }
219
- else if (param.uniprot_accession && param.uniprot_residue_number !== undefined) {
220
- selection['residueTest'] = l => {
221
- if (!siftMappings || currentAccession !== param.uniprot_accession) {
222
- siftMappings = sifts_mapping_1.SIFTSMapping.Provider.get(contextData.models[0]).value;
223
- currentAccession = param.uniprot_accession;
224
- }
225
- const rI = structure_1.StructureProperties.residue.key(l.element);
226
- return !!siftMappings && param.uniprot_accession === siftMappings.accession[rI] && param.uniprot_residue_number === +siftMappings.num[rI];
227
- };
234
+ if (param.residue_number !== undefined) {
235
+ predicates.residue.push(l => structure_1.StructureProperties.residue.label_seq_id(l.element) === param.residue_number);
228
236
  }
229
- else if (param.uniprot_accession && param.start_uniprot_residue_number !== undefined && param.end_uniprot_residue_number !== undefined) {
230
- selection['residueTest'] = l => {
231
- if (!siftMappings || currentAccession !== param.uniprot_accession) {
232
- siftMappings = sifts_mapping_1.SIFTSMapping.Provider.get(contextData.models[0]).value;
233
- currentAccession = param.uniprot_accession;
234
- }
235
- const rI = structure_1.StructureProperties.residue.key(l.element);
236
- return !!siftMappings && param.uniprot_accession === siftMappings.accession[rI] && (param.start_uniprot_residue_number <= +siftMappings.num[rI] && param.end_uniprot_residue_number >= +siftMappings.num[rI]);
237
- };
237
+ if (param.start_residue_number !== undefined) {
238
+ predicates.residue.push(l => structure_1.StructureProperties.residue.label_seq_id(l.element) >= param.start_residue_number);
238
239
  }
239
- else if (param.residue_number !== undefined) {
240
- selection['residueTest'] = l => structure_1.StructureProperties.residue.label_seq_id(l.element) === param.residue_number;
240
+ if (param.end_residue_number !== undefined) {
241
+ predicates.residue.push(l => structure_1.StructureProperties.residue.label_seq_id(l.element) <= param.end_residue_number);
241
242
  }
242
- else if (param.start_residue_number !== undefined && param.end_residue_number !== undefined && param.end_residue_number > param.start_residue_number) {
243
- selection['residueTest'] = l => {
244
- const labelSeqId = structure_1.StructureProperties.residue.label_seq_id(l.element);
245
- return labelSeqId >= param.start_residue_number && labelSeqId <= param.end_residue_number;
246
- };
243
+ if (param.auth_seq_id !== undefined) {
244
+ predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.auth_seq_id);
247
245
  }
248
- else if (param.start_residue_number !== undefined && param.end_residue_number !== undefined && param.end_residue_number === param.start_residue_number) {
249
- selection['residueTest'] = l => structure_1.StructureProperties.residue.label_seq_id(l.element) === param.start_residue_number;
246
+ if (param.auth_residue_number !== undefined) {
247
+ predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.auth_residue_number);
250
248
  }
251
- else if (param.auth_seq_id !== undefined) {
252
- selection['residueTest'] = l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.auth_seq_id;
249
+ if (param.auth_ins_code_id !== undefined) {
250
+ predicates.residue.push(l => structure_1.StructureProperties.residue.pdbx_PDB_ins_code(l.element) === param.auth_ins_code_id);
253
251
  }
254
- else if (param.auth_residue_number !== undefined && !param.auth_ins_code_id) {
255
- selection['residueTest'] = l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.auth_residue_number;
252
+ if (param.start_auth_residue_number !== undefined) {
253
+ predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) >= param.start_auth_residue_number);
254
+ if (param.start_auth_ins_code_id !== undefined) {
255
+ // This assumes insertion code come in alphabetical order, which is not always true (e.g. 1ucy chain A [auth L]). However, authors of such monstrosities do not deserve their structures to be displayed correctly.
256
+ predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) > param.start_auth_residue_number || structure_1.StructureProperties.residue.pdbx_PDB_ins_code(l.element) >= param.start_auth_ins_code_id);
257
+ }
258
+ }
259
+ if (param.end_auth_residue_number !== undefined) {
260
+ predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) <= param.end_auth_residue_number);
261
+ if (param.end_auth_ins_code_id !== undefined) {
262
+ // This assumes insertion code come in alphabetical order, which is not always true (e.g. 1ucy chain A [auth L]). However, authors of such monstrosities do not deserve their structures to be displayed correctly.
263
+ predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) < param.end_auth_residue_number || structure_1.StructureProperties.residue.pdbx_PDB_ins_code(l.element) <= param.end_auth_ins_code_id);
264
+ }
256
265
  }
257
- else if (param.auth_residue_number !== undefined && param.auth_ins_code_id) {
258
- selection['residueTest'] = l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.auth_residue_number;
259
- // TODO really check ins code, same for auth_seq_id, start_auth_ins_code_id, end_auth_ins_code_id
266
+ if (param.uniprot_accession !== undefined) {
267
+ predicates.residue.push(l => { var _a; return ((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.accession[structure_1.StructureProperties.residue.key(l.element)]) === param.uniprot_accession; });
260
268
  }
261
- else if (param.start_auth_residue_number !== undefined && param.end_auth_residue_number !== undefined && param.end_auth_residue_number > param.start_auth_residue_number) {
262
- selection['residueTest'] = l => {
263
- const authSeqId = structure_1.StructureProperties.residue.auth_seq_id(l.element);
264
- return authSeqId >= param.start_auth_residue_number && authSeqId <= param.end_auth_residue_number;
265
- };
269
+ if (param.uniprot_residue_number !== undefined) {
270
+ predicates.residue.push(l => { var _a; return Number((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.num[structure_1.StructureProperties.residue.key(l.element)]) === param.uniprot_residue_number; });
266
271
  }
267
- else if (param.start_auth_residue_number !== undefined && param.end_auth_residue_number !== undefined && param.end_auth_residue_number === param.start_auth_residue_number) {
268
- selection['residueTest'] = l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.start_auth_residue_number;
272
+ if (param.start_uniprot_residue_number !== undefined) {
273
+ predicates.residue.push(l => { var _a; return Number((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.num[structure_1.StructureProperties.residue.key(l.element)]) >= param.start_uniprot_residue_number; });
269
274
  }
270
- // atoms
275
+ if (param.end_uniprot_residue_number !== undefined) {
276
+ predicates.residue.push(l => { var _a; return Number((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.num[structure_1.StructureProperties.residue.key(l.element)]) <= param.end_uniprot_residue_number; });
277
+ }
278
+ // atom
271
279
  if (param.atoms) {
272
- selection['atomTest'] = l => param.atoms.includes(structure_1.StructureProperties.atom.label_atom_id(l.element));
280
+ predicates.atom.push(l => param.atoms.includes(structure_1.StructureProperties.atom.label_atom_id(l.element)));
273
281
  }
274
282
  if (param.atom_id) {
275
- selection['atomTest'] = l => param.atom_id.includes(structure_1.StructureProperties.atom.id(l.element));
283
+ predicates.atom.push(l => param.atom_id.includes(structure_1.StructureProperties.atom.id(l.element)));
276
284
  }
277
- selections.push(selection);
278
- });
279
- const atmGroupsQueries = [];
280
- selections.forEach(selection => {
281
- atmGroupsQueries.push(structure_1.Queries.generators.atoms(selection));
282
- });
285
+ selections.push({
286
+ entityTest: predicateConjunction(predicates.entity),
287
+ chainTest: predicateConjunction(predicates.chain),
288
+ residueTest: predicateConjunction(predicates.residue),
289
+ atomTest: predicateConjunction(predicates.atom),
290
+ });
291
+ }
292
+ const atmGroupsQueries = selections.map(selection => structure_1.Queries.generators.atoms(selection));
283
293
  return structure_1.Queries.combinators.merge(atmGroupsQueries);
284
294
  }
285
295
  QueryHelper.getQueryObject = getQueryObject;
296
+ function predicateConjunction(predicates) {
297
+ if (predicates.length === 0)
298
+ return undefined;
299
+ if (predicates.length === 1)
300
+ return predicates[0];
301
+ return (x => predicates.every(pred => pred(x)));
302
+ }
286
303
  function getInteractivityLoci(params, contextData) {
287
304
  const sel = query_1.StructureQuery.run(QueryHelper.getQueryObject(params, contextData), contextData);
288
305
  return structure_1.StructureSelection.toLociWithSourceUnits(sel);
@@ -570,3 +587,31 @@ var PluginConfigUtils;
570
587
  }
571
588
  PluginConfigUtils.getConfigValues = getConfigValues;
572
589
  })(PluginConfigUtils || (exports.PluginConfigUtils = PluginConfigUtils = {}));
590
+ function pluginLayoutStateFromInitParams(initParams) {
591
+ return {
592
+ isExpanded: initParams.expanded,
593
+ showControls: !initParams.hideControls,
594
+ regionState: {
595
+ left: initParams.leftPanel ? 'full' : 'hidden',
596
+ right: initParams.rightPanel ? 'full' : 'hidden',
597
+ top: initParams.sequencePanel ? 'full' : 'hidden',
598
+ bottom: initParams.logPanel ? 'full' : 'hidden',
599
+ },
600
+ controlsDisplay: initParams.reactive ? 'reactive' : initParams.landscape ? 'landscape' : layout_1.PluginLayoutStateParams.controlsDisplay.defaultValue,
601
+ };
602
+ }
603
+ function getRotationMat4(view) {
604
+ switch (view) {
605
+ case 'front': return linear_algebra_1.Mat4.identity();
606
+ case 'back': return [-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1];
607
+ case 'right': return [0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1];
608
+ case 'left': return [0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1];
609
+ case 'top': return [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1];
610
+ case 'bottom': return [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1];
611
+ default: {
612
+ const out = linear_algebra_1.Mat4.fromMat3((0, linear_algebra_1.Mat4)(), view);
613
+ out[15] = 1;
614
+ return out;
615
+ }
616
+ }
617
+ }
package/lib/spec.d.ts CHANGED
@@ -123,7 +123,7 @@ export interface InitParams {
123
123
  /** Hide all control panels by default (can be shown by the Toggle Controls Panel button (wrench icon)) */
124
124
  hideControls: boolean;
125
125
  /** Hide individual icon buttons in the top-right corner of the canvas */
126
- hideCanvasControls: ('expand' | 'controlToggle' | 'controlInfo' | 'selection' | 'animation' | 'trajectory')[];
126
+ hideCanvasControls: ('screenshot' | 'expand' | 'controlToggle' | 'controlInfo' | 'selection' | 'animation' | 'trajectory')[];
127
127
  /** Display Sequence panel */
128
128
  sequencePanel: boolean;
129
129
  /** Display Left control panel */
@@ -3,9 +3,9 @@
3
3
  // Color constants must be imported before importing this file
4
4
  // (from molstar/lib/mol-plugin-ui/skin/*.scss or molstar/lib/mol-plugin-ui/skin/colors/*.scss)
5
5
 
6
- .msp-plugin {
7
- @import 'molstar/lib/mol-plugin-ui/skin/base/variables.scss';
6
+ @use 'molstar/lib/mol-plugin-ui/skin/base/_vars.scss' as vars;
8
7
 
8
+ .msp-plugin {
9
9
  ::-webkit-scrollbar-thumb {
10
10
  background-color: #80808080;
11
11
  border-radius: 10px;
@@ -22,7 +22,7 @@
22
22
  }
23
23
 
24
24
  .msp-viewport-controls-panel .msp-viewport-controls-panel-controls {
25
- max-height: calc(100cqh - 41px - 24px - 10px); // 100cqh = viewport container, 41px = control buttons offset, 24px = header, 10px = space below
25
+ max-height: calc(100cqh - 46px - 24px - 10px); // 100cqh = viewport container, 46px = control buttons offset, 24px = header, 10px = space below
26
26
  }
27
27
 
28
28
  // Avoid wraping headers in left panel help
@@ -43,7 +43,7 @@
43
43
  float: left;
44
44
 
45
45
  &:not(:empty) {
46
- margin-right: $control-spacing;
46
+ margin-right: vars.$control-spacing;
47
47
  }
48
48
  }
49
49
 
@@ -52,11 +52,11 @@
52
52
  display: flex;
53
53
  flex-direction: column;
54
54
  align-items: center;
55
- padding-inline: calc(54px + 2 * $control-spacing); // 54px = PDBe logo box
55
+ padding-inline: calc(54px + 2 * vars.$control-spacing); // 54px = PDBe logo box
56
56
  pointer-events: none;
57
57
 
58
58
  >* {
59
- margin-top: $control-spacing;
59
+ margin-top: vars.$control-spacing;
60
60
  pointer-events: auto;
61
61
  max-width: 100%;
62
62
  }
@@ -145,7 +145,7 @@
145
145
  .pdbemolstar-state-gallery-title-box {
146
146
  width: 500px;
147
147
  max-width: 100%;
148
- background-color: $msp-form-control-background;
148
+ background-color: vars.$msp-form-control-background;
149
149
  display: flex;
150
150
  flex-direction: row;
151
151
  justify-content: space-between;
@@ -1,2 +1,2 @@
1
- @import 'molstar/lib/mol-plugin-ui/skin/dark';
2
- @import './pdbe-molstar/_index';
1
+ @use 'molstar/lib/mol-plugin-ui/skin/dark';
2
+ @use './pdbe-molstar/_index';
@@ -1,2 +1,2 @@
1
- @import 'molstar/lib/mol-plugin-ui/skin/light';
2
- @import './pdbe-molstar/_index';
1
+ @use 'molstar/lib/mol-plugin-ui/skin/light';
2
+ @use './pdbe-molstar/_index';
@@ -17,9 +17,8 @@ class PDBeViewportControls extends viewport_1.ViewportControls {
17
17
  const showPDBeLink = (initParams === null || initParams === void 0 ? void 0 : initParams.moleculeId) && (initParams === null || initParams === void 0 ? void 0 : initParams.pdbeLink) && !(initParams === null || initParams === void 0 ? void 0 : initParams.superposition);
18
18
  const pdbeLinkColor = this.isBlack() ? '#fff' : '#555';
19
19
  const pdbeLink = {
20
- parentStyle: { width: 'auto' },
21
- bgStyle: { position: 'absolute', height: '27px', width: '54px', marginLeft: '-33px' },
22
- containerStyle: { position: 'absolute', right: '10px', top: '10px', padding: '3px 3px 3px 18px' },
20
+ containerStyle: { position: 'absolute', right: '10px', top: '10px', padding: '6px', paddingRight: '3px', paddingLeft: '18px' },
21
+ bgStyle: { position: 'absolute', height: '32px', width: '54px', marginLeft: '-33px' },
23
22
  style: { display: 'inline-block', fontSize: '14px', color: pdbeLinkColor, borderBottom: 'none', cursor: 'pointer', textDecoration: 'none', position: 'absolute', right: '5px' },
24
23
  pdbeImg: {
25
24
  src: 'https://www.ebi.ac.uk/pdbe/entry/static/images/logos/PDBe/logo_T_64.png',
@@ -27,7 +26,7 @@ class PDBeViewportControls extends viewport_1.ViewportControls {
27
26
  style: { height: '12px', width: '12px', border: 0, position: 'absolute', margin: '4px 0 0 -13px' },
28
27
  },
29
28
  };
30
- return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [showPDBeLink && (0, jsx_runtime_1.jsxs)("div", { className: 'msp-viewport-controls-buttons', style: pdbeLink.containerStyle, children: [(0, jsx_runtime_1.jsx)("div", { className: 'msp-semi-transparent-background', style: pdbeLink.bgStyle }), (0, jsx_runtime_1.jsxs)("a", { className: 'msp-pdbe-link', style: pdbeLink.style, target: "_blank", href: `https://pdbe.org/${initParams.moleculeId}`, children: [(0, jsx_runtime_1.jsx)("img", { src: pdbeLink.pdbeImg.src, alt: pdbeLink.pdbeImg.alt, style: pdbeLink.pdbeImg.style }), initParams.moleculeId] })] }), (0, jsx_runtime_1.jsx)("div", { style: { position: 'absolute', top: showPDBeLink ? (27 + 4) : 0, right: 0 }, children: super.render() })] });
29
+ return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [showPDBeLink && (0, jsx_runtime_1.jsxs)("div", { className: 'msp-viewport-controls-buttons', style: pdbeLink.containerStyle, children: [(0, jsx_runtime_1.jsx)("div", { className: 'msp-semi-transparent-background', style: pdbeLink.bgStyle }), (0, jsx_runtime_1.jsxs)("a", { className: 'msp-pdbe-link', style: pdbeLink.style, target: "_blank", href: `https://pdbe.org/${initParams.moleculeId}`, children: [(0, jsx_runtime_1.jsx)("img", { src: pdbeLink.pdbeImg.src, alt: pdbeLink.pdbeImg.alt, style: pdbeLink.pdbeImg.style }), initParams.moleculeId] })] }), (0, jsx_runtime_1.jsx)("div", { style: { position: 'absolute', top: showPDBeLink ? (32 + 4) : 0, right: 0 }, children: super.render() })] });
31
30
  }
32
31
  }
33
32
  exports.PDBeViewportControls = PDBeViewportControls;
@@ -1,6 +1,10 @@
1
1
  import { DefaultViewport } from 'molstar/lib/mol-plugin-ui/plugin';
2
+ import { ComponentClass } from 'react';
2
3
  /** A modified copy of DefaultViewport */
3
4
  export declare class CustomizableDefaultViewport extends DefaultViewport {
4
5
  render(): import("react/jsx-runtime").JSX.Element;
5
6
  }
6
- export declare const PDBeViewport: import("react").ComponentClass<{}, any>;
7
+ /** Version of `PDBeViewport` to use as part of other components. Does not expand to fullscreen individually. */
8
+ export declare const PDBeViewport_NoFullscreen: ComponentClass<{}, any>;
9
+ /** Component containing 3D canvas, button in top left and top right corners, and tooltip box (center panel in default layout). Changes to fullscreen view by "Toggle Expanded Viewport" button, or "expanded" option. */
10
+ export declare const PDBeViewport: ComponentClass<{}, any>;