ソースを参照

wip, sequence view, select options

Alexander Rose 5 年 前
コミット
e301eca9c2

+ 27 - 27
src/mol-plugin/skin/base/components/controls.scss

@@ -8,42 +8,42 @@
     select, button, input[type=text] {
         @extend .msp-form-control;
     }
-    
+
     button {
         @extend .msp-btn;
         @extend .msp-btn-block;
     }
 }
 
-.msp-control-row {    
+.msp-control-row {
     position: relative;
     height: $row-height;
     background: $default-background;
     margin-top: 1px;
-    
+
     > span {
         line-height: $row-height;
         display: block;
         width: $control-label-width + $control-spacing;
         text-align: right;
-        padding: 0 $control-spacing; 
+        padding: 0 $control-spacing;
         color: color-lower-contrast($font-color, 15%);
         overflow: hidden;
         text-overflow: ellipsis;
         white-space: nowrap;
-        
-        @include non-selectable;        
+
+        @include non-selectable;
     }
-    
+
     select, button, input[type=text] {
         @extend .msp-form-control;
     }
-    
+
     button {
         @extend .msp-btn;
         @extend .msp-btn-block;
     }
-    
+
     > div:nth-child(2) {
         background: $msp-form-control-background;
         position: absolute;
@@ -58,21 +58,21 @@
     position: relative;
 }
 
-.msp-toggle-button {    
+.msp-toggle-button {
     .msp-icon {
         display: inline-block;
         margin-right: 6px;
     }
-    
+
     > div > button:hover {
         border-color: color-increase-contrast($msp-form-control-background, 5%) !important;
         border: none;
         outline-offset: -1px  !important;
-        outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;  
+        outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
     }
 }
 
-.msp-slider {    
+.msp-slider {
     > div:first-child {
         position: absolute;
         top: 0;
@@ -91,14 +91,14 @@
         top: 0;
         bottom: 0;
     }
-    
+
     input[type=text] {
         padding-right: 6px;
         padding-left: 4px;
         font-size: 80%;
         text-align: right;
     }
-    
+
     // input[type=range] {
     //     width: 100%;
     // }
@@ -135,14 +135,14 @@
         bottom: 0;
         font-size: 80%;
     }
-    
+
     input[type=text] {
         padding-right: 4px;
         padding-left: 4px;
         font-size: 80%;
         text-align: center;
     }
-    
+
     // input[type=range] {
     //     width: 100%;
     // }
@@ -154,24 +154,24 @@
         margin: 0;
         text-align: center;
         padding-right: $control-spacing;
-        padding-left: $control-spacing;        
-        
+        padding-left: $control-spacing;
+
         &:hover {
             border-color: color-increase-contrast($msp-form-control-background, 5%) !important;
             border: none;
             outline-offset: -1px  !important;
-            outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;  
+            outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
         }
     }
-    
+
     .msp-color-picker {
         position: absolute;
         z-index: 100000;
         background: $default-background;
         border-top: 1px solid $default-background;
         padding-bottom: $control-spacing / 2;
-        width: 100%;        
-        
+        width: 100%;
+
         // input[type=text] {
         //     background: $msp-form-control-background !important;
         // }
@@ -217,7 +217,7 @@
         height: 2 * $row-height / 3 !important;
         line-height: 2 * $row-height / 3 !important;
         font-size: 70% !important;
-        background: $default-background !important;        
+        background: $default-background !important;
         color: color-lower-contrast($font-color, 15%) !important;
     }
 }
@@ -231,13 +231,13 @@
 
 .msp-control-subgroup {
     margin-top: 1px;
-    
+
     .msp-control-row {
         margin-left: $control-spacing !important;
         > span {
             width: $control-label-width !important;
         }
-        
+
         > div:nth-child(2) {
             left: $control-label-width !important;
         }
@@ -254,7 +254,7 @@
     width: $control-label-width + $control-spacing;
     text-align: left;
     background: transparent;
-    
+
     .msp-icon {
         line-height: $row-height - 3;
         width: $row-height - 1;

+ 11 - 4
src/mol-plugin/skin/base/components/sequence.scss

@@ -4,19 +4,26 @@
     top: 0;
     left: 0;
     bottom: 0;
-    overflow-y: scroll;
-    overflow-x: hidden;
     font-size: 90%;
     background: $sequence-background;
 }
 
-.msp-sequence-entity {
+.msp-sequence-select {
+    float: left;
+    width: $sequence-select-width;
+}
+
+.msp-sequence-wrapper {
     word-break: break-word;
     padding: $info-vertical-padding $control-spacing $info-vertical-padding $control-spacing;
     user-select: none;
+    height: 100%;
+    overflow-y: auto;
+    overflow-x: hidden;
+    font-size: 90%;
 }
 
-.msp-sequence-entity {
+.msp-sequence-wrapper {
     span {
         cursor: pointer;
     }

+ 2 - 1
src/mol-plugin/skin/base/variables.scss

@@ -78,4 +78,5 @@ $entity-color-partialy-visible: color-lower-contrast($font-color, 33%);
 $entity-tag-color: color-lower-contrast($font-color, 20%);
 
 // sequence
-$sequence-background: $default-background;
+$sequence-background: $default-background;
+$sequence-select-width: 300px;

+ 128 - 37
src/mol-plugin/ui/sequence.tsx

@@ -18,92 +18,183 @@ import { MarkerAction } from '../../mol-util/marker-action';
 import { ParameterControls } from './controls/parameters';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 
-function getSequenceWrapperForStructure(index: number, structure: Structure, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | undefined {
-    let j = 0
+function opKey(ids: string[]) {
+    return ids.sort().join(',')
+}
+
+function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | undefined {
+    const { structure, entity, chain, operator } = 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
-        if (j === index) {
-            const sw = new PolymerSequenceWrapper({ structure, unit })
-            sw.markResidue(structureSelection.get(structure), MarkerAction.Select)
-            return sw
-        }
-        j += 1
+
+        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(SP.unit.pdbx_struct_oper_list_ids(l)) !== operator) continue
+
+        // console.log('new PolymerSequenceWrapper', structureSelection.get(structure))
+        const sw = new PolymerSequenceWrapper({ structure, unit })
+        sw.markResidue(structureSelection.get(structure), MarkerAction.Select)
+        return sw
     }
 }
 
-function getPolymerOptionsForStructure(structure: Structure) {
-    const options: [number, string][] = []
+function getEntityOptions(structure: Structure) {
+    const options: [string, string][] = []
+    const l = StructureElement.create()
+    const seen = new Set<string>()
+
+    structure.units.forEach(unit => {
+        if (unit.polymerElements.length === 0) return
+
+        StructureElement.set(l, unit, unit.elements[0])
+        const id = SP.entity.id(l)
+        if (seen.has(id)) return
+
+        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 l = StructureElement.create()
+    const seen = new Set<string>()
 
-    let i = 0
     structure.units.forEach(unit => {
         if (unit.polymerElements.length === 0) return
 
-        const l = StructureElement.create(unit, unit.elements[0])
-        const entityDescription = SP.entity.pdbx_description(l)
-        const label_asym_id = SP.chain.label_asym_id(l)
-        const label = `${label_asym_id}: ${entityDescription}`
+        StructureElement.set(l, unit, unit.elements[0])
+        if (SP.entity.id(l) !== entityId) return
+
+        const id = SP.chain.label_asym_id(l)
+        if (seen.has(id)) return
 
-        options.push([ i, label ])
-        i += 1
+        const label = `${id}: ${SP.chain.auth_asym_id(l)}`
+        options.push([ id, label ])
+        seen.add(id)
     })
 
+    if (options.length === 0) options.push(['', 'No chains'])
     return options
 }
 
-export class SequenceView extends PluginUIComponent<{ }, { polymer: number }> {
+function getOperatorOptions(structure: Structure, entityId: string, label_asym_id: string) {
+    const options: [string, string][] = []
+    const l = StructureElement.create()
+    const seen = new Set<string>()
+
+    structure.units.forEach(unit => {
+        if (unit.polymerElements.length === 0) return
+        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
+
+        const id = opKey(SP.unit.pdbx_struct_oper_list_ids(l))
+        if (seen.has(id)) return
+
+        const label = `${SP.unit.pdbx_struct_oper_list_ids(l).join(', ')}`
+        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 }
+
+export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
     private spine: StateTreeSpine.Impl
 
-    state = { polymer: 0 }
+    state = { structure: Structure.Empty, entity: '', chain: '', operator: '' }
 
-    componentDidMount() {
+    constructor(props: {}, context?: any) {
+        super(props, context);
         this.spine = new StateTreeSpine.Impl(this.plugin.state.dataState.cells);
+    }
 
+    componentDidMount() {
         this.subscribe(this.plugin.state.behavior.currentObject, o => {
             const current = this.plugin.state.dataState.cells.get(o.ref)!;
             this.spine.current = current
-            this.forceUpdate();
+            this.setState(this.getInitialState())
         });
 
         this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
             const current = this.spine.current;
             if (!current || current.sourceRef !== ref || current.state !== state) return;
-            this.forceUpdate();
+            this.setState(this.getInitialState())
         });
     }
 
     private getStructure() {
-        const so = this.spine && this.spine.getRootOfType(SO.Molecule.Structure)
-        return so && so.data
+        const so = this.spine.getRootOfType(SO.Molecule.Structure)
+        return (so && so.data) || Structure.Empty
+    }
+
+    private getSequenceWrapper() {
+        return getSequenceWrapper(this.state, this.plugin.helpers.structureSelection)
+    }
+
+    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 }
     }
 
-    private getParams(structure: Structure) {
+    private get params() {
+        const { structure, entity, chain } = this.state
+        const entityOptions = getEntityOptions(structure)
+        const chainOptions = getChainOptions(structure, entity)
+        const operatorOptions = getOperatorOptions(structure, entity, chain)
         return {
-            polymer: PD.Select(0, getPolymerOptionsForStructure(structure))
+            entity: PD.Select(entityOptions[0][0], entityOptions),
+            chain: PD.Select(chainOptions[0][0], chainOptions),
+            operator: PD.Select(operatorOptions[0][0], operatorOptions)
         }
     }
 
     private setParamProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
-        if (p.name === 'polymer') {
-            this.setState({ polymer: p.value })
+        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]
+                break
+            case 'chain':
+                state.chain = p.value
+                state.operator = getOperatorOptions(state.structure, state.entity, state.chain)[0][0]
+                break
+            case 'operator':
+                state.operator = p.value
+                break
         }
+        this.setState(state)
     }
 
     render() {
-        const structure = this.getStructure();
-        if (!structure) return <div className='msp-sequence'>
-            <div className='msp-sequence-entity'>No structure available</div>
+        if (this.state.structure === Structure.Empty) return <div className='msp-sequence'>
+            <div className='msp-sequence-wrapper'>No structure available</div>
         </div>;
 
-        const { structureSelection } = this.plugin.helpers
-        const params = this.getParams(structure)
-        const sequenceWrapper = getSequenceWrapperForStructure(this.state.polymer, structure, structureSelection)
-
+        const sequenceWrapper = this.getSequenceWrapper()
         return <div className='msp-sequence'>
-            <ParameterControls params={params} values={this.state} onChange={this.setParamProps} />
+            <div className='msp-sequence-select'>
+                <ParameterControls params={this.params} values={this.state} onChange={this.setParamProps} />
+            </div>
             {sequenceWrapper !== undefined
                 ? <Sequence sequenceWrapper={sequenceWrapper} />
-                : <div className='msp-sequence-entity'>No sequence available</div>}
+                : <div className='msp-sequence-wrapper'>No sequence available</div>}
         </div>;
     }
 }

+ 7 - 10
src/mol-plugin/ui/sequence/polymer.ts

@@ -19,11 +19,11 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
 
     eachResidue(loci: Loci, apply: (interval: Interval) => boolean) {
         let changed = false
-        const { structure } = this.data
+        const { structure, unit } = this.data
         if (!StructureElement.isLoci(loci)) return false
         if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
 
-        const { location, entityId, label_asym_id } = this
+        const { location, label_asym_id } = this
         for (const e of loci.elements) {
             let rIprev = -1
             location.unit = e.unit
@@ -35,7 +35,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
                 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.unit.id(location) !== unit.id) return
                     if (SP.chain.label_asym_id(location) !== label_asym_id) return
 
                     if (apply(getSeqIdInterval(location))) changed = true
@@ -47,7 +47,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
     }
 
     getLoci(seqId: number) {
-        const query = createResidueQuery(this.entityId, seqId, this.label_asym_id);
+        const query = createResidueQuery(this.data.unit.id, seqId);
         return StructureSelection.toLoci2(StructureQuery.run(query, this.data.structure));
     }
 
@@ -67,13 +67,10 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
     }
 }
 
-function createResidueQuery(entityId: string, label_seq_id: number, label_asym_id: string) {
+function createResidueQuery(unitId: number, label_seq_id: number) {
     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
+        unitTest: ctx => {
+            return SP.unit.id(ctx.element) === unitId
         },
         residueTest: ctx => {
             if (ctx.element.unit.kind === Unit.Kind.Atomic) {

+ 1 - 1
src/mol-plugin/ui/sequence/sequence.tsx

@@ -96,7 +96,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P, Sequ
         }
 
         return <div
-            className='msp-sequence-entity'
+            className='msp-sequence-wrapper'
             onContextMenu={this.contextMenu}
             onMouseDown={this.mouseDown}
         >