index.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import * as ReactDOM from 'react-dom';
  7. import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
  8. import './index.html'
  9. import { PluginContext } from '../../mol-plugin/context';
  10. import { PluginCommands } from '../../mol-plugin/command';
  11. import { StateTransforms } from '../../mol-plugin/state/transforms';
  12. import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
  13. import { Color } from '../../mol-util/color';
  14. import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
  15. import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
  16. import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
  17. import { EvolutionaryConservation } from './annotation';
  18. import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
  19. import { RxEventHelper } from '../../mol-util/rx-event-helper';
  20. import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
  21. import { PluginState } from '../../mol-plugin/state';
  22. import { Scheduler } from '../../mol-task';
  23. import { createProteopediaCustomTheme } from './coloring';
  24. import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
  25. import { BuiltInStructureRepresentations } from '../../mol-repr/structure/registry';
  26. import { BuiltInColorThemes } from '../../mol-theme/color';
  27. import { BuiltInSizeThemes } from '../../mol-theme/size';
  28. import { ColorNames } from '../../mol-util/color/names';
  29. import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
  30. import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
  31. // import { Vec3 } from 'mol-math/linear-algebra';
  32. // import { ParamDefinition } from 'mol-util/param-definition';
  33. // import { Text } from 'mol-geo/geometry/text/text';
  34. require('../../mol-plugin-ui/skin/light.scss')
  35. class MolStarProteopediaWrapper {
  36. static VERSION_MAJOR = 3;
  37. static VERSION_MINOR = 4;
  38. private _ev = RxEventHelper.create();
  39. readonly events = {
  40. modelInfo: this._ev<ModelInfo>()
  41. };
  42. plugin: PluginContext;
  43. init(target: string | HTMLElement, options?: {
  44. customColorList?: number[]
  45. }) {
  46. this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
  47. ...DefaultPluginSpec,
  48. animations: [
  49. AnimateModelIndex
  50. ],
  51. layout: {
  52. initial: {
  53. isExpanded: false,
  54. showControls: false
  55. },
  56. controls: {
  57. right: ControlsWrapper
  58. }
  59. }
  60. });
  61. const customColoring = createProteopediaCustomTheme((options && options.customColorList) || []);
  62. this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add('proteopedia-custom', customColoring);
  63. this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.propertyProvider.descriptor.name, EvolutionaryConservation.colorThemeProvider!);
  64. this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
  65. this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider, true);
  66. }
  67. get state() {
  68. return this.plugin.state.dataState;
  69. }
  70. private download(b: StateBuilder.To<PSO.Root>, url: string) {
  71. return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
  72. }
  73. private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats) {
  74. const parsed = format === 'cif'
  75. ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
  76. : b.apply(StateTransforms.Model.TrajectoryFromPDB);
  77. return parsed
  78. .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }, { ref: StateElements.Model });
  79. }
  80. private structure(assemblyId: string) {
  81. const model = this.state.build().to(StateElements.Model);
  82. const s = model
  83. .apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.propertyProvider.descriptor.name] }, { ref: StateElements.ModelProps, state: { isGhost: false } })
  84. .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: StateElements.Assembly });
  85. s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
  86. s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: StateElements.Het });
  87. s.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }, { ref: StateElements.Water });
  88. return s;
  89. }
  90. private visual(_style?: RepresentationStyle, partial?: boolean) {
  91. const structure = this.getObj<PluginStateObject.Molecule.Structure>(StateElements.Assembly);
  92. if (!structure) return;
  93. const style = _style || { };
  94. const update = this.state.build();
  95. if (!partial || (partial && style.sequence)) {
  96. const root = update.to(StateElements.Sequence);
  97. if (style.sequence && style.sequence.hide) {
  98. root.delete(StateElements.SequenceVisual);
  99. } else {
  100. root.applyOrUpdate(StateElements.SequenceVisual, StateTransforms.Representation.StructureRepresentation3D,
  101. StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
  102. (style.sequence && style.sequence.kind) || 'cartoon',
  103. (style.sequence && style.sequence.coloring) || 'unit-index', structure));
  104. }
  105. }
  106. if (!partial || (partial && style.hetGroups)) {
  107. const root = update.to(StateElements.Het);
  108. if (style.hetGroups && style.hetGroups.hide) {
  109. root.delete(StateElements.HetVisual);
  110. } else {
  111. if (style.hetGroups && style.hetGroups.hide) {
  112. root.delete(StateElements.HetVisual);
  113. } else {
  114. root.applyOrUpdate(StateElements.HetVisual, StateTransforms.Representation.StructureRepresentation3D,
  115. StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
  116. (style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
  117. (style.hetGroups && style.hetGroups.coloring), structure));
  118. }
  119. }
  120. }
  121. if (!partial || (partial && style.snfg3d)) {
  122. const root = update.to(StateElements.Het);
  123. if (style.hetGroups && style.hetGroups.hide) {
  124. root.delete(StateElements.HetVisual);
  125. } else {
  126. if (style.snfg3d && style.snfg3d.hide) {
  127. root.delete(StateElements.Het3DSNFG);
  128. } else {
  129. root.applyOrUpdate(StateElements.Het3DSNFG, StateTransforms.Representation.StructureRepresentation3D,
  130. StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, 'carbohydrate', void 0, structure));
  131. }
  132. }
  133. }
  134. if (!partial || (partial && style.water)) {
  135. const root = update.to(StateElements.Water);
  136. if (style.water && style.water.hide) {
  137. root.delete(StateElements.WaterVisual);
  138. } else {
  139. root.applyOrUpdate(StateElements.WaterVisual, StateTransforms.Representation.StructureRepresentation3D,
  140. StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
  141. (style.water && style.water.kind) || 'ball-and-stick',
  142. (style.water && style.water.coloring), structure, { alpha: 0.51 }));
  143. }
  144. }
  145. return update;
  146. }
  147. private getObj<T extends StateObject>(ref: string): T['data'] {
  148. const state = this.state;
  149. const cell = state.select(ref)[0];
  150. if (!cell || !cell.obj) return void 0;
  151. return (cell.obj as T).data;
  152. }
  153. private async doInfo(checkPreferredAssembly: boolean) {
  154. const model = this.getObj<PluginStateObject.Molecule.Model>('model');
  155. if (!model) return;
  156. const info = await ModelInfo.get(this.plugin, model, checkPreferredAssembly)
  157. this.events.modelInfo.next(info);
  158. return info;
  159. }
  160. private applyState(tree: StateBuilder) {
  161. return PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
  162. }
  163. private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
  164. async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
  165. let loadType: 'full' | 'update' = 'full';
  166. const state = this.plugin.state.dataState;
  167. if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
  168. loadType = 'full';
  169. } else if (this.loadedParams.url === url) {
  170. if (state.select(StateElements.Assembly).length > 0) loadType = 'update';
  171. }
  172. if (loadType === 'full') {
  173. await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
  174. const modelTree = this.model(this.download(state.build().toRoot(), url), format);
  175. await this.applyState(modelTree);
  176. const info = await this.doInfo(true);
  177. const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
  178. const structureTree = this.structure(asmId);
  179. await this.applyState(structureTree);
  180. } else {
  181. const tree = state.build();
  182. const info = await this.doInfo(true);
  183. const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
  184. tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
  185. await this.applyState(tree);
  186. }
  187. await this.updateStyle(representationStyle);
  188. this.loadedParams = { url, format, assemblyId };
  189. Scheduler.setImmediate(() => PluginCommands.Camera.Reset.dispatch(this.plugin, { }));
  190. }
  191. async updateStyle(style?: RepresentationStyle, partial?: boolean) {
  192. const tree = this.visual(style, partial);
  193. if (!tree) return;
  194. await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
  195. }
  196. setBackground(color: number) {
  197. if (!this.plugin.canvas3d) return;
  198. const renderer = this.plugin.canvas3d.props.renderer;
  199. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
  200. }
  201. toggleSpin() {
  202. if (!this.plugin.canvas3d) return;
  203. const trackball = this.plugin.canvas3d.props.trackball;
  204. const spinning = trackball.spin;
  205. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
  206. if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
  207. }
  208. viewport = {
  209. setSettings: (settings?: Canvas3DProps) => {
  210. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
  211. settings: settings || DefaultCanvas3DParams
  212. });
  213. }
  214. };
  215. camera = {
  216. toggleSpin: () => this.toggleSpin(),
  217. resetPosition: () => PluginCommands.Camera.Reset.dispatch(this.plugin, { }),
  218. // setClip: (options?: { distance?: number, near?: number, far?: number }) => {
  219. // if (!options) {
  220. // PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
  221. // settings: {
  222. // cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
  223. // clip: DefaultCanvas3DParams.clip
  224. // }
  225. // });
  226. // return;
  227. // }
  228. // options = options || { };
  229. // const props = this.plugin.canvas3d.props;
  230. // const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
  231. // const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
  232. // PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
  233. // settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
  234. // });
  235. // }
  236. }
  237. animate = {
  238. modelIndex: {
  239. maxFPS: 8,
  240. onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) },
  241. onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) },
  242. palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) },
  243. loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) },
  244. stop: () => this.plugin.state.animation.stop()
  245. }
  246. }
  247. coloring = {
  248. evolutionaryConservation: async (params?: { sequence?: boolean, het?: boolean, keepStyle?: boolean }) => {
  249. if (!params || !params.keepStyle) {
  250. await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
  251. }
  252. const state = this.state;
  253. // const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
  254. // for (const v of visuals) {
  255. // }
  256. const tree = state.build();
  257. const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
  258. if (!params || !!params.sequence) {
  259. tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
  260. }
  261. if (params && !!params.het) {
  262. tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
  263. }
  264. await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
  265. }
  266. }
  267. private experimentalDataElement?: Element = void 0;
  268. experimentalData = {
  269. init: async (parent: Element) => {
  270. const asm = this.state.select(StateElements.Assembly)[0].obj!;
  271. const params = InitVolumeStreaming.createDefaultParams(asm, this.plugin);
  272. params.options.behaviorRef = StateElements.VolumeStreaming;
  273. params.defaultView = 'box';
  274. params.options.channelParams['fo-fc(+ve)'] = { wireframe: true };
  275. params.options.channelParams['fo-fc(-ve)'] = { wireframe: true };
  276. await this.plugin.runTask(this.state.applyAction(InitVolumeStreaming, params, StateElements.Assembly));
  277. this.experimentalDataElement = parent;
  278. volumeStreamingControls(this.plugin, parent);
  279. },
  280. remove: () => {
  281. const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
  282. if (!r) return;
  283. PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.state, ref: r.transform.ref });
  284. if (this.experimentalDataElement) {
  285. ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
  286. this.experimentalDataElement = void 0;
  287. }
  288. }
  289. }
  290. hetGroups = {
  291. reset: () => {
  292. const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
  293. PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
  294. PluginCommands.Camera.Reset.dispatch(this.plugin, { });
  295. },
  296. focusFirst: async (compId: string) => {
  297. if (!this.state.transforms.has(StateElements.Assembly)) return;
  298. await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
  299. // const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
  300. const update = this.state.build();
  301. update.delete(StateElements.HetGroupFocusGroup);
  302. const core = MS.struct.filter.first([
  303. MS.struct.generator.atomGroups({
  304. 'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), compId]),
  305. 'group-by': MS.core.str.concat([MS.struct.atomProperty.core.operatorName(), MS.struct.atomProperty.macromolecular.residueKey()])
  306. })
  307. ]);
  308. const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
  309. const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
  310. group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Core', expression: core }, { ref: StateElements.HetGroupFocus })
  311. .apply(StateTransforms.Representation.StructureRepresentation3D, this.createCoreVisualParams());
  312. group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings', expression: surroundings })
  313. .apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
  314. // sel.apply(StateTransforms.Representation.StructureLabels3D, {
  315. // target: { name: 'residues', params: { } },
  316. // options: {
  317. // ...ParamDefinition.getDefaultValues(Text.Params),
  318. // background: true,
  319. // backgroundMargin: 0.2,
  320. // backgroundColor: ColorNames.snow,
  321. // backgroundOpacity: 0.9,
  322. // }
  323. // });
  324. await PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
  325. const focus = (this.state.select(StateElements.HetGroupFocus)[0].obj as PluginStateObject.Molecule.Structure).data;
  326. const sphere = focus.boundary.sphere;
  327. // const asmCenter = asm.boundary.sphere.center;
  328. // const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
  329. // Vec3.normalize(position, position);
  330. // Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
  331. const radius = Math.max(sphere.radius, 5)
  332. const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius, radius);
  333. PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
  334. }
  335. }
  336. private createSurVisualParams() {
  337. const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
  338. return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
  339. repr: BuiltInStructureRepresentations['ball-and-stick'],
  340. color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
  341. size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
  342. });
  343. }
  344. private createCoreVisualParams() {
  345. const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
  346. return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
  347. repr: BuiltInStructureRepresentations['ball-and-stick'],
  348. // color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
  349. // size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
  350. });
  351. }
  352. snapshot = {
  353. get: () => {
  354. return this.plugin.state.getSnapshot();
  355. },
  356. set: (snapshot: PluginState.Snapshot) => {
  357. return this.plugin.state.setSnapshot(snapshot);
  358. },
  359. download: async (url: string) => {
  360. try {
  361. const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
  362. await this.plugin.state.setSnapshot(snapshot);
  363. } catch (e) {
  364. console.log(e);
  365. }
  366. }
  367. }
  368. }
  369. (window as any).MolStarProteopediaWrapper = MolStarProteopediaWrapper;