UNPKG

molstar

Version:

A comprehensive macromolecular library.

382 lines (381 loc) 20.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SequenceView = void 0; exports.opKey = opKey; exports.splitModelEntityId = splitModelEntityId; exports.getSequenceWrapper = getSequenceWrapper; exports.getModelEntityOptions = getModelEntityOptions; exports.getChainOptions = getChainOptions; exports.getOperatorOptions = getOperatorOptions; exports.getStructureOptions = getStructureOptions; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); /** * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> * @author Ventura Rivera <venturaxrivera@gmail.com> */ const React = tslib_1.__importStar(require("react")); const base_1 = require("./base"); const objects_1 = require("../mol-plugin-state/objects"); const sequence_1 = require("./sequence/sequence"); const structure_1 = require("../mol-model/structure"); const polymer_1 = require("./sequence/polymer"); const marker_action_1 = require("../mol-util/marker-action"); const parameters_1 = require("./controls/parameters"); const param_definition_1 = require("../mol-util/param-definition"); const hetero_1 = require("./sequence/hetero"); const mol_state_1 = require("../mol-state"); const chain_1 = require("./sequence/chain"); const element_1 = require("./sequence/element"); const label_1 = require("../mol-theme/label"); const icons_1 = require("./controls/icons"); const array_1 = require("../mol-util/array"); const MaxDisplaySequenceLength = 5000; // TODO: add virtualized Select controls (at best with a search box)? const MaxSelectOptionsCount = 1000; const MaxSequenceWrappersCount = 30; function opKey(l) { const ids = structure_1.StructureProperties.unit.pdbx_struct_oper_list_ids(l); const ncs = structure_1.StructureProperties.unit.struct_ncs_oper_id(l); const hkl = structure_1.StructureProperties.unit.hkl(l); const spgrOp = structure_1.StructureProperties.unit.spgrOp(l); return `${ids.sort().join(',')}|${ncs}|${hkl}|${spgrOp}`; } function splitModelEntityId(modelEntityId) { const [modelIdx, entityId] = modelEntityId.split('|'); return [parseInt(modelIdx), entityId]; } function getSequenceWrapper(state, structureSelection) { const { structure, modelEntityId, chainGroupId, operatorKey } = state; const l = structure_1.StructureElement.Location.create(structure); const [modelIdx, entityId] = splitModelEntityId(modelEntityId); const units = []; for (const unit of structure.units) { structure_1.StructureElement.Location.set(l, structure, unit, unit.elements[0]); if (structure.getModelIndex(unit.model) !== modelIdx) continue; if (structure_1.StructureProperties.entity.id(l) !== entityId) continue; if (unit.chainGroupId !== chainGroupId) continue; if (opKey(l) !== operatorKey) continue; units.push(unit); } if (units.length > 0) { const data = { structure, units }; const unit = units[0]; let sw; if (unit.polymerElements.length) { const l = structure_1.StructureElement.Location.create(structure, unit, unit.elements[0]); const entitySeq = unit.model.sequence.byEntityKey[structure_1.StructureProperties.entity.key(l)]; // check if entity sequence is available if (entitySeq && entitySeq.sequence.length <= MaxDisplaySequenceLength) { sw = new polymer_1.PolymerSequenceWrapper(data); } else { const polymerElementCount = units.reduce((a, v) => a + v.polymerElements.length, 0); if (structure_1.Unit.isAtomic(unit) || polymerElementCount > MaxDisplaySequenceLength) { sw = new chain_1.ChainSequenceWrapper(data); } else { sw = new element_1.ElementSequenceWrapper(data); } } } else if (structure_1.Unit.isAtomic(unit)) { const residueCount = units.reduce((a, v) => a + v.residueCount, 0); if (residueCount > MaxDisplaySequenceLength) { sw = new chain_1.ChainSequenceWrapper(data); } else { sw = new hetero_1.HeteroSequenceWrapper(data); } } else { console.warn('should not happen, expecting coarse units to be polymeric'); sw = new chain_1.ChainSequenceWrapper(data); } sw.markResidue(structureSelection.getLoci(structure), marker_action_1.MarkerAction.Select); return sw; } else { return 'No sequence available'; } } function getModelEntityOptions(structure, polymersOnly = false) { const options = []; const l = structure_1.StructureElement.Location.create(structure); const seen = new Set(); for (const unit of structure.units) { structure_1.StructureElement.Location.set(l, structure, unit, unit.elements[0]); const id = structure_1.StructureProperties.entity.id(l); const modelIdx = structure.getModelIndex(unit.model); const key = `${modelIdx}|${id}`; if (seen.has(key)) continue; if (polymersOnly && structure_1.StructureProperties.entity.type(l) !== 'polymer') continue; let description = structure_1.StructureProperties.entity.pdbx_description(l).join(', '); if (structure.models.length) { if (structure.representativeModel) { // indicates model trajectory description += ` (Model ${structure.models[modelIdx].modelNum})`; } else if (description.startsWith('Polymer ')) { // indicates generic entity name description += ` (${structure.models[modelIdx].entry})`; } } const label = `${id}: ${description}`; options.push([key, label]); seen.add(key); if (options.length > MaxSelectOptionsCount) { return [['', 'Too many entities']]; } } if (options.length === 0) options.push(['', 'No entities']); return options; } function getChainOptions(structure, modelEntityId) { const options = []; const l = structure_1.StructureElement.Location.create(structure); const seen = new Set(); const [modelIdx, entityId] = splitModelEntityId(modelEntityId); for (const unit of structure.units) { structure_1.StructureElement.Location.set(l, structure, unit, unit.elements[0]); if (structure.getModelIndex(unit.model) !== modelIdx) continue; if (structure_1.StructureProperties.entity.id(l) !== entityId) continue; const id = unit.chainGroupId; if (seen.has(id)) continue; // TODO handle special case // - more than one chain in a unit const label = (0, label_1.elementLabel)(l, { granularity: 'chain', hidePrefix: true, htmlStyling: false }); options.push([id, label]); seen.add(id); if (options.length > MaxSelectOptionsCount) { return [[-1, 'Too many chains']]; } } if (options.length === 0) options.push([-1, 'No chains']); return options; } function getOperatorOptions(structure, modelEntityId, chainGroupId) { const options = []; const l = structure_1.StructureElement.Location.create(structure); const seen = new Set(); const [modelIdx, entityId] = splitModelEntityId(modelEntityId); for (const unit of structure.units) { structure_1.StructureElement.Location.set(l, structure, unit, unit.elements[0]); if (structure.getModelIndex(unit.model) !== modelIdx) continue; if (structure_1.StructureProperties.entity.id(l) !== entityId) continue; if (unit.chainGroupId !== chainGroupId) continue; const id = opKey(l); if (seen.has(id)) continue; const label = unit.conformation.operator.name; options.push([id, label]); seen.add(id); if (options.length > MaxSelectOptionsCount) { return [['', 'Too many operators']]; } } if (options.length === 0) options.push(['', 'No operators']); return options; } function getStructureOptions(state) { var _a; const options = []; const all = []; const structures = state.select(mol_state_1.StateSelection.Generators.rootsOfType(objects_1.PluginStateObject.Molecule.Structure)); for (const s of structures) { if (!((_a = s.obj) === null || _a === void 0 ? void 0 : _a.data)) continue; all.push(s.obj.data); options.push([s.transform.ref, s.obj.data.label]); } if (options.length === 0) options.push(['', 'No structure']); return { options, all }; } const SequenceViewModeParam = param_definition_1.ParamDefinition.Select('single', [['single', 'Chain'], ['polymers', 'Polymers'], ['all', 'Everything']]); class SequenceView extends base_1.PluginUIComponent { constructor() { super(...arguments); this.state = { structureOptions: { options: [], all: [] }, structure: structure_1.Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '', mode: 'single', sequenceViewModeParam: SequenceViewModeParam }; this.setParamProps = (p) => { const state = { ...this.state }; switch (p.name) { case 'mode': state.mode = p.value; if (this.state.mode === state.mode) return; if (state.mode === 'all' || state.mode === 'polymers') { break; } case 'structure': if (p.name === 'structure') state.structureRef = p.value; state.structure = this.getStructure(state.structureRef); state.modelEntityId = getModelEntityOptions(state.structure)[0][0]; state.chainGroupId = getChainOptions(state.structure, state.modelEntityId)[0][0]; state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0]; break; case 'entity': state.modelEntityId = p.value; state.chainGroupId = getChainOptions(state.structure, state.modelEntityId)[0][0]; state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0]; break; case 'chain': state.chainGroupId = p.value; state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0]; break; case 'operator': state.operatorKey = p.value; break; } this.setState(state); }; } componentDidMount() { var _a, _b; if (this.plugin.state.data.select(mol_state_1.StateSelection.Generators.rootsOfType(objects_1.PluginStateObject.Molecule.Structure)).length > 0) this.setState(this.getInitialState()); this.subscribe(this.plugin.state.events.object.updated, ({ ref, obj }) => { if (ref === this.state.structureRef && obj && obj.type === objects_1.PluginStateObject.Molecule.Structure.type && obj.data !== this.state.structure) { this.sync(); } }); this.subscribe(this.plugin.state.events.object.created, ({ obj }) => { if (obj && obj.type === objects_1.PluginStateObject.Molecule.Structure.type) { this.sync(); } }); this.subscribe(this.plugin.state.events.object.removed, ({ obj }) => { if (obj && obj.type === objects_1.PluginStateObject.Molecule.Structure.type) { this.sync(); } }); const modeOptions = (_b = (_a = this.plugin.spec.components) === null || _a === void 0 ? void 0 : _a.sequenceViewer) === null || _b === void 0 ? void 0 : _b.modeOptions; if (modeOptions) { const modeSet = new Set(modeOptions); const sequenceViewModeParam = { ...SequenceViewModeParam, options: SequenceViewModeParam.options.filter(([firstItem]) => modeSet.has(firstItem)), }; this.setState({ sequenceViewModeParam: sequenceViewModeParam }); } } sync() { const structureOptions = getStructureOptions(this.plugin.state.data); if ((0, array_1.arrayEqual)(structureOptions.all, this.state.structureOptions.all)) return; this.setState(this.getInitialState()); } getStructure(ref) { const state = this.plugin.state.data; const cell = state.select(ref)[0]; if (!ref || !cell || !cell.obj) return structure_1.Structure.Empty; return cell.obj.data; } getSequenceWrapper(params) { return { wrapper: getSequenceWrapper(this.state, this.plugin.managers.structure.selection), label: `${param_definition_1.ParamDefinition.optionLabel(params.chain, this.state.chainGroupId)} | ${param_definition_1.ParamDefinition.optionLabel(params.entity, this.state.modelEntityId)}` }; } getSequenceWrappers(params) { if (this.state.mode === 'single') return [this.getSequenceWrapper(params)]; const structure = this.getStructure(this.state.structureRef); const wrappers = []; for (const [modelEntityId, eLabel] of getModelEntityOptions(structure, this.state.mode === 'polymers')) { for (const [chainGroupId, cLabel] of getChainOptions(structure, modelEntityId)) { for (const [operatorKey] of getOperatorOptions(structure, modelEntityId, chainGroupId)) { wrappers.push({ wrapper: getSequenceWrapper({ structure, modelEntityId, chainGroupId, operatorKey }, this.plugin.managers.structure.selection), label: `${cLabel} | ${eLabel}` }); if (wrappers.length > MaxSequenceWrappersCount) return []; } } } return wrappers; } getInitialState() { var _a, _b, _c, _d; const structureOptions = getStructureOptions(this.plugin.state.data); const structureRef = structureOptions.options[0][0]; const structure = this.getStructure(structureRef); let modelEntityId = getModelEntityOptions(structure)[0][0]; let chainGroupId = getChainOptions(structure, modelEntityId)[0][0]; let operatorKey = getOperatorOptions(structure, modelEntityId, chainGroupId)[0][0]; if (this.state.structure && this.state.structure === structure) { modelEntityId = this.state.modelEntityId; chainGroupId = this.state.chainGroupId; operatorKey = this.state.operatorKey; } const defaultMode = (_b = (_a = this.plugin.spec.components) === null || _a === void 0 ? void 0 : _a.sequenceViewer) === null || _b === void 0 ? void 0 : _b.defaultMode; const initialMode = (_d = (_c = this.props.defaultMode) !== null && _c !== void 0 ? _c : defaultMode) !== null && _d !== void 0 ? _d : 'single'; return { structureOptions, structure, structureRef, modelEntityId, chainGroupId, operatorKey, mode: initialMode, sequenceViewModeParam: this.state.sequenceViewModeParam }; } get params() { const { structureOptions, structure, modelEntityId, chainGroupId } = this.state; const entityOptions = getModelEntityOptions(structure); const chainOptions = getChainOptions(structure, modelEntityId); const operatorOptions = getOperatorOptions(structure, modelEntityId, chainGroupId); return { structure: param_definition_1.ParamDefinition.Select(structureOptions.options[0][0], structureOptions.options, { shortLabel: true }), entity: param_definition_1.ParamDefinition.Select(entityOptions[0][0], entityOptions, { shortLabel: true }), chain: param_definition_1.ParamDefinition.Select(chainOptions[0][0], chainOptions, { shortLabel: true, twoColumns: true, label: 'Chain' }), operator: param_definition_1.ParamDefinition.Select(operatorOptions[0][0], operatorOptions, { shortLabel: true, twoColumns: true }), mode: this.state.sequenceViewModeParam, }; } get values() { return { structure: this.state.structureRef, entity: this.state.modelEntityId, chain: this.state.chainGroupId, operator: this.state.operatorKey, mode: this.state.mode }; } render() { if (this.getStructure(this.state.structureRef) === structure_1.Structure.Empty) { return (0, jsx_runtime_1.jsx)("div", { className: 'msp-sequence', children: (0, jsx_runtime_1.jsxs)("div", { className: 'msp-sequence-select', children: [(0, jsx_runtime_1.jsx)(icons_1.Icon, { svg: icons_1.HelpOutlineSvg, style: { cursor: 'help', position: 'absolute', right: 0, top: 0 }, title: 'Shows a sequence of one or more chains. Use the controls to alter selection.' }), (0, jsx_runtime_1.jsx)("span", { children: "Sequence" }), (0, jsx_runtime_1.jsx)("span", { style: { fontWeight: 'normal' }, children: "No structure available" })] }) }); } const params = this.params; const values = this.values; const sequenceWrappers = this.getSequenceWrappers(params); return (0, jsx_runtime_1.jsxs)("div", { className: 'msp-sequence', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'msp-sequence-select', children: [(0, jsx_runtime_1.jsx)(icons_1.Icon, { svg: icons_1.HelpOutlineSvg, style: { cursor: 'help', position: 'absolute', right: 0, top: 0 }, title: 'This shows a single sequence. Use the controls to show a different sequence. \nUse Ctrl or Cmd key to add a sequence range to focus; use Shift key to extend last focused/selected range.' }), (0, jsx_runtime_1.jsx)("span", { children: "Sequence of" }), (0, jsx_runtime_1.jsx)(parameters_1.PureSelectControl, { title: `[Structure] ${param_definition_1.ParamDefinition.optionLabel(params.structure, values.structure)}`, param: params.structure, name: 'structure', value: values.structure, onChange: this.setParamProps }), (0, jsx_runtime_1.jsx)(parameters_1.PureSelectControl, { title: `[Mode]`, param: this.state.sequenceViewModeParam, name: 'mode', value: values.mode, onChange: this.setParamProps }), values.mode === 'single' && (0, jsx_runtime_1.jsx)(parameters_1.PureSelectControl, { title: `[Entity] ${param_definition_1.ParamDefinition.optionLabel(params.entity, values.entity)}`, param: params.entity, name: 'entity', value: values.entity, onChange: this.setParamProps }), values.mode === 'single' && (0, jsx_runtime_1.jsx)(parameters_1.PureSelectControl, { title: `[Chain] ${param_definition_1.ParamDefinition.optionLabel(params.chain, values.chain)}`, param: params.chain, name: 'chain', value: values.chain, onChange: this.setParamProps }), params.operator.options.length > 1 && (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(parameters_1.PureSelectControl, { title: `[Instance] ${param_definition_1.ParamDefinition.optionLabel(params.operator, values.operator)}`, param: params.operator, name: 'operator', value: values.operator, onChange: this.setParamProps }) })] }), (0, jsx_runtime_1.jsx)(NonEmptySequenceWrapper, { children: sequenceWrappers.map((s, i) => { const elem = typeof s.wrapper === 'string' ? (0, jsx_runtime_1.jsx)("div", { className: 'msp-sequence-wrapper', children: s.wrapper }, i) : (0, jsx_runtime_1.jsx)(sequence_1.Sequence, { sequenceWrapper: s.wrapper }, i); if (values.mode === 'single') return elem; return (0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: 'msp-sequence-chain-label', children: s.label }), elem] }, i); }) })] }); } } exports.SequenceView = SequenceView; function NonEmptySequenceWrapper({ children }) { return (0, jsx_runtime_1.jsx)("div", { className: 'msp-sequence-wrapper-non-empty', children: children }); }