Skip to content
Snippets Groups Projects
Commit 75c3cc1f authored by Alexander Rose's avatar Alexander Rose
Browse files

wip, convert canvas app to react

parent 94e1bafc
No related branches found
No related tags found
No related merge requests found
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import Viewer from 'mol-view/viewer';
import { getCifFromUrl, getModelsFromMmcif } from './util';
import { StructureView } from './view';
import { BehaviorSubject } from 'rxjs';
export class App {
viewer: Viewer
container: HTMLDivElement | null = null;
canvas: HTMLCanvasElement | null = null;
structureView: StructureView | null = null;
pdbIdLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null)
initViewer(_canvas: HTMLCanvasElement, _container: HTMLDivElement) {
this.canvas = _canvas
this.container = _container
try {
this.viewer = Viewer.create(this.canvas, this.container)
this.viewer.animate()
return true
} catch (e) {
console.error(e)
return false
}
}
async loadPdbId(id: string) {
if (this.structureView) this.structureView.destroy()
const cif = await getCifFromUrl(`https://files.rcsb.org/download/${id}.cif`)
const models = await getModelsFromMmcif(cif)
this.structureView = await StructureView(this.viewer, models, { assembly: '1' })
this.pdbIdLoaded.next(this.structureView)
}
}
\ No newline at end of file
......@@ -4,17 +4,14 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { AssemblySymmetry } from "mol-model-props/rcsb/symmetry";
import { Table } from "mol-data/db";
import { Color } from "mol-util/color";
import { MeshBuilder } from "mol-geo/mesh/mesh-builder";
import { Tensor } from "mol-math/linear-algebra";
import { addSphere } from "mol-geo/mesh/builder/sphere";
import { addCylinder } from "mol-geo/mesh/builder/cylinder";
import { Shape } from "mol-model/shape";
import { DefaultView } from "./view";
import { Model } from "mol-model/structure";
import { ShapeRepresentation, ShapeProps } from "mol-geo/representation/shape";
import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry';
import { Table } from 'mol-data/db';
import { Color } from 'mol-util/color';
import { MeshBuilder } from 'mol-geo/mesh/mesh-builder';
import { Tensor } from 'mol-math/linear-algebra';
import { addSphere } from 'mol-geo/mesh/builder/sphere';
import { addCylinder } from 'mol-geo/mesh/builder/cylinder';
import { Shape } from 'mol-model/shape';
export function getAxesShape(featureId: number, assemblySymmetry: AssemblySymmetry) {
const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature
......@@ -59,39 +56,4 @@ export function getClusterColorTheme(featureId: number, assemblySymmetry: Assemb
for (let i = 0, il = clusters._rowCount; i < il; ++i) {
console.log(clusters.members.value(i), clusters.avg_rmsd.value(i), feature.stoichiometry_value, feature.stoichiometry_description)
}
}
export interface SymmetryView extends DefaultView {
readonly axes: ShapeRepresentation<ShapeProps> // TODO
}
export async function SymmetryView(model: Model, assembly: string): Promise<SymmetryView> {
const view = await DefaultView(model, assembly)
const axesRepr = ShapeRepresentation()
await AssemblySymmetry.attachFromCifOrAPI(model)
const assemblySymmetry = AssemblySymmetry.get(model)
console.log(assemblySymmetry)
if (assemblySymmetry) {
const features = assemblySymmetry.getFeatures(assembly)
if (features._rowCount) {
const axesShape = getAxesShape(features.id.value(1), assemblySymmetry)
console.log(axesShape)
if (axesShape) {
await axesRepr.create(axesShape, {
colorTheme: { name: 'shape-group' },
// colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
useFog: false // TODO fog not working properly
}).run()
}
getClusterColorTheme(features.id.value(0), assemblySymmetry)
getClusterColorTheme(features.id.value(1), assemblySymmetry)
}
}
return Object.assign({}, view, {
axes: axesRepr
})
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { StructureView } from '../view';
import { App } from '../app';
import { Viewport } from './viewport';
import { StructureComponent } from './structure';
// export function FileInput (props: {
// accept: string
// onChange: (v: FileList | null) => void,
// }) {
// return <input
// accept={props.accept || '*.*'}
// type='file'
// onChange={e => props.onChange.call(null, e.target.files)}
// />
// }
export interface AppProps {
app: App
}
export interface AppState {
structureView: StructureView | null
}
export class AppComponent extends React.Component<AppProps, AppState> {
state = {
structureView: this.props.app.structureView,
}
componentDidMount() {
this.props.app.pdbIdLoaded.subscribe(() => this.setState({
structureView: this.props.app.structureView
}))
}
render() {
const { structureView } = this.state
return <div style={{width: '100%', height: '100%'}}>
<div style={{float: 'left', width: '70%', height: '100%'}}>
<Viewport app={this.props.app} />
</div>
<div style={{float: 'right', width: '25%', height: '100%'}}>
{structureView ? <StructureComponent structureView={structureView} /> : ''}
</div>
</div>;
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { StructureView } from '../view';
// export function FileInput (props: {
// accept: string
// onChange: (v: FileList | null) => void,
// }) {
// return <input
// accept={props.accept || '*.*'}
// type='file'
// onChange={e => props.onChange.call(null, e.target.files)}
// />
// }
export interface StructureComponentProps {
structureView: StructureView
}
export interface StructureComponentState {
label: string
assemblyId: string
assemblyIds: { id: string, label: string }[]
symmetryFeatureId: number
symmetryFeatureIds: { id: number, label: string }[]
}
export class StructureComponent extends React.Component<StructureComponentProps, StructureComponentState> {
state = {
label: this.props.structureView.label,
assemblyId: this.props.structureView.assemblyId,
assemblyIds: this.props.structureView.getAssemblyIds(),
symmetryFeatureId: this.props.structureView.symmetryFeatureId,
symmetryFeatureIds: this.props.structureView.getSymmetryFeatureIds()
}
componentWillMount() {
const sv = this.props.structureView
this.setState({
...this.state,
label: sv.label,
assemblyId: sv.assemblyId,
assemblyIds: sv.getAssemblyIds(),
symmetryFeatureId: sv.symmetryFeatureId,
symmetryFeatureIds: sv.getSymmetryFeatureIds()
})
}
async update(state: Partial<StructureComponentState>) {
const sv = this.props.structureView
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,
assemblyId: sv.assemblyId,
assemblyIds: sv.getAssemblyIds(),
symmetryFeatureId: sv.symmetryFeatureId,
symmetryFeatureIds: sv.getSymmetryFeatureIds()
}
this.setState(newState)
}
render() {
const { label, assemblyIds, symmetryFeatureIds } = this.state
const assemblyIdOptions = assemblyIds.map(a => {
return <option key={a.id} value={a.id}>{a.label}</option>
})
const symmetryFeatureIdOptions = symmetryFeatureIds.map(f => {
return <option key={f.id} value={f.id}>{f.label}</option>
})
return <div>
<div>
<span>{label}</span>
</div>
<div>
<div>
<span>Assembly</span>
<select
value={this.state.assemblyId}
onChange={(e) => {
this.update({ assemblyId: e.target.value })
}}
>
{assemblyIdOptions}
</select>
</div>
<div>
<span>Symmetry Feature</span>
<select
value={this.state.symmetryFeatureId}
onChange={(e) => {
this.update({ symmetryFeatureId: parseInt(e.target.value) })
}}
>
{symmetryFeatureIdOptions}
</select>
</div>
</div>
</div>;
}
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { App } from '../app';
import { MarkerAction } from 'mol-geo/util/marker-data';
import { EveryLoci } from 'mol-model/loci';
import { labelFirst } from 'mol-view/label';
interface ViewportProps {
app: App
}
interface ViewportState {
noWebGl: boolean,
info: string
}
export class Viewport extends React.Component<ViewportProps, ViewportState> {
private container: HTMLDivElement | null = null;
private canvas: HTMLCanvasElement | null = null;
state: ViewportState = {
noWebGl: false,
info: ''
};
handleResize() {
this.props.app.viewer.handleResize()
}
componentDidMount() {
if (!this.canvas || !this.container || !this.props.app.initViewer(this.canvas, this.container)) {
this.setState({ noWebGl: true });
}
this.handleResize()
const viewer = this.props.app.viewer
viewer.input.resize.subscribe(() => this.handleResize())
viewer.input.move.subscribe(({x, y, inside, buttons}) => {
if (!inside || buttons) return
const p = viewer.identify(x, y)
const loci = viewer.getLoci(p)
viewer.mark(EveryLoci, MarkerAction.RemoveHighlight)
viewer.mark(loci, MarkerAction.Highlight)
const label = labelFirst(loci)
const info = `${label}`
this.setState({ info })
})
}
componentWillUnmount() {
if (super.componentWillUnmount) super.componentWillUnmount();
// TODO viewer cleanup
}
renderMissing() {
return <div>
<div>
<p><b>WebGL does not seem to be available.</b></p>
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
<p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p>
</div>
</div>
}
render() {
if (this.state.noWebGl) return this.renderMissing();
return <div style={{ backgroundColor: 'rgb(0, 0, 0)', width: '100%', height: '100%'}}>
<div ref={elm => this.container = elm} style={{width: '100%', height: '100%'}}>
<canvas ref={elm => this.canvas = elm}></canvas>
</div>
<div
style={{
position: 'absolute',
top: 10,
left: 10,
padding: 10,
color: 'lightgrey',
background: 'rgba(0, 0, 0, 0.2)'
}}
>
{this.state.info}
</div>
</div>;
}
}
\ No newline at end of file
......@@ -4,12 +4,20 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Canvas</title>
<style>
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container" style="width:1024px; height: 768px;">
<canvas id="canvas"></canvas>
</div>
<span id="info"></span>
<div id="app" style="width: 100%; height: 100%"></div>
<script type="text/javascript" src="./index.js"></script>
</body>
</html>
\ No newline at end of file
......@@ -4,57 +4,18 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import './index.html'
import Viewer from 'mol-view/viewer';
import { EveryLoci } from 'mol-model/loci';
import { MarkerAction } from 'mol-geo/util/marker-data';
import { labelFirst } from 'mol-view/label';
import { getCifFromUrl, getModelFromMmcif } from './util';
import { SymmetryView } from './assembly-symmetry';
const container = document.getElementById('container')
if (!container) throw new Error('Can not find element with id "container".')
const canvas = document.getElementById('canvas') as HTMLCanvasElement
if (!canvas) throw new Error('Can not find element with id "canvas".')
const info = document.getElementById('info')
if (!info) throw new Error('Can not find element with id "info".')
import * as React from 'react'
import * as ReactDOM from 'react-dom'
const viewer = Viewer.create(canvas, container)
viewer.animate()
viewer.input.resize.subscribe(() => {
// do whatever appropriate
})
viewer.input.move.subscribe(({x, y, inside, buttons}) => {
if (!inside || buttons) return
const p = viewer.identify(x, y)
const loci = viewer.getLoci(p)
viewer.mark(EveryLoci, MarkerAction.RemoveHighlight)
viewer.mark(loci, MarkerAction.Highlight)
const label = labelFirst(loci)
info.innerText = `${label}`
})
import './index.html'
async function init() {
const assembly = '1'
import { App } from './app';
import { AppComponent } from './component/app';
const cif = await getCifFromUrl('https://files.rcsb.org/download/4hhb.cif')
const model = await getModelFromMmcif(cif)
const view = await SymmetryView(model, assembly)
viewer.center(view.structure.boundary.sphere.center)
viewer.add(view.cartoon)
viewer.add(view.ballAndStick)
viewer.add(view.axes)
const elm = document.getElementById('app') as HTMLElement
if (!elm) throw new Error('Can not find element with id "app".')
// ensure the added representations get rendered, i.e. without mouse input
viewer.requestDraw()
}
const app = new App()
ReactDOM.render(React.createElement(AppComponent, { app }), elm);
init()
\ No newline at end of file
app.loadPdbId('2ONK')
\ No newline at end of file
......@@ -5,7 +5,7 @@
*/
import CIF, { CifBlock } from 'mol-io/reader/cif'
import { readUrlAs } from "mol-util/read";
import { readUrlAs } from 'mol-util/read';
import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure';
// import { parse as parseObj } from 'mol-io/reader/obj/parser'
......@@ -25,16 +25,15 @@ export async function getCifFromUrl(url: string) {
return parsed.result.blocks[0]
}
export async function getModelFromMmcif(cif: CifBlock) {
const models = await Model.create(Format.mmCIF(cif)).run()
return models[0]
export async function getModelsFromMmcif(cif: CifBlock) {
return await Model.create(Format.mmCIF(cif)).run()
}
export async function getStructureFromModel(model: Model, assembly = '0') {
export async function getStructureFromModel(model: Model, assembly: string) {
const assemblies = model.symmetry.assemblies
if (assemblies.length) {
return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run()
} else {
if (assembly === '0') {
return Structure.ofModel(model)
} else if (assemblies.find(a => a.id === assembly)) {
return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run()
}
}
\ No newline at end of file
......@@ -4,54 +4,213 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Model, Structure } from "mol-model/structure";
import { Model, Structure } from 'mol-model/structure';
import { CartoonRepresentation } from 'mol-geo/representation/structure/representation/cartoon';
import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick';
import { getStructureFromModel } from "./util";
// import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick';
import { getStructureFromModel } from './util';
import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry';
import { ShapeRepresentation, ShapeProps } from 'mol-geo/representation/shape';
import { getAxesShape } from './assembly-symmetry';
import Viewer from 'mol-view/viewer';
export interface StructureView {
readonly label: string
readonly models: ReadonlyArray<Model>
readonly structure: Structure | undefined
readonly assemblySymmetry: AssemblySymmetry | undefined
export interface DefaultView {
readonly model: Model
readonly structure: Structure
readonly cartoon: CartoonRepresentation
readonly ballAndStick: BallAndStickRepresentation
setAssembly(assembly: string): void
// readonly ballAndStick: BallAndStickRepresentation
readonly axes: ShapeRepresentation<ShapeProps>
readonly modelId: number
readonly assemblyId: string
readonly symmetryFeatureId: number
setAssembly(assembly: string): Promise<void>
getAssemblyIds(): { id: string, label: string }[]
setSymmetryFeature(symmetryFeature: number): Promise<void>
getSymmetryFeatureIds(): { id: number, label: string }[]
destroy: () => void
}
export async function DefaultView(model: Model, assembly: string): Promise<DefaultView> {
interface StructureViewProps {
assembly?: string
symmetryFeature?: number
}
export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps): Promise<StructureView> {
const cartoon = CartoonRepresentation()
const ballAndStick = BallAndStickRepresentation()
let structure: Structure
// const ballAndStick = BallAndStickRepresentation()
const axes = ShapeRepresentation()
let label: string
let model: Model | undefined
let assemblySymmetry: AssemblySymmetry | undefined
let structure: Structure | undefined
let modelId: number
let assemblyId: string
let symmetryFeatureId: number
async function setAssembly(assembly: string) {
structure = await getStructureFromModel(model, assembly)
await createRepr()
async function setModel(newModelId: number, newAssemblyId?: string, newSymmetryFeatureId?: number) {
modelId = newModelId
model = models[modelId]
await AssemblySymmetry.attachFromCifOrAPI(model)
assemblySymmetry = AssemblySymmetry.get(model)
await setAssembly(newAssemblyId, newSymmetryFeatureId)
}
async function setAssembly(newAssemblyId?: string, newSymmetryFeatureId?: number) {
if (newAssemblyId !== undefined) {
assemblyId = newAssemblyId
} else if (model && model.symmetry.assemblies.length) {
assemblyId = model.symmetry.assemblies[0].id
} else if (model) {
assemblyId = '0'
} else {
assemblyId = '-1'
}
await getStructure()
await createStructureRepr()
await setSymmetryFeature(newSymmetryFeatureId)
}
async function createRepr() {
await cartoon.create(structure, {
colorTheme: { name: 'chain-id' },
sizeTheme: { name: 'uniform', value: 0.2 },
useFog: false // TODO fog not working properly
}).run()
await ballAndStick.create(structure, {
colorTheme: { name: 'element-symbol' },
sizeTheme: { name: 'uniform', value: 0.1 },
useFog: false // TODO fog not working properly
}).run()
function getAssemblyIds() {
const assemblyIds: { id: string, label: string }[] = [
{ id: '0', label: '0: model' }
]
if (model) model.symmetry.assemblies.forEach(a => {
assemblyIds.push({ id: a.id, label: `${a.id}: ${a.details}` })
})
return assemblyIds
}
await setAssembly(assembly)
async function setSymmetryFeature(newSymmetryFeatureId?: number) {
if (newSymmetryFeatureId !== undefined) {
symmetryFeatureId = newSymmetryFeatureId
} else if (assemblySymmetry) {
const f = assemblySymmetry.getFeatures(assemblyId)
if (f._rowCount) {
symmetryFeatureId = f.id.value(0)
} else {
symmetryFeatureId = -1
}
} else {
symmetryFeatureId = -1
}
await createSymmetryRepr()
}
function getSymmetryFeatureIds() {
const symmetryFeatureIds: { id: number, label: string }[] = []
if (assemblySymmetry) {
const symmetryFeatures = assemblySymmetry.getFeatures(assemblyId)
for (let i = 0, il = symmetryFeatures._rowCount; i < il; ++i) {
const id = symmetryFeatures.id.value(i)
const symmetry = symmetryFeatures.symmetry_value.value(i)
const type = symmetryFeatures.type.value(i)
const stoichiometry = symmetryFeatures.stoichiometry_value.value(i)
const label = `${id}: ${symmetry} ${type} ${stoichiometry}`
symmetryFeatureIds.push({ id, label })
}
}
return symmetryFeatureIds
}
async function getStructure() {
if (model) structure = await getStructureFromModel(model, assemblyId)
if (model && structure) {
label = `${model.label} - Assembly ${assemblyId}`
} else {
label = ''
}
await createStructureRepr()
}
async function createStructureRepr() {
if (structure) {
await cartoon.create(structure, {
colorTheme: { name: 'chain-id' },
sizeTheme: { name: 'uniform', value: 0.2 },
useFog: false // TODO fog not working properly
}).run()
// await ballAndStick.create(structure, {
// colorTheme: { name: 'element-symbol' },
// sizeTheme: { name: 'uniform', value: 0.1 },
// useFog: false // TODO fog not working properly
// }).run()
viewer.center(structure.boundary.sphere.center)
} else {
cartoon.destroy()
// ballAndStick.destroy()
}
viewer.add(cartoon)
// viewer.add(ballAndStick)
}
async function createSymmetryRepr() {
if (assemblySymmetry) {
const features = assemblySymmetry.getFeatures(assemblyId)
if (features._rowCount) {
const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry)
if (axesShape) {
// getClusterColorTheme(symmetryFeatureId, assemblySymmetry)
await axes.create(axesShape, {
colorTheme: { name: 'shape-group' },
// colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
useFog: false // TODO fog not working properly
}).run()
} else {
axes.destroy()
}
} else {
axes.destroy()
}
} else {
axes.destroy()
}
viewer.add(axes)
viewer.requestDraw()
}
await setModel(0, props.assembly, props.symmetryFeature)
return {
model,
get label() { return label },
models,
get structure() { return structure },
get assemblySymmetry() { return assemblySymmetry },
cartoon,
ballAndStick,
// ballAndStick,
axes,
get modelId() { return modelId },
get assemblyId() { return assemblyId },
get symmetryFeatureId() { return symmetryFeatureId },
setAssembly,
getAssemblyIds,
setSymmetryFeature,
getSymmetryFeatureIds,
destroy: () => {
viewer.remove(cartoon)
// viewer.remove(ballAndStick)
viewer.remove(axes)
viewer.requestDraw()
setAssembly
cartoon.destroy()
// ballAndStick.destroy()
axes.destroy()
}
}
}
......
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