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

wip, per-chain sequence widget

parent c3f937e1
No related branches found
No related tags found
No related merge requests found
......@@ -9,19 +9,29 @@ import * as React from 'react'
import { PluginUIComponent } from './base';
import { StateTreeSpine } from '../../mol-state/tree/spine';
import { PluginStateObject as SO } from '../state/objects';
import { Sequence } from './sequence/sequence';
import { Structure } from '../../mol-model/structure';
import { SequenceWrapper } from './sequence/util';
import { PolymerSequenceWrapper } from './sequence/polymer';
import { StructureElementSelectionManager } from '../util/structure-element-selection';
import { MarkerAction } from '../../mol-util/marker-action';
import { PolymerSequence } from './sequence/polymer';
import { StructureSeq, markResidue } from './sequence/util';
function getStructureSeqKey(structureSeq: StructureSeq) {
const { structure, seq } = structureSeq
const strucHash = structure.parent ? structure.parent.hashCode : structure.hashCode
return `${strucHash}|${seq.entityId}`
function getSequenceWrappersForStructure(structure: Structure, structureSelection: StructureElementSelectionManager) {
const sequenceWrappers: SequenceWrapper.Any[] = []
structure.units.forEach(unit => {
if (unit.polymerElements.length === 0) return
const sw = new PolymerSequenceWrapper({ structure, unit })
sw.markResidue(structureSelection.get(structure), MarkerAction.Select)
sequenceWrappers.push(sw)
})
return sequenceWrappers
}
export class SequenceView extends PluginUIComponent<{ }, { }> {
private spine: StateTreeSpine.Impl
private markerArrays = new Map<string, Uint8Array>()
componentDidMount() {
this.spine = new StateTreeSpine.Impl(this.plugin.state.dataState.cells);
......@@ -39,20 +49,6 @@ export class SequenceView extends PluginUIComponent<{ }, { }> {
});
}
private getMarkerArray(structureSeq: StructureSeq): Uint8Array {
const { structure, seq } = structureSeq
const key = getStructureSeqKey(structureSeq)
let markerArray = this.markerArrays.get(key)
if (!markerArray) {
markerArray = new Uint8Array(seq.sequence.sequence.length)
this.markerArrays.set(key, markerArray)
}
const loci = this.plugin.helpers.structureSelection.get(structure)
markerArray.fill(0)
markResidue(loci, structureSeq, markerArray, MarkerAction.Select)
return markerArray
}
private getStructure() {
const so = this.spine && this.spine.getRootOfType(SO.Molecule.Structure)
return so && so.data
......@@ -64,12 +60,11 @@ export class SequenceView extends PluginUIComponent<{ }, { }> {
<div className='msp-sequence-entity'>No structure available</div>
</div>;
const seqs = structure.models[0].sequence.sequences;
const { structureSelection } = this.plugin.helpers
const sequenceWrappers = getSequenceWrappersForStructure(structure, structureSelection)
return <div className='msp-sequence'>
{seqs.map((seq, i) => {
const structureSeq = { structure, seq }
const markerArray = this.getMarkerArray(structureSeq)
return <PolymerSequence key={i} structureSeq={structureSeq} markerArray={markerArray} />
{sequenceWrappers.map((sequenceWrapper, i) => {
return <Sequence key={i} sequenceWrapper={sequenceWrapper} />
})}
</div>;
}
......
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StructureSelection, StructureQuery, Structure, Queries, StructureProperties as SP, StructureElement, Unit } from '../../../mol-model/structure';
import { SequenceWrapper } from './util';
import { OrderedSet, Interval } from '../../../mol-data/int';
import { Loci } from '../../../mol-model/loci';
export type StructureUnit = { structure: Structure, unit: Unit }
export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
private readonly location = StructureElement.create()
private entityId: string
private label_asym_id: string
eachResidue(loci: Loci, apply: (interval: Interval) => boolean) {
let changed = false
const { structure } = this.data
if (!StructureElement.isLoci(loci)) return false
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
const { location, entityId, label_asym_id } = this
for (const e of loci.elements) {
let rIprev = -1
location.unit = e.unit
const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments
OrderedSet.forEach(e.indices, v => {
location.element = e.unit.elements[v]
const rI = residueIndex[location.element]
// avoid checking for the same residue multiple times
if (rI !== rIprev) {
if (SP.entity.id(location) !== entityId) return
if (SP.chain.label_asym_id(location) !== label_asym_id) return
if (apply(getSeqIdInterval(location))) changed = true
rIprev = rI
}
})
}
return changed
}
getLoci(seqId: number) {
const query = createResidueQuery(this.entityId, seqId, this.label_asym_id);
return StructureSelection.toLoci2(StructureQuery.run(query, this.data.structure));
}
constructor(readonly data: StructureUnit) {
super()
const l = this.location
l.unit = data.unit
l.element = data.unit.elements[0]
this.entityId = SP.entity.id(l)
this.label_asym_id = SP.chain.label_asym_id(l)
this.label = `${this.label_asym_id}|${this.entityId}`
this.sequence = data.unit.model.sequence.byEntityKey[SP.entity.key(l)].sequence
this.markerArray = new Uint8Array(this.sequence.sequence.length)
}
}
function createResidueQuery(entityId: string, label_seq_id: number, label_asym_id: string) {
return Queries.generators.atoms({
entityTest: ctx => {
return SP.entity.id(ctx.element) === entityId
},
chainTest: ctx => {
return SP.chain.label_asym_id(ctx.element) === label_asym_id
},
residueTest: ctx => {
if (ctx.element.unit.kind === Unit.Kind.Atomic) {
return SP.residue.label_seq_id(ctx.element) === label_seq_id
} else {
return (
SP.coarse.seq_id_begin(ctx.element) <= label_seq_id &&
SP.coarse.seq_id_end(ctx.element) >= label_seq_id
)
}
}
});
}
/** Zero-indexed */
function getSeqIdInterval(location: StructureElement): Interval {
const { unit, element } = location
const { model } = unit
switch (unit.kind) {
case Unit.Kind.Atomic:
const residueIndex = model.atomicHierarchy.residueAtomSegments.index[element]
const seqId = model.atomicHierarchy.residues.label_seq_id.value(residueIndex)
return Interval.ofSingleton(seqId - 1)
case Unit.Kind.Spheres:
return Interval.ofRange(
model.coarseHierarchy.spheres.seq_id_begin.value(element) - 1,
model.coarseHierarchy.spheres.seq_id_end.value(element) - 1
)
case Unit.Kind.Gaussians:
return Interval.ofRange(
model.coarseHierarchy.gaussians.seq_id_begin.value(element) - 1,
model.coarseHierarchy.gaussians.seq_id_end.value(element) - 1
)
}
}
\ No newline at end of file
/**
* Copyright (c) 2018-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 * as React from 'react'
import { StructureSelection, StructureQuery } from '../../../mol-model/structure';
import { createResidueQuery } from './util';
import { Residue } from './residue';
import { BaseSequence } from './base';
export class PolymerSequence extends BaseSequence {
getLoci(seqId: number) {
const { structure, seq } = this.props.structureSeq
const query = createResidueQuery(seq.entityId, seqId);
return StructureSelection.toLoci2(StructureQuery.run(query, structure));
}
render() {
const { markerData } = this.state;
const { seq } = this.props.structureSeq;
const { offset, sequence } = seq.sequence;
const elems: JSX.Element[] = [];
for (let i = 0, _i = sequence.length; i < _i; i++) {
elems[elems.length] = <Residue seqId={offset + i + 1} letter={sequence[i]} parent={this} marker={markerData.value[i]} key={i} />;
}
return <div
className='msp-sequence-entity'
onContextMenu={this.contextMenu}
onMouseDown={this.mouseDown}
>
<span style={{ fontWeight: 'bold' }}>{seq.entityId}:{offset}&nbsp;</span>
{elems}
</div>;
}
}
......@@ -8,9 +8,9 @@
import * as React from 'react'
import { PurePluginUIComponent } from '../base';
import { getButtons, getModifiers } from '../../../mol-util/input/input-observer';
import { BaseSequence } from './base';
import { Sequence } from './sequence';
export class Residue extends PurePluginUIComponent<{ seqId: number, letter: string, parent: BaseSequence, marker: number }> {
export class Residue extends PurePluginUIComponent<{ seqId: number, letter: string, parent: Sequence<any>, marker: number }> {
mouseEnter = (e: React.MouseEvent) => {
const modifiers = getModifiers(e.nativeEvent)
......
......@@ -6,39 +6,44 @@
*/
import * as React from 'react'
import { StructureSelection, StructureQuery } from '../../../mol-model/structure';
import { PluginUIComponent } from '../base';
import { Interactivity } from '../../util/interactivity';
import { MarkerAction } from '../../../mol-util/marker-action';
import { ButtonsType, ModifiersKeys, getButtons, getModifiers } from '../../../mol-util/input/input-observer';
import { ValueBox } from '../../../mol-util';
import { createResidueQuery, markResidue, StructureSeq } from './util';
import { Residue } from './residue';
import { SequenceWrapper } from './util';
type BaseSequenceProps = { structureSeq: StructureSeq, markerArray: Uint8Array }
type BaseSequenceState = { markerData: ValueBox<Uint8Array> }
type SequenceProps = { sequenceWrapper: SequenceWrapper.Any }
type SequenceState = { markerData: ValueBox<Uint8Array> }
function getState(markerData: ValueBox<Uint8Array>) {
return { markerData: ValueBox.withValue(markerData, markerData.value) }
}
// TODO: this is really inefficient and should be done using a canvas.
export class BaseSequence extends PluginUIComponent<BaseSequenceProps, BaseSequenceState> {
export class Sequence<P extends SequenceProps> extends PluginUIComponent<P, SequenceState> {
state = {
markerData: ValueBox.create(new Uint8Array(this.props.markerArray))
markerData: ValueBox.create(this.props.sequenceWrapper.markerArray)
}
private setMarkerData(markerData: ValueBox<Uint8Array>) {
this.setState(getState(markerData))
}
private lociHighlightProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
const { markerData } = this.state;
const changed = markResidue(loci.loci, this.props.structureSeq, markerData.value, action)
if (changed) this.setState({ markerData: ValueBox.withValue(markerData, markerData.value) })
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
if (changed) this.setMarkerData(this.state.markerData)
}
private lociSelectionProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
const { markerData } = this.state;
const changed = markResidue(loci.loci, this.props.structureSeq, markerData.value, action)
if (changed) this.setState({ markerData: ValueBox.withValue(markerData, markerData.value) })
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
if (changed) this.setMarkerData(this.state.markerData)
}
static getDerivedStateFromProps(nextProps: BaseSequenceProps, prevState: BaseSequenceState): BaseSequenceState | null {
if (prevState.markerData.value !== nextProps.markerArray) {
return { markerData: ValueBox.create(nextProps.markerArray) }
static getDerivedStateFromProps(nextProps: SequenceProps, prevState: SequenceState): SequenceState | null {
if (prevState.markerData.value !== nextProps.sequenceWrapper.markerArray) {
return getState(ValueBox.create(nextProps.sequenceWrapper.markerArray))
}
return null
}
......@@ -53,16 +58,10 @@ export class BaseSequence extends PluginUIComponent<BaseSequenceProps, BaseSeque
this.plugin.interactivity.lociSelections.removeProvider(this.lociSelectionProvider)
}
getLoci(seqId: number) {
const { structure, seq } = this.props.structureSeq
const query = createResidueQuery(seq.entityId, seqId);
return StructureSelection.toLoci2(StructureQuery.run(query, structure));
}
highlight(seqId?: number, modifiers?: ModifiersKeys) {
const ev = { current: Interactivity.Loci.Empty, modifiers }
if (seqId !== undefined) {
const loci = this.getLoci(seqId);
const loci = this.props.sequenceWrapper.getLoci(seqId);
if (loci.elements.length > 0) ev.current = { loci };
}
this.plugin.behaviors.interaction.highlight.next(ev)
......@@ -71,7 +70,7 @@ export class BaseSequence extends PluginUIComponent<BaseSequenceProps, BaseSeque
click(seqId: number | undefined, buttons: ButtonsType, modifiers: ModifiersKeys) {
const ev = { current: Interactivity.Loci.Empty, buttons, modifiers }
if (seqId !== undefined) {
const loci = this.getLoci(seqId);
const loci = this.props.sequenceWrapper.getLoci(seqId);
if (loci.elements.length > 0) ev.current = { loci };
}
this.plugin.behaviors.interaction.click.next(ev)
......@@ -89,8 +88,8 @@ export class BaseSequence extends PluginUIComponent<BaseSequenceProps, BaseSeque
render() {
const { markerData } = this.state;
const { seq } = this.props.structureSeq;
const { offset, sequence } = seq.sequence;
const { label } = this.props.sequenceWrapper
const { offset, sequence } = this.props.sequenceWrapper.sequence;
const elems: JSX.Element[] = [];
for (let i = 0, _i = sequence.length; i < _i; i++) {
......@@ -102,7 +101,7 @@ export class BaseSequence extends PluginUIComponent<BaseSequenceProps, BaseSeque
onContextMenu={this.contextMenu}
onMouseDown={this.mouseDown}
>
<span style={{ fontWeight: 'bold' }}>{seq.entityId}:{offset}&nbsp;</span>
<span style={{ fontWeight: 'bold' }}>{label}:{offset}&nbsp;</span>
{elems}
</div>;
}
......
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* 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 { Structure, StructureSequence, Queries, StructureProperties as SP, StructureElement, Unit } from '../../../mol-model/structure';
import { OrderedSet, Interval } from '../../../mol-data/int';
import { Interval } from '../../../mol-data/int';
import { Loci } from '../../../mol-model/loci';
import { applyMarkerAction, MarkerAction } from '../../../mol-util/marker-action';
import { MarkerAction, applyMarkerAction } from '../../../mol-util/marker-action';
import { StructureElement } from '../../../mol-model/structure';
import { Sequence } from '../../../mol-model/sequence';
export function createResidueQuery(entityId: string, label_seq_id: number) {
return Queries.generators.atoms({
entityTest: ctx => {
return SP.entity.id(ctx.element) === entityId
},
residueTest: ctx => {
if (ctx.element.unit.kind === Unit.Kind.Atomic) {
return SP.residue.label_seq_id(ctx.element) === label_seq_id
} else {
return (
SP.coarse.seq_id_begin(ctx.element) <= label_seq_id &&
SP.coarse.seq_id_end(ctx.element) >= label_seq_id
)
}
}
});
}
/** Zero-indexed */
export function getSeqIdInterval(location: StructureElement): Interval {
const { unit, element } = location
const { model } = unit
switch (unit.kind) {
case Unit.Kind.Atomic:
const residueIndex = model.atomicHierarchy.residueAtomSegments.index[element]
const seqId = model.atomicHierarchy.residues.label_seq_id.value(residueIndex)
return Interval.ofSingleton(seqId - 1)
case Unit.Kind.Spheres:
return Interval.ofRange(
model.coarseHierarchy.spheres.seq_id_begin.value(element) - 1,
model.coarseHierarchy.spheres.seq_id_end.value(element) - 1
)
case Unit.Kind.Gaussians:
return Interval.ofRange(
model.coarseHierarchy.gaussians.seq_id_begin.value(element) - 1,
model.coarseHierarchy.gaussians.seq_id_end.value(element) - 1
)
}
}
export { SequenceWrapper }
export type StructureSeq = { structure: Structure, seq: StructureSequence.Entity }
abstract class SequenceWrapper<D> {
label: string
data: D
markerArray: Uint8Array
sequence: Sequence
abstract eachResidue(loci: Loci, apply: (interval: Interval) => boolean): boolean
abstract getLoci(seqId: number): StructureElement.Loci
export function eachResidue(loci: Loci, structureSeq: StructureSeq, apply: (interval: Interval) => boolean) {
let changed = false
const { structure, seq } = structureSeq
if (!StructureElement.isLoci(loci)) return false
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
const l = StructureElement.create()
for (const e of loci.elements) {
l.unit = e.unit
OrderedSet.forEach(e.indices, v => {
l.element = e.unit.elements[v]
const entityId = SP.entity.id(l)
if (entityId === seq.entityId) {
if (apply(getSeqIdInterval(l))) changed = true
}
markResidue(loci: Loci, action: MarkerAction) {
return this.eachResidue(loci, (i: Interval) => {
return applyMarkerAction(this.markerArray, i, action)
})
}
return changed
}
export function markResidue(loci: Loci, structureSeq: StructureSeq, array: Uint8Array, action: MarkerAction) {
const { structure, seq } = structureSeq
return eachResidue(loci, { structure , seq }, (i: Interval) => {
return applyMarkerAction(array, i, action)
})
namespace SequenceWrapper {
export type Any = SequenceWrapper<any>
}
\ No newline at end of file
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