diff --git a/src/mol-model-props/pdbe/themes/structure-quality-report.ts b/src/mol-model-props/pdbe/themes/structure-quality-report.ts new file mode 100644 index 0000000000000000000000000000000000000000..a527e58f7797addf3ad4b2a3de9109c5179f330b --- /dev/null +++ b/src/mol-model-props/pdbe/themes/structure-quality-report.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-report'; +import { Location } from 'mol-model/location'; +import { StructureElement } from 'mol-model/structure'; +import { ColorTheme, LocationColor } from 'mol-theme/color'; +import { ThemeDataContext } from 'mol-theme/theme'; +import { Color } from 'mol-util/color'; +import { TableLegend } from 'mol-util/color/tables'; + +const ValidationColors = [ + Color.fromRgb(170, 170, 170), // not applicable + Color.fromRgb(0, 255, 0), // 0 issues + Color.fromRgb(255, 255, 0), // 1 + Color.fromRgb(255, 128, 0), // 2 + Color.fromRgb(255, 0, 0), // 3 or more +] + +const ValidationColorTable: [string, Color][] = [ + ['No Issues', ValidationColors[1]], + ['One Issue', ValidationColors[2]], + ['Two Issues', ValidationColors[3]], + ['Three Or More Issues', ValidationColors[4]], + ['Not Applicable', ValidationColors[9]] +] + +export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> { + let color: LocationColor + + if (ctx.structure && ctx.structure.models[0].customProperties.has(StructureQualityReport.Descriptor)) { + const getIssues = StructureQualityReport.getIssues; + color = (location: Location) => { + if (StructureElement.isLocation(location)) { + return ValidationColors[Math.min(3, getIssues(location).length) + 1]; + } + return ValidationColors[0]; + } + } else { + color = () => ValidationColors[0]; + } + + return { + factory: StructureQualityReportColorTheme, + granularity: 'group', + color: color, + props: props, + description: 'Assigns residue colors according to the number of issues in the PDBe Validation Report.', + legend: TableLegend(ValidationColorTable) + } +} \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/custom-props.ts b/src/mol-plugin/behavior/dynamic/custom-props.ts index 2c31fddcf2538e8d0b1dbd62ced3738aa7d7ac24..a6e0ad81e316f9469ae8dc452937f708e48b55fa 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props.ts @@ -4,15 +4,14 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { ParamDefinition } from 'mol-util/param-definition'; -import { PluginBehavior } from '../behavior'; +import { OrderedSet } from 'mol-data/int'; import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-report'; -import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry'; +import { StructureQualityReportColorTheme } from 'mol-model-props/pdbe/themes/structure-quality-report'; import { Loci } from 'mol-model/loci'; import { StructureElement } from 'mol-model/structure'; -import { OrderedSet } from 'mol-data/int'; - -// TODO: make auto attach working better for "initial state" by supporting default props in state updates +import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { PluginBehavior } from '../behavior'; export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean }>({ name: 'pdbe-structure-quality-report-prop', @@ -33,6 +32,15 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo register(): void { this.ctx.customModelProperties.register(this.provider); this.ctx.lociLabels.addProvider(labelPDBeValidation); + + // TODO: support filtering of themes based on the input structure + // in this case, it would check structure.models[0].customProperties.has(StructureQualityReport.Descriptor) + // TODO: add remove functionality + this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', { + label: 'PDBe Structure Quality Report', + factory: StructureQualityReportColorTheme, + getParams: () => ({}) + }) } update(p: { autoAttach: boolean }) { @@ -45,10 +53,13 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo unregister() { this.ctx.customModelProperties.unregister(StructureQualityReport.Descriptor.name); this.ctx.lociLabels.removeProvider(labelPDBeValidation); + + // TODO: add remove functionality to registry + // this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('pdbe-structure-quality-report') } }, params: () => ({ - autoAttach: ParamDefinition.Boolean(false) + autoAttach: PD.Boolean(false) }), display: { name: 'Focus Loci on Select', group: 'Camera' } }); diff --git a/src/mol-plugin/state/actions/basic.ts b/src/mol-plugin/state/actions/basic.ts index ad60f56f20e545aa22f904f1a9d802699f790ab0..e9607ca09b3e91d8f62a8a853092c000d1e8d635 100644 --- a/src/mol-plugin/state/actions/basic.ts +++ b/src/mol-plugin/state/actions/basic.ts @@ -27,10 +27,23 @@ const DownloadStructure = StateAction.build({ display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its default Assembly and visual.' }, params: { source: PD.MappedStatic('bcif-static', { - 'pdbe-updated': PD.Text('1cbs', { label: 'Id' }), - 'rcsb': PD.Text('1tqn', { label: 'Id' }), - 'bcif-static': PD.Text('1tqn', { label: 'Id' }), - 'url': PD.Group({ url: PD.Text(''), isBinary: PD.Boolean(false) }, { isExpanded: true }) + 'pdbe-updated': PD.Group({ + id: PD.Text('1cbs', { label: 'Id' }), + supportProps: PD.Boolean(false) + }, { isExpanded: true }), + 'rcsb': PD.Group({ + id: PD.Text('1tqn', { label: 'Id' }), + supportProps: PD.Boolean(false) + }, { isExpanded: true }), + 'bcif-static': PD.Group({ + id: PD.Text('1tqn', { label: 'Id' }), + supportProps: PD.Boolean(false) + }, { isExpanded: true }), + 'url': PD.Group({ + url: PD.Text(''), + isBinary: PD.Boolean(false), + supportProps: PD.Boolean(false) + }, { isExpanded: true }) }, { options: [ ['pdbe-updated', 'PDBe Updated'], @@ -50,19 +63,19 @@ const DownloadStructure = StateAction.build({ url = src.params; break; case 'pdbe-updated': - url = { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params}` }; + url = { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.id.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params}` }; break; case 'rcsb': - url = { url: `https://files.rcsb.org/download/${src.params.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params}` }; + url = { url: `https://files.rcsb.org/download/${src.params.id.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params}` }; break; case 'bcif-static': - url = { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params}` }; + url = { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.id.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params}` }; break; default: throw new Error(`${(src as any).name} not supported.`); } const data = b.toRoot().apply(StateTransforms.Data.Download, url); - return state.update(createStructureTree(data)); + return state.update(createStructureTree(data, params.source.params.supportProps)); }); export const OpenStructure = StateAction.build({ @@ -72,16 +85,20 @@ export const OpenStructure = StateAction.build({ })(({ params, state }) => { const b = state.build(); const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) }); - return state.update(createStructureTree(data)); + return state.update(createStructureTree(data, false)); }); -function createStructureTree(b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree { - const root = b +function createStructureTree(b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, supportProps: boolean): StateTree { + let root = b .apply(StateTransforms.Data.ParseCif) .apply(StateTransforms.Model.TrajectoryFromMmCif, {}) - .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) - .apply(StateTransforms.Model.CustomModelProperties, { properties: [] }) - .apply(StateTransforms.Model.StructureAssemblyFromModel); + .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }); + + if (supportProps) { + // TODO: implement automatic default property assigment in State.update + root = root.apply(StateTransforms.Model.CustomModelProperties, { properties: [] }); + } + root = root.apply(StateTransforms.Model.StructureAssemblyFromModel); complexRepresentation(root); diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index d7ca14ec8bfe1d0670e1879bf63fe952d51213f6..440d3eb78d6501808bf4a355a0c75a24cd4133b1 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -180,7 +180,7 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({ apply({ a, params }, ctx: PluginContext) { return Task.create('Custom Props', async taskCtx => { await attachProps(a.data, ctx, taskCtx, params.properties); - return new SO.Molecule.Model(a.data, { label: a.label, description: 'Custom Props' }); + return new SO.Molecule.Model(a.data, { label: 'Props', description: `${params.properties.length} Selected` }); }); } }); diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index ac3330b70dfe374eb44afa13e6455cc75639265c..9d5a802b96009435dadc2175cb8a9236034c8b9a 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -45,7 +45,7 @@ export class LociLabelControl extends PluginComponent<{}, { entries: ReadonlyArr } render() { - return <div> + return <div style={{ textAlign: 'right' }}> {this.state.entries.map((e, i) => <div key={'' + i}>{e}</div>)} </div> }