molstar
Version:
A comprehensive macromolecular library.
390 lines (389 loc) • 20.6 kB
JavaScript
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as ReactDOM from 'react-dom';
import { DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createPluginUI } from '../../mol-plugin-ui';
import { renderReact18 } from '../../mol-plugin-ui/react18';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { PluginCommands } from '../../mol-plugin/commands';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateSelection } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { getFormattedTime } from '../../mol-util/date';
import { download } from '../../mol-util/download';
import { RxEventHelper } from '../../mol-util/rx-event-helper';
import { EvolutionaryConservation } from './annotation';
import { createProteopediaCustomTheme } from './coloring';
import { ModelInfo, StateElements } from './helpers';
import './index.html';
import { volumeStreamingControls } from './ui/controls';
require('../../mol-plugin-ui/skin/light.scss');
class MolStarProteopediaWrapper {
constructor() {
this._ev = RxEventHelper.create();
this.events = {
modelInfo: this._ev()
};
this.emptyLoadedParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
this.loadedParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
this.viewport = {
setSettings: (settings) => {
PluginCommands.Canvas3D.SetSettings(this.plugin, {
settings: settings || DefaultCanvas3DParams
});
}
};
this.camera = {
toggleSpin: () => this.toggleSpin(),
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, {})
};
this.animate = {
modelIndex: {
targetFps: 8,
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
stop: () => this.plugin.managers.animation.stop()
}
};
this.coloring = {
evolutionaryConservation: async (params) => {
if (!params || !params.keepStyle) {
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
}
const state = this.state;
const tree = state.build();
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
if (!params || !!params.sequence) {
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
}
if (params && !!params.het) {
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
}
await PluginCommands.State.Update(this.plugin, { state, tree });
}
};
this.experimentalDataElement = void 0;
this.experimentalData = {
init: async (parent) => {
const asm = this.state.select(StateElements.Assembly)[0].obj;
const params = InitVolumeStreaming.createDefaultParams(asm, this.plugin);
params.options.behaviorRef = StateElements.VolumeStreaming;
params.defaultView = 'box';
params.options.channelParams['fo-fc(+ve)'] = { wireframe: true };
params.options.channelParams['fo-fc(-ve)'] = { wireframe: true };
await this.plugin.runTask(this.state.applyAction(InitVolumeStreaming, params, StateElements.Assembly));
this.experimentalDataElement = parent;
volumeStreamingControls(this.plugin, parent);
},
remove: () => {
const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
if (!r)
return;
PluginCommands.State.RemoveObject(this.plugin, { state: this.state, ref: r.transform.ref });
if (this.experimentalDataElement) {
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
this.experimentalDataElement = void 0;
}
}
};
this.hetGroups = {
reset: () => {
const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
PluginCommands.Camera.Reset(this.plugin, {});
},
focusFirst: async (compId, options) => {
if (!this.state.transforms.has(StateElements.Assembly))
return;
await PluginCommands.Camera.Reset(this.plugin, {});
const update = this.state.build();
update.delete(StateElements.HetGroupFocusGroup);
const core = MS.struct.filter.first([
MS.struct.generator.atomGroups({
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), compId]),
'group-by': MS.core.str.concat([MS.struct.atomProperty.core.operatorName(), MS.struct.atomProperty.macromolecular.residueKey()])
})
]);
const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
const asm = this.state.select(StateElements.Assembly)[0].obj;
const coreSel = group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Core', expression: core }, { ref: StateElements.HetGroupFocus });
coreSel.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
type: 'ball-and-stick'
}));
coreSel.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
type: 'label',
typeParams: { level: 'element' }
}), { tags: ['proteopedia-labels'] });
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings', expression: surroundings })
.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
type: 'ball-and-stick',
color: 'uniform', colorParams: { value: ColorNames.gray },
size: 'uniform', sizeParams: { value: 0.33 }
}));
if (!(options === null || options === void 0 ? void 0 : options.hideLabels)) {
// Labels
const waters = MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.entityType(), 'water']),
});
const exclude = (options === null || options === void 0 ? void 0 : options.doNotLabelWaters) ? MS.struct.combinator.merge([core, waters]) : core;
const onlySurroundings = MS.struct.modifier.exceptBy({ 0: surroundings, by: exclude });
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings (only)', expression: onlySurroundings })
.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
type: 'label',
typeParams: { level: 'residue' }
}), { tags: ['proteopedia-labels'] }); // the tag can later be used to toggle the labels
}
await PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
const focus = this.state.select(StateElements.HetGroupFocus)[0].obj.data;
const sphere = focus.boundary.sphere;
const radius = Math.max(sphere.radius, 5);
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, radius);
PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: 250 });
}
};
this.snapshot = {
get: (params) => {
return this.plugin.state.getSnapshot(params);
},
set: (snapshot) => {
return this.plugin.state.setSnapshot(snapshot);
},
download: async (type = 'molj', params) => {
const data = await this.plugin.managers.snapshot.serialize({ type, params });
download(data, `mol-star_state_${getFormattedTime()}.${type}`);
},
fetch: async (url, type = 'molj') => {
try {
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
this.loadedParams = { ...this.emptyLoadedParams };
return await this.plugin.managers.snapshot.open(new File([data], `state.${type}`));
}
catch (e) {
console.log(e);
}
}
};
}
async init(target, options) {
this.plugin = await createPluginUI({
target: typeof target === 'string' ? document.getElementById(target) : target,
render: renderReact18,
spec: {
...DefaultPluginUISpec(),
animations: [
AnimateModelIndex
],
layout: {
initial: {
isExpanded: false,
showControls: false
}
},
components: {
remoteState: 'none'
}
}
});
const customColoring = createProteopediaCustomTheme((options && options.customColorList) || []);
this.plugin.representation.structure.themes.colorThemeRegistry.add(customColoring);
this.plugin.representation.structure.themes.colorThemeRegistry.add(EvolutionaryConservation.colorThemeProvider);
this.plugin.managers.lociLabels.addProvider(EvolutionaryConservation.labelProvider);
this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider, true);
}
get state() {
return this.plugin.state.data;
}
download(b, url, isBinary) {
return b.apply(StateTransforms.Data.Download, { url: Asset.Url(url), isBinary });
}
model(b, format) {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
return parsed
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }, { ref: StateElements.Model });
}
structure(assemblyId) {
const model = this.state.build().to(StateElements.Model);
const props = {
type: assemblyId ? {
name: 'assembly',
params: { id: assemblyId }
} : {
name: 'model',
params: {}
}
};
const s = model
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: StateElements.Het });
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }, { ref: StateElements.Water });
return s;
}
visual(_style, partial) {
const structure = this.getObj(StateElements.Assembly);
if (!structure)
return;
const style = _style || {};
const update = this.state.build();
if (!partial || (partial && style.sequence)) {
const root = update.to(StateElements.Sequence);
if (style.sequence && style.sequence.hide) {
root.delete(StateElements.SequenceVisual);
}
else {
root.applyOrUpdate(StateElements.SequenceVisual, StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, structure, {
type: (style.sequence && style.sequence.kind) || 'cartoon',
color: (style.sequence && style.sequence.coloring) || 'unit-index'
}));
}
}
if (!partial || (partial && style.hetGroups)) {
const root = update.to(StateElements.Het);
if (style.hetGroups && style.hetGroups.hide) {
root.delete(StateElements.HetVisual);
}
else {
if (style.hetGroups && style.hetGroups.hide) {
root.delete(StateElements.HetVisual);
}
else {
root.applyOrUpdate(StateElements.HetVisual, StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, structure, {
type: (style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
color: style.hetGroups && style.hetGroups.coloring
}));
}
}
}
if (!partial || (partial && style.snfg3d)) {
const root = update.to(StateElements.Het);
if (style.hetGroups && style.hetGroups.hide) {
root.delete(StateElements.HetVisual);
}
else {
if (style.snfg3d && style.snfg3d.hide) {
root.delete(StateElements.Het3DSNFG);
}
else {
root.applyOrUpdate(StateElements.Het3DSNFG, StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, structure, { type: 'carbohydrate' }));
}
}
}
if (!partial || (partial && style.water)) {
const root = update.to(StateElements.Water);
if (style.water && style.water.hide) {
root.delete(StateElements.WaterVisual);
}
else {
root.applyOrUpdate(StateElements.WaterVisual, StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, structure, {
type: (style.water && style.water.kind) || 'ball-and-stick',
typeParams: { alpha: 0.51 },
color: style.water && style.water.coloring
}));
}
}
return update;
}
getObj(ref) {
const state = this.state;
const cell = state.select(ref)[0];
if (!cell || !cell.obj)
return void 0;
return cell.obj.data;
}
async doInfo(checkPreferredAssembly) {
const model = this.getObj('model');
if (!model)
return;
const info = await ModelInfo.get(this.plugin, model, checkPreferredAssembly);
this.events.modelInfo.next(info);
return info;
}
applyState(tree) {
return PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
}
async load({ url, format = 'cif', assemblyId = '', isBinary = false, representationStyle }) {
let loadType = 'full';
const state = this.plugin.state.data;
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
loadType = 'full';
}
else if (this.loadedParams.url === url) {
if (state.select(StateElements.Assembly).length > 0)
loadType = 'update';
}
if (loadType === 'full') {
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
const modelTree = this.model(this.download(state.build().toRoot(), url, isBinary), format);
await this.applyState(modelTree);
const info = await this.doInfo(true);
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
const structureTree = this.structure(asmId);
await this.applyState(structureTree);
}
else {
const tree = state.build();
const info = await this.doInfo(true);
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
const props = {
type: assemblyId ? {
name: 'assembly',
params: { id: asmId }
} : {
name: 'model',
params: {}
}
};
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
await this.applyState(tree);
}
await this.updateStyle(representationStyle);
this.loadedParams = { url, format, assemblyId };
}
async updateStyle(style, partial) {
const tree = this.visual(style, partial);
if (!tree)
return;
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
}
setBackground(color) {
if (!this.plugin.canvas3d)
return;
const renderer = this.plugin.canvas3d.props.renderer;
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
}
toggleSpin() {
if (!this.plugin.canvas3d)
return;
const trackball = this.plugin.canvas3d.props.trackball;
PluginCommands.Canvas3D.SetSettings(this.plugin, {
settings: {
trackball: {
...trackball,
animate: trackball.animate.name === 'spin'
? { name: 'off', params: {} }
: { name: 'spin', params: { speed: 1 } }
}
}
});
}
animateModelIndexTargetFps() {
return Math.max(1, this.animate.modelIndex.targetFps | 0);
}
}
MolStarProteopediaWrapper.VERSION_MAJOR = 5;
MolStarProteopediaWrapper.VERSION_MINOR = 5;
window.MolStarProteopediaWrapper = MolStarProteopediaWrapper;