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

Basic sequence view with loci event raising

parent 0c6670e8
No related branches found
No related tags found
No related merge requests found
Showing with 185 additions and 11 deletions
......@@ -51,17 +51,34 @@ This project builds on experience from previous solutions:
npm run watch-extra
### Build/watch mol-viewer
Build:
**Build**
npm run build
npm run build-viewer
Watch:
**Watch**
npm run watch
npm run watch-extra
npm run watch-viewer
**Run**
If not installed previously:
npm install -g http-server
...or a similar solution.
From the root of the project:
http-server -p PORT-NUMBER
and navigate to `build/viewer`
## Contributing
Just open an issue or make a pull request. All contributions are welcome.
......
......@@ -22,6 +22,7 @@ import { EntityTree } from 'mol-app/ui/entity/tree';
import { EntityTreeController } from 'mol-app/controller/entity/tree';
import { TransformListController } from 'mol-app/controller/transform/list';
import { TransformList } from 'mol-app/ui/transform/list';
import { SequenceView } from 'mol-app/ui/visualization/sequence-view';
const elm = document.getElementById('app')
if (!elm) throw new Error('Can not find element with id "app".')
......@@ -45,6 +46,14 @@ targets[LayoutRegion.Bottom].components.push({
isStatic: true
});
targets[LayoutRegion.Top].components.push({
key: 'molstar-sequence-view',
controller: ctx.components.sequenceView,
region: LayoutRegion.Top,
view: SequenceView,
isStatic: true
});
targets[LayoutRegion.Main].components.push({
key: 'molstar-background-jobs',
controller: new JobsController(ctx, 'Background'),
......
......@@ -15,6 +15,7 @@ import { Stage } from 'mol-view/stage';
import { AnyTransform } from 'mol-view/state/transform';
import { BehaviorSubject } from 'rxjs';
import { AnyEntity } from 'mol-view/state/entity';
import { SequenceViewController } from '../controller/visualization/sequence-view';
export class Settings {
private settings = new Map<string, any>();
......@@ -35,11 +36,16 @@ export class Context {
logger = new Logger(this);
performance = new PerformanceMonitor();
stage = new Stage();
stage = new Stage(this);
viewport = new ViewportController(this);
layout: LayoutController;
settings = new Settings();
// TODO: this is a temporary solution
components = {
sequenceView: new SequenceViewController(this)
};
currentEntity = new BehaviorSubject(undefined) as BehaviorSubject<AnyEntity | undefined>
currentTransforms = new BehaviorSubject([] as AnyTransform[])
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { shallowClone } from 'mol-util';
import { Context } from '../../context/context'
import { Controller } from '../controller';
import { Structure } from 'mol-model/structure';
export const DefaultSequenceViewState = {
structure: void 0 as (Structure | undefined)
}
export type SequenceViewState = typeof DefaultSequenceViewState
export class SequenceViewController extends Controller<SequenceViewState> {
constructor(context: Context) {
super(context, shallowClone(DefaultSequenceViewState));
}
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ import { Dispatcher } from '../service/dispatcher'
import { LayoutState } from '../controller/layout';
import { ViewportOptions } from '../controller/visualization/viewport';
import { Job } from '../service/job';
import { Element } from 'mol-model/structure'
const Lane = Dispatcher.Lane;
......@@ -31,3 +32,7 @@ export namespace LayoutEvents {
export const SetState = Event.create<Partial<LayoutState>>('lm.cmd.Layout.SetState', Lane.Slow);
export const SetViewportOptions = Event.create<ViewportOptions>('bs.cmd.Layout.SetViewportOptions', Lane.Slow);
}
export namespace InteractivityEvents {
export const HighlightElementLoci = Event.create<Element.Loci | undefined>('bs.Interactivity.HighlightElementLoci', Lane.Slow);
}
.molstar-sequence-view-wrap {
position: absolute;
right: 0;
top: 0;
left: 0;
bottom: 0;
overflow: hidden;
}
\ No newline at end of file
......@@ -23,7 +23,7 @@
overflow: hidden;
}
.molstar-layout-main, .molstar-layout-bottom {
.molstar-layout-main, .molstar-layout-bottom, .molstar-layout-top {
.molstar-layout-static {
left: 0;
right: 0;
......
......@@ -35,4 +35,5 @@
@import 'components/misc';
@import 'components/panel';
@import 'components/slider';
@import 'components/viewport';
\ No newline at end of file
@import 'components/viewport';
@import 'components/sequence-view';
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react'
import { View } from '../view';
import { SequenceViewController } from '../../controller/visualization/sequence-view';
import { Structure, StructureSequence, Queries, Selection } from 'mol-model/structure';
import { Context } from '../../context/context';
import { InteractivityEvents } from '../../event/basic';
import { SyncRuntimeContext } from 'mol-task/execution/synchronous';
export class SequenceView extends View<SequenceViewController, {}, {}> {
render() {
const s = this.controller.latestState.structure;
if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
const seqs = Structure.getModels(s)[0].sequence.sequences;
return <div className='molstar-sequence-view-wrap'>
{seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
</div>;
}
}
function createQuery(entityId: string, label_seq_id: number) {
return Queries.generators.atoms({
entityTest: l => Queries.props.entity.id(l) === entityId,
residueTest: l => Queries.props.residue.label_seq_id(l) === label_seq_id
});
}
// TODO: this is really ineffective and should be done using a canvas.
class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
async raiseInteractityEvent(seqId?: number) {
if (typeof seqId === 'undefined') {
InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0);
return;
}
const query = createQuery(this.props.seq.entityId, seqId);
const loci = Selection.toLoci(await query(this.props.structure, SyncRuntimeContext));
InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, loci);
}
render() {
const { ctx, seq } = this.props;
const { offset, sequence } = seq.sequence;
const elems: JSX.Element[] = [];
for (let i = 0, _i = sequence.length; i < _i; i++) {
elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
}
return <div style={{ wordWrap: 'break-word' }}>
<span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset}&nbsp;</span>
{elems}
</div>;
}
}
class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
state = { isHighlighted: false }
mouseEnter = () => {
this.setState({ isHighlighted: true });
this.props.parent.raiseInteractityEvent(this.props.seqId);
}
mouseLeave = () => {
this.setState({ isHighlighted: false });
this.props.parent.raiseInteractityEvent();
}
render() {
return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
{this.props.letter}
</span>;
}
}
\ No newline at end of file
......@@ -8,5 +8,6 @@ import Model from './model/model'
import * as Types from './model/types'
import Format from './model/format'
import { ModelSymmetry } from './model/properties/symmetry'
import StructureSequence from './model/properties/sequence'
export { Model, Types, Format, ModelSymmetry }
\ No newline at end of file
export { Model, Types, Format, ModelSymmetry, StructureSequence }
\ No newline at end of file
......@@ -27,6 +27,7 @@ export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHie
const { entity_id, num, mon_id } = cif.entity_poly_seq;
const byEntityKey: StructureSequence['byEntityKey'] = {};
const sequences: StructureSequence.Entity[] = [];
const count = entity_id.rowCount;
let i = 0;
......@@ -38,14 +39,17 @@ export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHie
const id = entity_id.value(start);
const _compId = Column.window(mon_id, start, i);
const _num = Column.window(num, start, i);
const entityKey = entities.getEntityIndex(id);
byEntityKey[entities.getEntityIndex(id)] = {
byEntityKey[entityKey] = {
entityId: id,
compId: _compId,
num: _num,
sequence: Sequence.ofResidueNames(_compId, _num)
};
sequences.push(byEntityKey[entityKey]);
}
return { byEntityKey };
return { byEntityKey, sequences };
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import { Entities } from './common';
import { Sequence } from '../../../sequence';
interface StructureSequence {
readonly sequences: ReadonlyArray<StructureSequence.Entity>,
readonly byEntityKey: { [key: number]: StructureSequence.Entity }
}
......@@ -27,6 +28,7 @@ namespace StructureSequence {
const { chainSegments, residueSegments } = hierarchy
const byEntityKey: StructureSequence['byEntityKey'] = { };
const sequences: StructureSequence.Entity[] = [];
for (let cI = 0, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) {
const entityKey = hierarchy.entityKey[cI];
......@@ -52,9 +54,11 @@ namespace StructureSequence {
num,
sequence: Sequence.ofResidueNames(compId, num)
};
sequences.push(byEntityKey[entityKey]);
}
return { byEntityKey }
return { byEntityKey, sequences };
}
}
......
......@@ -5,8 +5,9 @@
*/
import { HashSet } from 'mol-data/generic'
import { Structure } from '../structure'
import { Structure, Element, Unit } from '../structure'
import { structureUnion } from './utils/structure';
import { SortedArray } from 'mol-data/int';
// A selection is a pair of a Structure and a sequence of unique AtomSets
type Selection = Selection.Singletons | Selection.Sequence
......@@ -34,6 +35,16 @@ namespace Selection {
return structureUnion(sel.source, sel.structures);
}
export function toLoci(sel: Selection): Element.Loci {
const loci: { unit: Unit, indices: SortedArray }[] = [];
for (const unit of unionStructure(sel).units) {
loci[loci.length] = { unit, indices: SortedArray.indicesOf(sel.source.unitMap.get(unit.id).elements, unit.elements) }
}
return Element.Loci(loci);
}
export interface Builder {
add(structure: Structure): void,
getSelection(): Selection
......
......@@ -10,6 +10,7 @@ import { Progress } from 'mol-task';
import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBond } from './state/transform';
import { UrlEntity } from './state/entity';
import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
import { Context } from 'mol-app/context/context';
// export const ColorTheme = {
// 'atom-index': {},
......@@ -30,7 +31,7 @@ export class Stage {
viewer: Viewer
ctx = new StateContext(Progress.format)
constructor() {
constructor(public globalContext: Context) {
}
......@@ -48,6 +49,8 @@ export class Stage {
const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity)
StructureToSpacefill.apply(this.ctx, structureEntity, spacefillProps)
StructureToBond.apply(this.ctx, structureEntity, spacefillProps) // TODO props
this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });
}
loadPdbid (pdbid: string) {
......
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