sequence.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import * as React from 'react';
  8. import { PluginUIComponent } from './base';
  9. import { PluginStateObject as PSO } from '../mol-plugin-state/objects';
  10. import { Sequence } from './sequence/sequence';
  11. import { Structure, StructureElement, StructureProperties as SP, Unit } from '../mol-model/structure';
  12. import { SequenceWrapper } from './sequence/wrapper';
  13. import { PolymerSequenceWrapper } from './sequence/polymer';
  14. import { MarkerAction } from '../mol-util/marker-action';
  15. import { PureSelectControl } from './controls/parameters';
  16. import { ParamDefinition as PD } from '../mol-util/param-definition';
  17. import { HeteroSequenceWrapper } from './sequence/hetero';
  18. import { State, StateSelection } from '../mol-state';
  19. import { ChainSequenceWrapper } from './sequence/chain';
  20. import { ElementSequenceWrapper } from './sequence/element';
  21. import { elementLabel } from '../mol-theme/label';
  22. import { Icon, HelpOutlineSvg } from './controls/icons';
  23. import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
  24. const MaxDisplaySequenceLength = 5000;
  25. function opKey(l: StructureElement.Location) {
  26. const ids = SP.unit.pdbx_struct_oper_list_ids(l);
  27. const ncs = SP.unit.struct_ncs_oper_id(l);
  28. const hkl = SP.unit.hkl(l);
  29. const spgrOp = SP.unit.spgrOp(l);
  30. return `${ids.sort().join(',')}|${ncs}|${hkl}|${spgrOp}`;
  31. }
  32. function splitModelEntityId(modelEntityId: string) {
  33. const [ modelIdx, entityId ] = modelEntityId.split('|');
  34. return [ parseInt(modelIdx), entityId ];
  35. }
  36. function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureSelectionManager): SequenceWrapper.Any | string {
  37. const { structure, modelEntityId, chainGroupId, operatorKey } = state;
  38. const l = StructureElement.Location.create(structure);
  39. const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
  40. const units: Unit[] = [];
  41. for (const unit of structure.units) {
  42. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  43. if (structure.getModelIndex(unit.model) !== modelIdx) continue;
  44. if (SP.entity.id(l) !== entityId) continue;
  45. if (unit.chainGroupId !== chainGroupId) continue;
  46. if (opKey(l) !== operatorKey) continue;
  47. units.push(unit);
  48. }
  49. if (units.length > 0) {
  50. const data = { structure, units };
  51. const unit = units[0];
  52. let sw: SequenceWrapper<any>;
  53. if (unit.polymerElements.length) {
  54. const l = StructureElement.Location.create(structure, unit, unit.elements[0]);
  55. const entitySeq = unit.model.sequence.byEntityKey[SP.entity.key(l)];
  56. // check if entity sequence is available
  57. if (entitySeq && entitySeq.sequence.length <= MaxDisplaySequenceLength) {
  58. sw = new PolymerSequenceWrapper(data);
  59. } else {
  60. const polymerElementCount = units.reduce((a, v) => a + v.polymerElements.length, 0);
  61. if (Unit.isAtomic(unit) || polymerElementCount > MaxDisplaySequenceLength) {
  62. sw = new ChainSequenceWrapper(data);
  63. } else {
  64. sw = new ElementSequenceWrapper(data);
  65. }
  66. }
  67. } else if (Unit.isAtomic(unit)) {
  68. const residueCount = units.reduce((a, v) => a + (v as Unit.Atomic).residueCount, 0);
  69. if (residueCount > MaxDisplaySequenceLength) {
  70. sw = new ChainSequenceWrapper(data);
  71. } else {
  72. sw = new HeteroSequenceWrapper(data);
  73. }
  74. } else {
  75. console.warn('should not happen, expecting coarse units to be polymeric');
  76. sw = new ChainSequenceWrapper(data);
  77. }
  78. sw.markResidue(structureSelection.getLoci(structure), MarkerAction.Select);
  79. return sw;
  80. } else {
  81. return 'No sequence available';
  82. }
  83. }
  84. function getModelEntityOptions(structure: Structure) {
  85. const options: [string, string][] = [];
  86. const l = StructureElement.Location.create(structure);
  87. const seen = new Set<string>();
  88. for (const unit of structure.units) {
  89. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  90. const id = SP.entity.id(l);
  91. const modelIdx = structure.getModelIndex(unit.model);
  92. const key = `${modelIdx}|${id}`;
  93. if (seen.has(key)) continue;
  94. let description = SP.entity.pdbx_description(l).join(', ');
  95. if (structure.models.length) {
  96. if (structure.representativeModel) { // indicates model trajectory
  97. description += ` (Model ${structure.models[modelIdx].modelNum})`;
  98. } else if (description.startsWith('Polymer ')) { // indicates generic entity name
  99. description += ` (${structure.models[modelIdx].entry})`;
  100. }
  101. }
  102. const label = `${id}: ${description}`;
  103. options.push([ key, label ]);
  104. seen.add(key);
  105. }
  106. if (options.length === 0) options.push(['', 'No entities']);
  107. return options;
  108. }
  109. function getChainOptions(structure: Structure, modelEntityId: string) {
  110. const options: [number, string][] = [];
  111. const l = StructureElement.Location.create(structure);
  112. const seen = new Set<number>();
  113. const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
  114. for (const unit of structure.units) {
  115. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  116. if (structure.getModelIndex(unit.model) !== modelIdx) continue;
  117. if (SP.entity.id(l) !== entityId) continue;
  118. const id = unit.chainGroupId;
  119. if (seen.has(id)) continue;
  120. // TODO handle special case
  121. // - more than one chain in a unit
  122. let label = elementLabel(l, { granularity: 'chain', hidePrefix: true, htmlStyling: false });
  123. options.push([ id, label ]);
  124. seen.add(id);
  125. }
  126. if (options.length === 0) options.push([-1, 'No units']);
  127. return options;
  128. }
  129. function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number) {
  130. const options: [string, string][] = [];
  131. const l = StructureElement.Location.create(structure);
  132. const seen = new Set<string>();
  133. const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
  134. for (const unit of structure.units) {
  135. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  136. if (structure.getModelIndex(unit.model) !== modelIdx) continue;
  137. if (SP.entity.id(l) !== entityId) continue;
  138. if (unit.chainGroupId !== chainGroupId) continue;
  139. const id = opKey(l);
  140. if (seen.has(id)) continue;
  141. const label = unit.conformation.operator.name;
  142. options.push([ id, label ]);
  143. seen.add(id);
  144. }
  145. if (options.length === 0) options.push(['', 'No operators']);
  146. return options;
  147. }
  148. function getStructureOptions(state: State) {
  149. const options: [string, string][] = [];
  150. const structures = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure));
  151. for (const s of structures) {
  152. options.push([s.transform.ref, s.obj!.data.label]);
  153. }
  154. if (options.length === 0) options.push(['', 'No structure']);
  155. return options;
  156. }
  157. type SequenceViewState = {
  158. structure: Structure,
  159. structureRef: string,
  160. modelEntityId: string,
  161. chainGroupId: number,
  162. operatorKey: string
  163. }
  164. export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
  165. state = { structure: Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '' }
  166. componentDidMount() {
  167. if (this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)).length > 0) this.setState(this.getInitialState());
  168. this.subscribe(this.plugin.state.events.object.updated, ({ ref, obj }) => {
  169. if (ref === this.state.structureRef && obj && obj.type === PSO.Molecule.Structure.type && obj.data !== this.state.structure) {
  170. this.setState(this.getInitialState());
  171. }
  172. });
  173. this.subscribe(this.plugin.state.events.object.created, ({ obj }) => {
  174. if (obj && obj.type === PSO.Molecule.Structure.type) {
  175. this.setState(this.getInitialState());
  176. }
  177. });
  178. this.subscribe(this.plugin.state.events.object.removed, ({ obj }) => {
  179. if (obj && obj.type === PSO.Molecule.Structure.type && obj.data === this.state.structure) {
  180. this.setState(this.getInitialState());
  181. }
  182. });
  183. }
  184. private getStructure(ref: string) {
  185. const state = this.plugin.state.data;
  186. const cell = state.select(ref)[0];
  187. if (!ref || !cell || !cell.obj) return Structure.Empty;
  188. return (cell.obj as PSO.Molecule.Structure).data;
  189. }
  190. private getSequenceWrapper() {
  191. return getSequenceWrapper(this.state, this.plugin.managers.structure.selection);
  192. }
  193. private getInitialState(): SequenceViewState {
  194. const structureRef = getStructureOptions(this.plugin.state.data)[0][0];
  195. const structure = this.getStructure(structureRef);
  196. let modelEntityId = getModelEntityOptions(structure)[0][0];
  197. let chainGroupId = getChainOptions(structure, modelEntityId)[0][0];
  198. let operatorKey = getOperatorOptions(structure, modelEntityId, chainGroupId)[0][0];
  199. if (this.state.structure && this.state.structure === structure) {
  200. modelEntityId = this.state.modelEntityId;
  201. chainGroupId = this.state.chainGroupId;
  202. operatorKey = this.state.operatorKey;
  203. }
  204. return { structure, structureRef, modelEntityId, chainGroupId, operatorKey };
  205. }
  206. private get params() {
  207. const { structure, modelEntityId, chainGroupId } = this.state;
  208. const structureOptions = getStructureOptions(this.plugin.state.data);
  209. const entityOptions = getModelEntityOptions(structure);
  210. const chainOptions = getChainOptions(structure, modelEntityId);
  211. const operatorOptions = getOperatorOptions(structure, modelEntityId, chainGroupId);
  212. return {
  213. structure: PD.Select(structureOptions[0][0], structureOptions, { shortLabel: true }),
  214. entity: PD.Select(entityOptions[0][0], entityOptions, { shortLabel: true }),
  215. chain: PD.Select(chainOptions[0][0], chainOptions, { shortLabel: true, twoColumns: true, label: 'Chain' }),
  216. operator: PD.Select(operatorOptions[0][0], operatorOptions, { shortLabel: true, twoColumns: true })
  217. };
  218. }
  219. private get values(): PD.Values<SequenceView['params']> {
  220. return {
  221. structure: this.state.structureRef,
  222. entity: this.state.modelEntityId,
  223. chain: this.state.chainGroupId,
  224. operator: this.state.operatorKey
  225. };
  226. }
  227. private setParamProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
  228. const state = { ...this.state };
  229. switch (p.name) {
  230. case 'structure':
  231. state.structureRef = p.value;
  232. state.structure = this.getStructure(p.value);
  233. state.modelEntityId = getModelEntityOptions(state.structure)[0][0];
  234. state.chainGroupId = getChainOptions(state.structure, state.modelEntityId)[0][0];
  235. state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0];
  236. break;
  237. case 'entity':
  238. state.modelEntityId = p.value;
  239. state.chainGroupId = getChainOptions(state.structure, state.modelEntityId)[0][0];
  240. state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0];
  241. break;
  242. case 'chain':
  243. state.chainGroupId = p.value;
  244. state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0];
  245. break;
  246. case 'operator':
  247. state.operatorKey = p.value;
  248. break;
  249. }
  250. this.setState(state);
  251. }
  252. render() {
  253. if (this.getStructure(this.state.structureRef) === Structure.Empty) {
  254. return <div className='msp-sequence'>
  255. <div className='msp-sequence-select'>
  256. <Icon svg={HelpOutlineSvg} style={{ cursor: 'help', position: 'absolute', right: 0, top: 0 }}
  257. title='This shows a single sequence. Use the controls to show a different sequence.'/>
  258. <span>Sequence</span><span style={{ fontWeight: 'normal' }}>No structure available</span>
  259. </div>
  260. </div>;
  261. }
  262. const sequenceWrapper = this.getSequenceWrapper();
  263. const params = this.params;
  264. const values = this.values;
  265. return <div className='msp-sequence'>
  266. <div className='msp-sequence-select'>
  267. <Icon svg={HelpOutlineSvg} style={{ cursor: 'help', position: 'absolute', right: 0, top: 0 }}
  268. title='This shows a single sequence. Use the controls to show a different sequence.' />
  269. <span>Sequence of</span>
  270. <PureSelectControl title={`[Structure] ${PD.optionLabel(params.structure, values.structure)}`} param={params.structure} name='structure' value={values.structure} onChange={this.setParamProps} />
  271. <PureSelectControl title={`[Entity] ${PD.optionLabel(params.entity, values.entity)}`} param={params.entity} name='entity' value={values.entity} onChange={this.setParamProps} />
  272. <PureSelectControl title={`[Chain] ${PD.optionLabel(params.chain, values.chain)}`} param={params.chain} name='chain' value={values.chain} onChange={this.setParamProps} />
  273. {params.operator.options.length > 1 && <>
  274. <PureSelectControl title={`[Instance] ${PD.optionLabel(params.operator, values.operator)}`} param={params.operator} name='operator' value={values.operator} onChange={this.setParamProps} />
  275. </>}
  276. </div>
  277. {typeof sequenceWrapper === 'string'
  278. ? <div className='msp-sequence-wrapper msp-sequence-wrapper-non-empty'>{sequenceWrapper}</div>
  279. : <Sequence sequenceWrapper={sequenceWrapper} />}
  280. </div>;
  281. }
  282. }