From ca13c576bf0ce5440bd62b333dd7f8ff5ab2798c Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Wed, 5 Sep 2018 18:46:37 -0700 Subject: [PATCH] wip, canvas example --- src/apps/canvas/app.ts | 19 ++++-- src/apps/canvas/component/app.tsx | 37 +++++++++-- .../component/structure-representation.tsx | 25 +++++--- src/apps/canvas/component/structure-view.tsx | 64 ++++++++----------- src/apps/canvas/util.ts | 13 +++- .../representation/structure/units-visual.ts | 1 + src/mol-geo/representation/util.ts | 12 +++- 7 files changed, 113 insertions(+), 58 deletions(-) diff --git a/src/apps/canvas/app.ts b/src/apps/canvas/app.ts index 340aa4869..9aeeccf00 100644 --- a/src/apps/canvas/app.ts +++ b/src/apps/canvas/app.ts @@ -5,9 +5,10 @@ */ import Viewer from 'mol-view/viewer'; -import { getCifFromUrl, getModelsFromMmcif } from './util'; +import { getCifFromUrl, getModelsFromMmcif, getCifFromFile } from './util'; import { StructureView } from './structure-view'; import { BehaviorSubject } from 'rxjs'; +import { CifBlock } from 'mol-io/reader/cif'; export class App { viewer: Viewer @@ -31,11 +32,21 @@ export class App { } } - async loadPdbId(id: string, assemblyId?: string) { - if (this.structureView) this.structureView.destroy() - const cif = await getCifFromUrl(`https://files.rcsb.org/download/${id}.cif`) + async loadCif(cif: CifBlock, assemblyId?: string) { const models = await getModelsFromMmcif(cif) this.structureView = await StructureView(this.viewer, models, { assemblyId }) this.pdbIdLoaded.next(this.structureView) } + + async loadPdbId(id: string, assemblyId?: string) { + if (this.structureView) this.structureView.destroy() + const cif = await getCifFromUrl(`https://files.rcsb.org/download/${id}.cif`) + this.loadCif(cif, assemblyId) + } + + async loadCifFile(file: File) { + if (this.structureView) this.structureView.destroy() + const cif = await getCifFromFile(file) + this.loadCif(cif) + } } \ No newline at end of file diff --git a/src/apps/canvas/component/app.tsx b/src/apps/canvas/component/app.tsx index 6663b10bb..2186e90c6 100644 --- a/src/apps/canvas/component/app.tsx +++ b/src/apps/canvas/component/app.tsx @@ -35,9 +35,11 @@ export class AppComponent extends React.Component<AppProps, AppState> { } componentDidMount() { - this.props.app.pdbIdLoaded.subscribe(() => this.setState({ - structureView: this.props.app.structureView - })) + this.props.app.pdbIdLoaded.subscribe((structureView) => { + this.setState({ + structureView: this.props.app.structureView + }) + }) } render() { @@ -49,7 +51,34 @@ export class AppComponent extends React.Component<AppProps, AppState> { </div> <div style={{float: 'right', width: '25%', height: '100%'}}> - {structureView ? <StructureViewComponent structureView={structureView} /> : ''} + <div> + <span>Load PDB ID</span> + <input + type='text' + onKeyDown={e => { + if (e.keyCode === 13) { + const value = e.currentTarget.value.trim() + if (value) { + this.props.app.loadPdbId(value) + } + } + }} + /> + </div> + <div> + <span>Load CIF file</span> + <input + accept='*.cif' + type='file' + onChange={e => { + if (e.target.files) this.props.app.loadCifFile(e.target.files[0]) + }} + /> + </div> + <hr/> + <div> + {structureView ? <StructureViewComponent structureView={structureView} /> : ''} + </div> </div> </div>; } diff --git a/src/apps/canvas/component/structure-representation.tsx b/src/apps/canvas/component/structure-representation.tsx index 3a08a80ca..0a0b12fe8 100644 --- a/src/apps/canvas/component/structure-representation.tsx +++ b/src/apps/canvas/component/structure-representation.tsx @@ -7,7 +7,8 @@ import * as React from 'react' import { StructureRepresentation, StructureProps } from 'mol-geo/representation/structure'; import Viewer from 'mol-view/viewer'; -import { VisualQuality } from 'mol-geo/representation/util'; +import { VisualQuality, VisualQualityNames } from 'mol-geo/representation/util'; +import { ColorThemeProps, ColorThemeName, ColorThemeNames } from 'mol-view/theme/color'; export interface StructureRepresentationComponentProps { viewer: Viewer @@ -18,6 +19,7 @@ export interface StructureRepresentationComponentState { label: string visible: boolean quality: VisualQuality + colorTheme: ColorThemeProps } export class StructureRepresentationComponent extends React.Component<StructureRepresentationComponentProps, StructureRepresentationComponentState> { @@ -25,6 +27,7 @@ export class StructureRepresentationComponent extends React.Component<StructureR label: this.props.representation.label, visible: this.props.representation.props.visible, quality: this.props.representation.props.quality, + colorTheme: this.props.representation.props.colorTheme, } componentWillMount() { @@ -35,6 +38,7 @@ export class StructureRepresentationComponent extends React.Component<StructureR label: repr.label, visible: repr.props.visible, quality: repr.props.quality, + colorTheme: repr.props.colorTheme, }) } @@ -44,6 +48,7 @@ export class StructureRepresentationComponent extends React.Component<StructureR if (state.visible !== undefined) props.visible = state.visible if (state.quality !== undefined) props.quality = state.quality + if (state.colorTheme !== undefined) props.colorTheme = state.colorTheme await repr.createOrUpdate(props).run() this.props.viewer.add(repr) @@ -52,13 +57,14 @@ export class StructureRepresentationComponent extends React.Component<StructureR const newState = { ...this.state, visible: repr.props.visible, - quality: repr.props.quality + quality: repr.props.quality, + colorTheme: repr.props.colorTheme, } this.setState(newState) } render() { - const { label, visible, quality } = this.state + const { label, visible, quality, colorTheme } = this.state return <div> <div> @@ -74,12 +80,13 @@ export class StructureRepresentationComponent extends React.Component<StructureR <div> <span>Quality</span> <select value={quality} onChange={(e) => this.update({ quality: e.target.value as VisualQuality }) }> - <option value='auto'>auto</option> - <option value='lowest'>lowest</option> - <option value='low'>low</option> - <option value='medium'>medium</option> - <option value='high'>high</option> - <option value='highest'>highest</option> + {VisualQualityNames.map(name => <option key={name} value={name}>{name}</option>)} + </select> + </div> + <div> + <span>Color Theme</span> + <select value={colorTheme.name} onChange={(e) => this.update({ colorTheme: { name: e.target.value as ColorThemeName } }) }> + {ColorThemeNames.map(name => <option key={name} value={name}>{name}</option>)} </select> </div> </div> diff --git a/src/apps/canvas/component/structure-view.tsx b/src/apps/canvas/component/structure-view.tsx index 7f3fdaab6..c00eb8e83 100644 --- a/src/apps/canvas/component/structure-view.tsx +++ b/src/apps/canvas/component/structure-view.tsx @@ -25,6 +25,8 @@ export interface StructureViewComponentProps { } export interface StructureViewComponentState { + structureView: StructureView + label: string modelId: number modelIds: { id: number, label: string }[] @@ -38,24 +40,12 @@ export interface StructureViewComponentState { } export class StructureViewComponent extends React.Component<StructureViewComponentProps, StructureViewComponentState> { - state = { - label: this.props.structureView.label, - modelId: this.props.structureView.modelId, - modelIds: this.props.structureView.getModelIds(), - assemblyId: this.props.structureView.assemblyId, - assemblyIds: this.props.structureView.getAssemblyIds(), - symmetryFeatureId: this.props.structureView.symmetryFeatureId, - symmetryFeatureIds: this.props.structureView.getSymmetryFeatureIds(), - - active: this.props.structureView.active, - structureRepresentations: this.props.structureView.structureRepresentations - } + state = this.stateFromStructureView(this.props.structureView) - componentWillMount() { - const sv = this.props.structureView + private stateFromStructureView(sv: StructureView) { + return { + structureView: sv, - this.setState({ - ...this.state, label: sv.label, modelId: sv.modelId, modelIds: sv.getModelIds(), @@ -66,7 +56,11 @@ export class StructureViewComponent extends React.Component<StructureViewCompone active: sv.active, structureRepresentations: sv.structureRepresentations - }) + } + } + + componentWillMount() { + this.setState(this.stateFromStructureView(this.props.structureView)) } componentDidMount() { @@ -78,33 +72,29 @@ export class StructureViewComponent extends React.Component<StructureViewCompone })) } - async update(state: Partial<StructureViewComponentState>) { - const sv = this.props.structureView + componentWillReceiveProps(nextProps: StructureViewComponentProps) { + if (nextProps.structureView !== this.props.structureView) { + this.setState(this.stateFromStructureView(nextProps.structureView)) - console.log(state) + nextProps.structureView.updated.subscribe(() => this.setState({ + symmetryFeatureIds: nextProps.structureView.getSymmetryFeatureIds(), + structureRepresentations: nextProps.structureView.structureRepresentations + })) + } + } + + async update(state: Partial<StructureViewComponentState>) { + const sv = this.state.structureView if (state.modelId !== undefined) await sv.setModel(state.modelId) if (state.assemblyId !== undefined) await sv.setAssembly(state.assemblyId) if (state.symmetryFeatureId !== undefined) await sv.setSymmetryFeature(state.symmetryFeatureId) - const newState = { - ...this.state, - label: sv.label, - modelId: sv.modelId, - modelIds: sv.getModelIds(), - assemblyId: sv.assemblyId, - assemblyIds: sv.getAssemblyIds(), - symmetryFeatureId: sv.symmetryFeatureId, - symmetryFeatureIds: sv.getSymmetryFeatureIds(), - - active: sv.active, - structureRepresentations: sv.structureRepresentations - } - this.setState(newState) + this.setState(this.stateFromStructureView(sv)) } render() { - const { label, modelIds, assemblyIds, symmetryFeatureIds, active, structureRepresentations } = this.state + const { structureView, label, modelIds, assemblyIds, symmetryFeatureIds, active, structureRepresentations } = this.state const modelIdOptions = modelIds.map(m => { return <option key={m.id} value={m.id}>{m.label}</option> @@ -172,7 +162,7 @@ export class StructureViewComponent extends React.Component<StructureViewCompone type='checkbox' checked={active[k]} onChange={(e) => { - const sv = this.props.structureView + const sv = structureView if (k === 'symmetryAxes') { sv.setSymmetryAxes(e.target.checked) } else if (Object.keys(sv.structureRepresentations).includes(k)) { @@ -190,7 +180,7 @@ export class StructureViewComponent extends React.Component<StructureViewCompone return <div key={i}> <StructureRepresentationComponent representation={structureRepresentations[k]} - viewer={this.props.structureView.viewer} + viewer={structureView.viewer} /> </div> } else { diff --git a/src/apps/canvas/util.ts b/src/apps/canvas/util.ts index f63797e10..9a2c8f7fa 100644 --- a/src/apps/canvas/util.ts +++ b/src/apps/canvas/util.ts @@ -5,7 +5,7 @@ */ import CIF, { CifBlock } from 'mol-io/reader/cif' -import { readUrlAs } from 'mol-util/read'; +import { readUrlAs, readFileAs } from 'mol-util/read'; import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure'; // import { parse as parseObj } from 'mol-io/reader/obj/parser' @@ -17,14 +17,21 @@ import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure // return parsed.result // } -export async function getCifFromUrl(url: string) { - const data = await readUrlAs(url, false) +export async function getCifFromData(data: string | Uint8Array) { const comp = CIF.parse(data) const parsed = await comp.run() if (parsed.isError) throw parsed return parsed.result.blocks[0] } +export async function getCifFromUrl(url: string) { + return getCifFromData(await readUrlAs(url, false)) +} + +export async function getCifFromFile(file: File, binary = false) { + return getCifFromData(await readFileAs(file, binary)) +} + export async function getModelsFromMmcif(cif: CifBlock) { return await Model.create(Format.mmCIF(cif)).run() } diff --git a/src/mol-geo/representation/structure/units-visual.ts b/src/mol-geo/representation/structure/units-visual.ts index fa7467ba0..8184f2f1b 100644 --- a/src/mol-geo/representation/structure/units-visual.ts +++ b/src/mol-geo/representation/structure/units-visual.ts @@ -131,6 +131,7 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu } else { if (group && !areGroupsIdentical(group, currentGroup)) { currentGroup = group + currentProps.colorTheme.structure = currentStructure } await update(ctx, props) } diff --git a/src/mol-geo/representation/util.ts b/src/mol-geo/representation/util.ts index 50c795ce0..39b116a3a 100644 --- a/src/mol-geo/representation/util.ts +++ b/src/mol-geo/representation/util.ts @@ -84,7 +84,17 @@ export function updateRenderableState(state: RenderableState, props: Required<Ba state.depthMask = props.depthMask } -export type VisualQuality = 'custom' | 'auto' | 'highest' | 'high' | 'medium' | 'low' | 'lowest' +export const VisualQualityInfo = { + 'custom': {}, + 'auto': {}, + 'highest': {}, + 'high': {}, + 'medium': {}, + 'low': {}, + 'lowest': {}, +} +export type VisualQuality = keyof typeof VisualQualityInfo +export const VisualQualityNames = Object.keys(VisualQualityInfo) export interface QualityProps { quality: VisualQuality -- GitLab