Skip to content
Snippets Groups Projects
Commit 7437e8c9 authored by David Sehnal's avatar David Sehnal
Browse files

wip proteopedia wrapper

parent 50ebd546
No related branches found
No related tags found
No related merge requests found
/**
* Copyright (c) 2019 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>
*/
import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/structure';
import { Color } from 'mol-util/color';
import { Location } from 'mol-model/location';
import { ColorTheme, LocationColor } from 'mol-theme/color';
import { ParamDefinition as PD } from 'mol-util/param-definition'
import { ThemeDataContext } from 'mol-theme/theme';
import { Column } from 'mol-data/db';
const Description = 'Gives every chain a color from a list based on its `asym_id` value.'
export function createProteopediaCustomTheme(colors: number[], defaultColor: number) {
const ProteopediaCustomColorThemeParams = {
colors: PD.ObjectList({ color: PD.Color(Color(0xffffff)) }, ({ color }) => Color.toHexString(color),
{ defaultValue: colors.map(c => ({ color: Color(c) })) }),
defaultColor: PD.Color(Color(defaultColor))
}
type ProteopediaCustomColorThemeParams = typeof ProteopediaCustomColorThemeParams
function getChainIdColorThemeParams(ctx: ThemeDataContext) {
return ProteopediaCustomColorThemeParams // TODO return copy
}
function getAsymId(unit: Unit): StructureElement.Property<string> {
switch (unit.kind) {
case Unit.Kind.Atomic:
return StructureProperties.chain.label_asym_id
case Unit.Kind.Spheres:
case Unit.Kind.Gaussians:
return StructureProperties.coarse.asym_id
}
}
function addAsymIds(map: Map<string, number>, data: Column<string>) {
let j = map.size
for (let o = 0, ol = data.rowCount; o < ol; ++o) {
const k = data.value(o)
if (!map.has(k)) {
map.set(k, j)
j += 1
}
}
}
function ProteopediaCustomColorTheme(ctx: ThemeDataContext, props: PD.Values<ProteopediaCustomColorThemeParams>): ColorTheme<ProteopediaCustomColorThemeParams> {
let color: LocationColor
const colors = props.colors, colorCount = colors.length, defaultColor = props.defaultColor;
if (ctx.structure) {
const l = StructureElement.create()
const { models } = ctx.structure
const asymIdSerialMap = new Map<string, number>()
for (let i = 0, il = models.length; i < il; ++i) {
const m = models[i]
addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id)
if (m.coarseHierarchy.isDefined) {
addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id)
addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id)
}
}
color = (location: Location): Color => {
if (StructureElement.isLocation(location)) {
const asym_id = getAsymId(location.unit);
const o = asymIdSerialMap.get(asym_id(location)) || 0;
return o < colorCount ? colors[o].color : defaultColor;
} else if (Link.isLocation(location)) {
const asym_id = getAsymId(location.aUnit)
l.unit = location.aUnit
l.element = location.aUnit.elements[location.aIndex]
const o = asymIdSerialMap.get(asym_id(l)) || 0;
return o < colorCount ? colors[o].color : defaultColor;
}
return defaultColor
}
} else {
color = () => defaultColor
}
return {
factory: ProteopediaCustomColorTheme,
granularity: 'group',
color,
props,
description: Description,
legend: undefined
}
}
const ProteopediaCustomColorThemeProvider: ColorTheme.Provider<ProteopediaCustomColorThemeParams> = {
label: 'Proteopedia Custom',
factory: ProteopediaCustomColorTheme,
getParams: getChainIdColorThemeParams,
defaultValues: PD.getDefaultValues(ProteopediaCustomColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
}
return ProteopediaCustomColorThemeProvider;
}
\ No newline at end of file
...@@ -92,9 +92,24 @@ export interface LoadParams { ...@@ -92,9 +92,24 @@ export interface LoadParams {
export interface RepresentationStyle { export interface RepresentationStyle {
sequence?: RepresentationStyle.Entry, sequence?: RepresentationStyle.Entry,
hetGroups?: RepresentationStyle.Entry, hetGroups?: RepresentationStyle.Entry,
snfg3d?: { hide?: boolean },
water?: RepresentationStyle.Entry water?: RepresentationStyle.Entry
} }
export namespace RepresentationStyle { export namespace RepresentationStyle {
export type Entry = { kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName } export type Entry = { hide?: boolean, kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName }
}
export enum StateElements {
Model = 'model',
ModelProps = 'model-props',
Assembly = 'assembly',
Sequence = 'sequence',
SequenceVisual = 'sequence-visual',
Het = 'het',
HetVisual = 'het-visual',
Het3DSNFG = 'het-3dsnfg',
Water = 'water',
WaterVisual = 'water-visual'
} }
\ No newline at end of file
...@@ -55,7 +55,11 @@ ...@@ -55,7 +55,11 @@
</select> </select>
</div> </div>
<div id="app"></div> <div id="app"></div>
<script> <script>
// it might be a good idea to define these colors in a separate script file
var CustomColors = [0x00ff00, 0x0000ff];
var DefaultCustomColor = 0xff0000;
// create an instance of the plugin // create an instance of the plugin
var PluginWrapper = new MolStarProteopediaWrapper(); var PluginWrapper = new MolStarProteopediaWrapper();
...@@ -78,9 +82,19 @@ ...@@ -78,9 +82,19 @@
// var format = 'pdb'; // var format = 'pdb';
// var assemblyId = 'deposited'; // var assemblyId = 'deposited';
PluginWrapper.init('app' /** or document.getElementById('app') */); var representationStyle = {
sequence: { coloring: 'proteopedia-custom' }, // or just { }
hetGroups: { kind: 'ball-and-stick' }, // or 'spacefill
water: { hide: true },
snfg3d: { hide: false }
};
PluginWrapper.init('app' /** or document.getElementById('app') */, {
customColorList: CustomColors,
customColorDefault: DefaultCustomColor
});
PluginWrapper.setBackground(0xffffff); PluginWrapper.setBackground(0xffffff);
PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId }); PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId, representationStyle: representationStyle });
PluginWrapper.toggleSpin(); PluginWrapper.toggleSpin();
PluginWrapper.events.modelInfo.subscribe(function (info) { PluginWrapper.events.modelInfo.subscribe(function (info) {
...@@ -92,6 +106,22 @@ ...@@ -92,6 +106,22 @@
addSeparator(); addSeparator();
addHeader('Representation');
addControl('Custom Chain Colors', () => PluginWrapper.updateStyle({ sequence: { coloring: 'proteopedia-custom' } }, true));
addControl('Default Chain Colors', () => PluginWrapper.updateStyle({ sequence: { } }, true));
addControl('HET Spacefill', () => PluginWrapper.updateStyle({ hetGroups: { kind: 'spacefill' } }, true));
addControl('HET Ball-and-stick', () => PluginWrapper.updateStyle({ hetGroups: { kind: 'ball-and-stick' } }, true));
addControl('Hide 3DSNFG', () => PluginWrapper.updateStyle({ snfg3d: { hide: true } }, true));
addControl('Show 3DSNFG', () => PluginWrapper.updateStyle({ snfg3d: { hide: false } }, true));
addControl('Hide Water', () => PluginWrapper.updateStyle({ water: { hide: true } }, true));
addControl('Show Water', () => PluginWrapper.updateStyle({ water: { } }, true));
addSeparator();
addHeader('Camera'); addHeader('Camera');
addControl('Toggle Spin', () => PluginWrapper.toggleSpin()); addControl('Toggle Spin', () => PluginWrapper.toggleSpin());
......
...@@ -15,10 +15,12 @@ import { PluginStateObject as PSO, PluginStateObject } from 'mol-plugin/state/ob ...@@ -15,10 +15,12 @@ import { PluginStateObject as PSO, PluginStateObject } from 'mol-plugin/state/ob
import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in'; import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in';
import { StateBuilder, StateObject } from 'mol-state'; import { StateBuilder, StateObject } from 'mol-state';
import { EvolutionaryConservation } from './annotation'; import { EvolutionaryConservation } from './annotation';
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo } from './helpers'; import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
import { RxEventHelper } from 'mol-util/rx-event-helper'; import { RxEventHelper } from 'mol-util/rx-event-helper';
import { ControlsWrapper } from './ui/controls'; import { ControlsWrapper } from './ui/controls';
import { PluginState } from 'mol-plugin/state'; import { PluginState } from 'mol-plugin/state';
import { Scheduler } from 'mol-task';
import { createProteopediaCustomTheme } from './coloring';
require('mol-plugin/skin/light.scss') require('mol-plugin/skin/light.scss')
class MolStarProteopediaWrapper { class MolStarProteopediaWrapper {
...@@ -33,9 +35,15 @@ class MolStarProteopediaWrapper { ...@@ -33,9 +35,15 @@ class MolStarProteopediaWrapper {
plugin: PluginContext; plugin: PluginContext;
init(target: string | HTMLElement) { init(target: string | HTMLElement, options?: {
customColorList?: number[],
customColorDefault?: number
}) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec, ...DefaultPluginSpec,
animations: [
AnimateModelIndex
],
layout: { layout: {
initial: { initial: {
isExpanded: false, isExpanded: false,
...@@ -47,6 +55,9 @@ class MolStarProteopediaWrapper { ...@@ -47,6 +55,9 @@ class MolStarProteopediaWrapper {
} }
}); });
const customColoring = createProteopediaCustomTheme((options && options.customColorList) || [], (options && options.customColorDefault) || 0x777777);
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add('proteopedia-custom', customColoring);
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.Descriptor.name, EvolutionaryConservation.colorTheme!); this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.Descriptor.name, EvolutionaryConservation.colorTheme!);
this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider); this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider);
this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider); this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider);
...@@ -66,43 +77,87 @@ class MolStarProteopediaWrapper { ...@@ -66,43 +77,87 @@ class MolStarProteopediaWrapper {
: b.apply(StateTransforms.Model.TrajectoryFromPDB); : b.apply(StateTransforms.Model.TrajectoryFromPDB);
return parsed return parsed
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }, { ref: 'model' }); .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }, { ref: StateElements.Model });
} }
private structure(assemblyId: string) { private structure(assemblyId: string) {
const model = this.state.build().to('model'); const model = this.state.build().to(StateElements.Model);
const s = model
.apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.Descriptor.name] }, { ref: StateElements.ModelProps, state: { isGhost: false } })
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { 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 model return s;
.apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.Descriptor.name] }, { ref: 'props', state: { isGhost: false } })
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
} }
private visual(ref: string, style?: RepresentationStyle) { private visual(_style?: RepresentationStyle, partial?: boolean) {
const structure = this.getObj<PluginStateObject.Molecule.Structure>(ref); const structure = this.getObj<PluginStateObject.Molecule.Structure>(StateElements.Assembly);
if (!structure) return; if (!structure) return;
const root = this.state.build().to(ref); const style = _style || { };
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'sequence' }) const update = this.state.build();
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, if (!partial || (partial && style.sequence)) {
(style && style.sequence && style.sequence.kind) || 'cartoon', const root = update.to(StateElements.Sequence);
(style && style.sequence && style.sequence.coloring) || 'unit-index', structure), if (style.sequence && style.sequence.hide) {
{ ref: 'sequence-visual' }); root.delete(StateElements.SequenceVisual);
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: 'het' }) } else {
.apply(StateTransforms.Representation.StructureRepresentation3D, root.applyOrUpdate(StateElements.SequenceVisual, StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
(style && style.hetGroups && style.hetGroups.kind) || 'ball-and-stick', (style.sequence && style.sequence.kind) || 'cartoon',
(style && style.hetGroups && style.hetGroups.coloring), structure), (style.sequence && style.sequence.coloring) || 'unit-index', structure));
{ ref: 'het-visual' }); }
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }, { ref: 'water' }) }
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, if (!partial || (partial && style.hetGroups)) {
(style && style.water && style.water.kind) || 'ball-and-stick', const root = update.to(StateElements.Het);
(style && style.water && style.water.coloring), structure, { alpha: 0.51 }), if (style.hetGroups && style.hetGroups.hide) {
{ ref: 'water-visual' }); root.delete(StateElements.HetVisual);
} else {
return root; if (style.hetGroups && style.hetGroups.hide) {
root.delete(StateElements.HetVisual);
} else {
root.applyOrUpdate(StateElements.HetVisual, StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
(style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
(style.hetGroups && style.hetGroups.coloring), structure));
}
}
}
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,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, 'carbohydrate', void 0, structure));
}
}
}
if (!partial || (partial && style.water)) {
const root = update.to(StateElements.Het);
if (style.water && style.water.hide) {
root.delete(StateElements.Water);
} else {
root.applyOrUpdate(StateElements.Water, StateTransforms.Model.StructureComplexElement, { type: 'water' })
.applyOrUpdate(StateElements.WaterVisual, StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
(style.water && style.water.kind) || 'ball-and-stick',
(style.water && style.water.coloring), structure, { alpha: 0.51 }));
}
}
return update;
} }
private getObj<T extends StateObject>(ref: string): T['data'] { private getObj<T extends StateObject>(ref: string): T['data'] {
...@@ -134,7 +189,7 @@ class MolStarProteopediaWrapper { ...@@ -134,7 +189,7 @@ class MolStarProteopediaWrapper {
if (this.loadedParams.url !== url || this.loadedParams.format !== format) { if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
loadType = 'full'; loadType = 'full';
} else if (this.loadedParams.url === url) { } else if (this.loadedParams.url === url) {
if (state.select('asm').length > 0) loadType = 'update'; if (state.select(StateElements.Assembly).length > 0) loadType = 'update';
} }
if (loadType === 'full') { if (loadType === 'full') {
...@@ -146,18 +201,18 @@ class MolStarProteopediaWrapper { ...@@ -146,18 +201,18 @@ class MolStarProteopediaWrapper {
await this.applyState(structureTree); await this.applyState(structureTree);
} else { } else {
const tree = state.build(); const tree = state.build();
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' })); tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
await this.applyState(tree); await this.applyState(tree);
} }
await this.updateStyle(representationStyle); await this.updateStyle(representationStyle);
this.loadedParams = { url, format, assemblyId }; this.loadedParams = { url, format, assemblyId };
PluginCommands.Camera.Reset.dispatch(this.plugin, { }); Scheduler.setImmediate(() => PluginCommands.Camera.Reset.dispatch(this.plugin, { }));
} }
async updateStyle(style?: RepresentationStyle) { async updateStyle(style?: RepresentationStyle, partial?: boolean) {
const tree = this.visual('asm', style); const tree = this.visual(style, partial);
if (!tree) return; if (!tree) return;
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree }); await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
} }
...@@ -186,7 +241,7 @@ class MolStarProteopediaWrapper { ...@@ -186,7 +241,7 @@ class MolStarProteopediaWrapper {
coloring = { coloring = {
evolutionaryConservation: async () => { evolutionaryConservation: async () => {
await this.updateStyle({ sequence: { kind: 'spacefill' } }); await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
const state = this.state; const state = this.state;
...@@ -194,7 +249,7 @@ class MolStarProteopediaWrapper { ...@@ -194,7 +249,7 @@ class MolStarProteopediaWrapper {
const tree = state.build(); const tree = state.build();
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues }; const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
tree.to('sequence-visual').update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme })); tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
// for (const v of visuals) { // for (const v of visuals) {
// } // }
......
...@@ -84,6 +84,7 @@ namespace StateBuilder { ...@@ -84,6 +84,7 @@ namespace StateBuilder {
} }
toRoot<A extends StateObject>() { return new To<A>(this.state, this.state.tree.root.ref, this); } toRoot<A extends StateObject>() { return new To<A>(this.state, this.state.tree.root.ref, this); }
delete(ref: StateTransform.Ref) { delete(ref: StateTransform.Ref) {
if (!this.state.tree.transforms.has(ref)) return this;
this.editInfo.count++; this.editInfo.count++;
this.state.tree.remove(ref); this.state.tree.remove(ref);
this.state.actions.push({ kind: 'delete', ref }); this.state.actions.push({ kind: 'delete', ref });
...@@ -113,6 +114,19 @@ namespace StateBuilder { ...@@ -113,6 +114,19 @@ namespace StateBuilder {
return new To(this.state, t.ref, this.root); return new To(this.state, t.ref, this.root);
} }
/**
* If the ref is present, the transform is applied.
* Otherwise a transform with the specifed ref is created.
*/
applyOrUpdate<T extends StateTransformer<A, any, any>>(ref: StateTransform.Ref, tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>): To<StateTransformer.To<T>> {
if (this.state.tree.transforms.has(ref)) {
this.to(ref).update(params);
return this.to(ref) as To<StateTransformer.To<T>>;
} else {
return this.apply(tr, params, { ...options, ref });
}
}
/** /**
* A helper to greate a group-like state object and keep the current type. * A helper to greate a group-like state object and keep the current type.
*/ */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment