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

hetero sequence wrapper

parent 58a49a85
No related branches found
No related tags found
No related merge requests found
......@@ -139,6 +139,8 @@ namespace StructureElement {
}
export function remap(loci: Loci, structure: Structure): Loci {
if (structure === loci.structure) return loci
return Loci(structure, loci.elements.map(e => ({
unit: structure.unitMap.get(e.unit.id)!,
indices: e.indices
......
......@@ -11,12 +11,13 @@ import { StateTreeSpine } from '../../mol-state/tree/spine';
import { PluginStateObject as SO } from '../state/objects';
import { Sequence } from './sequence/sequence';
import { Structure, StructureElement, StructureProperties as SP } from '../../mol-model/structure';
import { SequenceWrapper } from './sequence/util';
import { SequenceWrapper } from './sequence/wrapper';
import { PolymerSequenceWrapper } from './sequence/polymer';
import { StructureElementSelectionManager } from '../util/structure-element-selection';
import { MarkerAction } from '../../mol-util/marker-action';
import { ParameterControls } from './controls/parameters';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { HeteroSequenceWrapper } from './sequence/hetero';
function opKey(l: StructureElement) {
const ids = SP.unit.pdbx_struct_oper_list_ids(l)
......@@ -27,19 +28,16 @@ function opKey(l: StructureElement) {
}
function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | undefined {
const { structure, entity, chain, operator } = state
const { structure, entityId, invariantUnitId, operatorKey } = state
const l = StructureElement.create()
for (let i = 0, il = structure.units.length; i < il; ++i) {
const unit = structure.units[i]
if (unit.polymerElements.length === 0) continue
for (const unit of structure.units) {
StructureElement.set(l, unit, unit.elements[0])
if (SP.entity.id(l) !== entity) continue
if (SP.chain.label_asym_id(l) !== chain) continue
if (opKey(l) !== operator) continue
if (SP.entity.id(l) !== entityId) continue
if (unit.invariantId !== invariantUnitId) continue
if (opKey(l) !== operatorKey) continue
// console.log('new PolymerSequenceWrapper', structureSelection.get(structure))
const sw = new PolymerSequenceWrapper({ structure, unit })
const Wrapper = unit.polymerElements.length ? PolymerSequenceWrapper : HeteroSequenceWrapper
const sw = new Wrapper({ structure, unit })
sw.markResidue(structureSelection.get(structure), MarkerAction.Select)
return sw
}
......@@ -50,74 +48,76 @@ function getEntityOptions(structure: Structure) {
const l = StructureElement.create()
const seen = new Set<string>()
structure.units.forEach(unit => {
if (unit.polymerElements.length === 0) return
for (const unit of structure.units) {
StructureElement.set(l, unit, unit.elements[0])
const id = SP.entity.id(l)
if (seen.has(id)) return
if (seen.has(id)) continue
const label = `${id}: ${SP.entity.pdbx_description(l).join(', ')}`
options.push([ id, label ])
seen.add(id)
})
}
if (options.length === 0) options.push(['', 'No entities'])
return options
}
function getChainOptions(structure: Structure, entityId: string) {
const options: [string, string][] = []
const options: [number, string][] = []
const l = StructureElement.create()
const seen = new Set<string>()
structure.units.forEach(unit => {
if (unit.polymerElements.length === 0) return
const seen = new Set<number>()
const water = new Map<string, number>()
for (const unit of structure.units) {
StructureElement.set(l, unit, unit.elements[0])
if (SP.entity.id(l) !== entityId) return
if (SP.entity.id(l) !== entityId) continue
const id = unit.invariantId
if (seen.has(id)) continue
const id = SP.chain.label_asym_id(l)
if (seen.has(id)) return
let label = `${SP.chain.label_asym_id(l)}: ${SP.chain.auth_asym_id(l)}`
if (SP.entity.type(l) === 'water') {
const count = water.get(label) || 1
water.set(label, count + 1)
label += ` #${count}`
}
const label = `${id}: ${SP.chain.auth_asym_id(l)}`
options.push([ id, label ])
seen.add(id)
})
}
if (options.length === 0) options.push(['', 'No chains'])
if (options.length === 0) options.push([-1, 'No chains'])
return options
}
function getOperatorOptions(structure: Structure, entityId: string, label_asym_id: string) {
function getOperatorOptions(structure: Structure, entityId: string, invariantUnitId: number) {
const options: [string, string][] = []
const l = StructureElement.create()
const seen = new Set<string>()
structure.units.forEach(unit => {
if (unit.polymerElements.length === 0) return
for (const unit of structure.units) {
StructureElement.set(l, unit, unit.elements[0])
if (SP.entity.id(l) !== entityId) return
if (SP.chain.label_asym_id(l) !== label_asym_id) return
if (SP.entity.id(l) !== entityId) continue
if (unit.invariantId !== invariantUnitId) continue
const id = opKey(l)
if (seen.has(id)) return
if (seen.has(id)) continue
const label = unit.conformation.operator.name
options.push([ id, label ])
seen.add(id)
})
}
if (options.length === 0) options.push(['', 'No operators'])
return options
}
type SequenceViewState = { structure: Structure, entity: string, chain: string, operator: string }
type SequenceViewState = { structure: Structure, entityId: string, invariantUnitId: number, operatorKey: string }
export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
private spine: StateTreeSpine.Impl
state = { structure: Structure.Empty, entity: '', chain: '', operator: '' }
state = { structure: Structure.Empty, entityId: '', invariantUnitId: -1, operatorKey: '' }
constructor(props: {}, context?: any) {
super(props, context);
......@@ -151,17 +151,17 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
private getInitialState(): SequenceViewState {
const structure = this.getStructure()
const entity = getEntityOptions(structure)[0][0]
const chain = getChainOptions(structure, entity)[0][0]
const operator = getOperatorOptions(structure, entity, chain)[0][0]
return { structure, entity, chain, operator }
const entityId = getEntityOptions(structure)[0][0]
const invariantUnitId = getChainOptions(structure, entityId)[0][0]
const operatorKey = getOperatorOptions(structure, entityId, invariantUnitId)[0][0]
return { structure, entityId, invariantUnitId, operatorKey }
}
private get params() {
const { structure, entity, chain } = this.state
const { structure, entityId, invariantUnitId } = this.state
const entityOptions = getEntityOptions(structure)
const chainOptions = getChainOptions(structure, entity)
const operatorOptions = getOperatorOptions(structure, entity, chain)
const chainOptions = getChainOptions(structure, entityId)
const operatorOptions = getOperatorOptions(structure, entityId, invariantUnitId)
return {
entity: PD.Select(entityOptions[0][0], entityOptions),
chain: PD.Select(chainOptions[0][0], chainOptions),
......@@ -169,20 +169,29 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
}
}
private get values(): PD.Values<SequenceView['params']> {
return {
entity: this.state.entityId,
chain: this.state.invariantUnitId,
operator: this.state.operatorKey
}
}
// TODO try to use selected option from previous state
private setParamProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
const state = { ...this.state }
switch (p.name) {
case 'entity':
state.entity = p.value
state.chain = getChainOptions(state.structure, state.entity)[0][0]
state.operator = getOperatorOptions(state.structure, state.entity, state.chain)[0][0]
state.entityId = p.value
state.invariantUnitId = getChainOptions(state.structure, state.entityId)[0][0]
state.operatorKey = getOperatorOptions(state.structure, state.entityId, state.invariantUnitId)[0][0]
break
case 'chain':
state.chain = p.value
state.operator = getOperatorOptions(state.structure, state.entity, state.chain)[0][0]
state.invariantUnitId = p.value
state.operatorKey = getOperatorOptions(state.structure, state.entityId, state.invariantUnitId)[0][0]
break
case 'operator':
state.operator = p.value
state.operatorKey = p.value
break
}
this.setState(state)
......@@ -196,7 +205,7 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
const sequenceWrapper = this.getSequenceWrapper()
return <div className='msp-sequence'>
<div className='msp-sequence-select'>
<ParameterControls params={this.params} values={this.state} onChange={this.setParamProps} />
<ParameterControls params={this.params} values={this.values} onChange={this.setParamProps} />
</div>
{sequenceWrapper !== undefined
? <Sequence sequenceWrapper={sequenceWrapper} />
......
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure, StructureElement, ResidueIndex } from '../../../mol-model/structure';
import { SequenceWrapper, StructureUnit } from './wrapper';
import { OrderedSet, Segmentation, Interval, SortedArray } from '../../../mol-data/int';
import { Loci } from '../../../mol-model/loci';
import { ColorNames } from '../../../mol-util/color/tables';
export class HeteroSequenceWrapper extends SequenceWrapper<StructureUnit> {
private readonly sequence: string[]
private readonly sequenceIndices: Map<ResidueIndex, number>
private readonly residueIndices: Map<number, ResidueIndex>
residueLabel(seqIdx: number) {
return this.sequence[seqIdx]
}
residueColor(seqIdx: number) {
return ColorNames.black
}
eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean) {
let changed = false
const { structure, unit } = this.data
if (StructureElement.isLoci(loci)) {
if (!Structure.areParentsEqual(loci.structure, structure)) return false
for (const e of loci.elements) {
if (e.unit.id === unit.id) {
const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments
OrderedSet.forEach(e.indices, v => {
const seqIdx = this.sequenceIndices.get(residueIndex[unit.elements[v]])
if (seqIdx !== undefined && apply(Interval.ofSingleton(seqIdx))) changed = true
})
}
}
} else if (Structure.isLoci(loci)) {
if (!Structure.areParentsEqual(loci.structure, structure)) return false
if (apply(Interval.ofBounds(0, this.length))) changed = true
}
return changed
}
getLoci(seqIdx: number) {
const elements: StructureElement.Loci['elements'][0][] = []
const rI = this.residueIndices.get(seqIdx)
if (rI !== undefined) {
const { unit } = this.data
const { offsets } = unit.model.atomicHierarchy.residueAtomSegments
const start = SortedArray.findPredecessorIndex(unit.elements, offsets[rI])
const end = SortedArray.findPredecessorIndex(unit.elements, offsets[rI + 1])
elements.push({ unit, indices: Interval.ofBounds(start, end) })
}
return StructureElement.Loci(this.data.structure, elements)
}
constructor(data: StructureUnit) {
const sequence: string[] = []
const sequenceIndices = new Map<ResidueIndex, number>()
const residueIndices = new Map<number, ResidueIndex>()
const residueIt = Segmentation.transientSegments(data.unit.model.atomicHierarchy.residueAtomSegments, data.unit.elements)
while (residueIt.hasNext) {
const { index } = residueIt.move()
sequenceIndices.set(index, sequence.length)
residueIndices.set(sequence.length, index)
sequence.push(data.unit.model.atomicHierarchy.residues.label_comp_id.value(index))
}
const length = sequence.length
const markerArray = new Uint8Array(length)
super(data, markerArray, length)
this.sequence = sequence
this.sequenceIndices = sequenceIndices
this.residueIndices = residueIndices
}
}
\ No newline at end of file
......@@ -5,15 +5,13 @@
*/
import { StructureSelection, StructureQuery, Structure, Queries, StructureProperties as SP, StructureElement, Unit, ElementIndex } from '../../../mol-model/structure';
import { SequenceWrapper } from './util';
import { SequenceWrapper, StructureUnit } from './wrapper';
import { OrderedSet, Interval, SortedArray } from '../../../mol-data/int';
import { Loci } from '../../../mol-model/loci';
import { Sequence } from '../../../mol-model/sequence';
import { MissingResidues } from '../../../mol-model/structure/model/properties/common';
import { ColorNames } from '../../../mol-util/color/tables';
export type StructureUnit = { structure: Structure, unit: Unit }
export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
private readonly sequence: Sequence
private readonly missing: MissingResidues
......@@ -25,6 +23,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
seqId(seqIdx: number) {
return this.sequence.offset + seqIdx + 1
}
residueLabel(seqIdx: number) {
return this.sequence.sequence[seqIdx]
}
......@@ -55,8 +54,8 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
return changed
}
getLoci(seqId: number) {
const query = createResidueQuery(this.data.unit.id, seqId);
getLoci(seqIdx: number) {
const query = createResidueQuery(this.data.unit.id, this.seqId(seqIdx));
return StructureSelection.toLoci2(StructureQuery.run(query, this.data.structure));
}
......@@ -66,7 +65,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
const length = sequence.sequence.length
const markerArray = new Uint8Array(length)
super(data, markerArray, sequence.sequence.length)
super(data, markerArray, length)
this.sequence = sequence
this.missing = data.unit.model.properties.missingResidues
......
......@@ -11,11 +11,11 @@ import { getButtons, getModifiers } from '../../../mol-util/input/input-observer
import { Sequence } from './sequence';
import { Color } from '../../../mol-util/color';
export class Residue extends PurePluginUIComponent<{ seqId: number, label: string, parent: Sequence<any>, marker: number, color: Color }> {
export class Residue extends PurePluginUIComponent<{ seqIdx: number, label: string, parent: Sequence<any>, marker: number, color: Color }> {
mouseEnter = (e: React.MouseEvent) => {
const modifiers = getModifiers(e.nativeEvent)
this.props.parent.highlight(this.props.seqId, modifiers);
this.props.parent.highlight(this.props.seqIdx, modifiers);
}
mouseLeave = () => {
......@@ -25,11 +25,11 @@ export class Residue extends PurePluginUIComponent<{ seqId: number, label: strin
mouseDown = (e: React.MouseEvent) => {
const buttons = getButtons(e.nativeEvent)
const modifiers = getModifiers(e.nativeEvent)
this.props.parent.click(this.props.seqId, buttons, modifiers);
this.props.parent.click(this.props.seqIdx, buttons, modifiers);
e.stopPropagation() // so that `parent.mouseDown` is not called
}
getBackgroundColor() {
get backgroundColor() {
// TODO make marker color configurable
if (this.props.marker === 0) return ''
if (this.props.marker % 2 === 0) return 'rgb(51, 255, 25)' // selected
......@@ -37,6 +37,12 @@ export class Residue extends PurePluginUIComponent<{ seqId: number, label: strin
return 'rgb(255, 102, 153)' // highlighted
}
get margin() {
return this.props.label.length > 1 && this.props.seqIdx
? `0px 0px 0px 4px`
: undefined
}
render() {
return <span
onMouseEnter={this.mouseEnter}
......@@ -44,7 +50,8 @@ export class Residue extends PurePluginUIComponent<{ seqId: number, label: strin
onMouseDown={this.mouseDown}
style={{
color: Color.toStyle(this.props.color),
backgroundColor: this.getBackgroundColor()
backgroundColor: this.backgroundColor,
margin: this.margin
}}>
{this.props.label}
</span>;
......
......@@ -12,7 +12,7 @@ import { MarkerAction } from '../../../mol-util/marker-action';
import { ButtonsType, ModifiersKeys, getButtons, getModifiers } from '../../../mol-util/input/input-observer';
import { ValueBox } from '../../../mol-util';
import { Residue } from './residue';
import { SequenceWrapper } from './util';
import { SequenceWrapper } from './wrapper';
type SequenceProps = { sequenceWrapper: SequenceWrapper.Any }
type SequenceState = { markerData: ValueBox<Uint8Array> }
......@@ -93,7 +93,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P, Sequ
const elems: JSX.Element[] = [];
for (let i = 0, il = sw.length; i < il; ++i) {
elems[elems.length] = <Residue
seqId={sw.seqId(i)}
seqIdx={i}
label={sw.residueLabel(i)}
parent={this}
marker={markerData.value[i]}
......
......@@ -7,18 +7,19 @@
import { OrderedSet } from '../../../mol-data/int';
import { Loci } from '../../../mol-model/loci';
import { MarkerAction, applyMarkerAction } from '../../../mol-util/marker-action';
import { StructureElement } from '../../../mol-model/structure';
import { StructureElement, Structure, Unit } from '../../../mol-model/structure';
import { Color } from '../../../mol-util/color';
export type StructureUnit = { structure: Structure, unit: Unit }
export { SequenceWrapper }
abstract class SequenceWrapper<D> {
abstract seqId(seqIdx: number): number
abstract residueLabel(seqIdx: number): string
abstract residueColor(seqIdx: number): Color
abstract eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean): boolean
abstract getLoci(seqId: number): StructureElement.Loci
abstract getLoci(seqIdx: number): StructureElement.Loci
markResidue(loci: Loci, action: MarkerAction) {
return this.eachResidue(loci, (set: OrderedSet) => {
......
......@@ -98,9 +98,11 @@ namespace Interactivity {
}
loci = Granularity[this.props.granularity](loci)
if (Structure.isLoci(loci)) {
loci = Structure.toStructureElementLoci(loci)
// convert to StructureElement.Loci of root structure
loci = Structure.toStructureElementLoci(Structure.Loci(loci.structure.parent || loci.structure))
}
if (StructureElement.isLoci(loci) && loci.structure.parent) {
// ensure the root structure is used
loci = StructureElement.Loci.remap(loci, loci.structure.parent)
}
return { loci, repr }
......
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