Browse Source

improved link loci handling for interactivity

Alexander Rose 5 years ago
parent
commit
b1a0f46ade

+ 21 - 18
src/mol-model/structure/structure/element.ts

@@ -196,30 +196,33 @@ namespace StructureElement {
             const elements: Loci['elements'][0][] = [];
 
             for (const lociElement of loci.elements) {
-                if (lociElement.unit.kind !== Unit.Kind.Atomic) elements[elements.length] = lociElement;
+                if (lociElement.unit.kind === Unit.Kind.Atomic) {
+                    const unitElements = lociElement.unit.elements;
+                    const h = lociElement.unit.model.atomicHierarchy;
 
-                const unitElements = lociElement.unit.elements;
-                const h = lociElement.unit.model.atomicHierarchy;
-
-                const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments;
+                    const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments;
 
-                const newIndices: UnitIndex[] = [];
-                const indices = lociElement.indices, len = OrderedSet.size(indices);
-                let i = 0;
-                while (i < len) {
-                    const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]];
-                    i++;
-                    while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) {
+                    const newIndices: UnitIndex[] = [];
+                    const indices = lociElement.indices, len = OrderedSet.size(indices);
+                    let i = 0;
+                    while (i < len) {
+                        const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]];
                         i++;
+                        while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) {
+                            i++;
+                        }
+
+                        for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) {
+                            const idx = OrderedSet.indexOf(unitElements, j);
+                            if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex;
+                        }
                     }
 
-                    for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) {
-                        const idx = OrderedSet.indexOf(unitElements, j);
-                        if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex;
-                    }
+                    elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) };
+                } else {
+                    // coarse elements are already by-residue
+                    elements[elements.length] = lociElement;
                 }
-
-                elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) };
             }
 
             return Loci(loci.structure, elements);

+ 30 - 1
src/mol-model/structure/structure/unit/links.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -8,6 +8,7 @@
 import { Unit, StructureElement } from '../../structure'
 import Structure from '../structure';
 import { LinkType } from '../../model/types';
+import { SortedArray } from '../../../../mol-data/int';
 
 export * from './links/data'
 export * from './links/intra-compute'
@@ -61,6 +62,34 @@ namespace Link {
         return true
     }
 
+    // TODO
+    export function toStructureElementLoci(loci: Loci): StructureElement.Loci {
+        const elements: StructureElement.Loci['elements'][0][] = []
+        const map = new Map<number, number[]>()
+
+        for (const lociLink of loci.links) {
+            const { aIndex, aUnit, bIndex, bUnit } = lociLink
+            if (aUnit === bUnit) {
+                if (map.has(aUnit.id)) map.get(aUnit.id)!.push(aIndex, bIndex)
+                else map.set(aUnit.id, [aIndex, bIndex])
+            } else {
+                if (map.has(aUnit.id)) map.get(aUnit.id)!.push(aIndex)
+                else map.set(aUnit.id, [aIndex])
+                if (map.has(bUnit.id)) map.get(bUnit.id)!.push(bIndex)
+                else map.set(bUnit.id, [bIndex])
+            }
+        }
+
+        map.forEach((indices: number[], id: number) => {
+            elements.push({
+                unit: loci.structure.unitMap.get(id)!,
+                indices: SortedArray.deduplicate(SortedArray.ofUnsortedArray(indices))
+            })
+        })
+
+        return StructureElement.Loci(loci.structure, elements);
+    }
+
     export function getType(structure: Structure, link: Location<Unit.Atomic>): LinkType {
         if (link.aUnit === link.bUnit) {
             const links = link.aUnit.links;

+ 26 - 20
src/mol-plugin/util/interactivity.ts

@@ -8,7 +8,7 @@
 import { Loci as ModelLoci, EmptyLoci } from '../../mol-model/loci';
 import { ModifiersKeys, ButtonsType } from '../../mol-util/input/input-observer';
 import { Representation } from '../../mol-repr/representation';
-import { StructureElement } from '../../mol-model/structure';
+import { StructureElement, Link } from '../../mol-model/structure';
 import { MarkerAction } from '../../mol-util/marker-action';
 import { StructureElementSelectionManager } from './structure-element-selection';
 import { PluginContext } from '../context';
@@ -90,8 +90,13 @@ namespace Interactivity {
             // TODO clear, then re-apply remaining providers
         }
 
-        expandLoci(loci: ModelLoci) {
-            return LociExpansion[this.props.lociExpansion](loci)
+        normalizedLoci(interactivityLoci: Loci) {
+            let { loci, repr } = interactivityLoci
+            if (this.props.lociExpansion !== 'none' && Link.isLoci(loci)) {
+                loci = Link.toStructureElementLoci(loci)
+            }
+            loci = LociExpansion[this.props.lociExpansion](loci)
+            return { loci, repr }
         }
 
         protected mark(current: Loci<ModelLoci>, action: MarkerAction) {
@@ -111,22 +116,23 @@ namespace Interactivity {
 
         apply(e: HighlightEvent) {
             const { current, modifiers } = e
-            const expanded: Loci<ModelLoci> = { loci: this.expandLoci(current.loci), repr: current.repr }
-            if (StructureElement.isLoci(expanded.loci)) {
-                let loci: StructureElement.Loci = expanded.loci;
+
+            const normalized: Loci<ModelLoci> = this.normalizedLoci(current)
+            if (StructureElement.isLoci(normalized.loci)) {
+                let loci: StructureElement.Loci = normalized.loci;
                 if (modifiers && modifiers.shift) {
                     loci = this.sel.tryGetRange(loci) || loci;
                 }
 
                 this.mark(this.prev, MarkerAction.RemoveHighlight);
-                const toHighlight = { loci, repr: expanded.repr };
+                const toHighlight = { loci, repr: normalized.repr };
                 this.mark(toHighlight, MarkerAction.Highlight);
                 this.prev = toHighlight;
             } else {
-                if (!Loci.areEqual(this.prev, expanded)) {
+                if (!Loci.areEqual(this.prev, normalized)) {
                     this.mark(this.prev, MarkerAction.RemoveHighlight);
-                    this.mark(expanded, MarkerAction.Highlight);
-                    this.prev = expanded;
+                    this.mark(normalized, MarkerAction.Highlight);
+                    this.prev = normalized;
                 }
             }
         }
@@ -150,34 +156,34 @@ namespace Interactivity {
 
         apply(e: ClickEvent) {
             const { current, buttons, modifiers } = e
-            const expanded: Loci<ModelLoci> = { loci: this.expandLoci(current.loci), repr: current.repr }
-            if (expanded.loci.kind === 'empty-loci') {
+            const normalized: Loci<ModelLoci> = this.normalizedLoci(current)
+            if (normalized.loci.kind === 'empty-loci') {
                 if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
                     // clear the selection on Ctrl + Right-Click on empty
                     const sels = this.sel.clear();
                     for (const s of sels) this.mark({ loci: s }, MarkerAction.Deselect);
                 }
-            } else if (StructureElement.isLoci(expanded.loci)) {
+            } else if (StructureElement.isLoci(normalized.loci)) {
                 if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
                     // select only the current element on Ctrl + Right-Click
-                    const old = this.sel.get(expanded.loci.structure);
+                    const old = this.sel.get(normalized.loci.structure);
                     this.mark({ loci: old }, MarkerAction.Deselect);
-                    this.sel.set(expanded.loci);
-                    this.mark(expanded, MarkerAction.Select);
+                    this.sel.set(normalized.loci);
+                    this.mark(normalized, MarkerAction.Select);
                 } else if (modifiers.control && buttons === ButtonsType.Flag.Primary) {
                     // toggle current element on Ctrl + Left-Click
-                    this.toggleSel(expanded as Representation.Loci<StructureElement.Loci>);
+                    this.toggleSel(normalized as Representation.Loci<StructureElement.Loci>);
                 } else if (modifiers.shift && buttons === ButtonsType.Flag.Primary) {
                     // try to extend sequence on Shift + Left-Click
-                    let loci: StructureElement.Loci = expanded.loci;
+                    let loci: StructureElement.Loci = normalized.loci;
                     if (modifiers && modifiers.shift) {
                         loci = this.sel.tryGetRange(loci) || loci;
                     }
-                    this.toggleSel({ loci, repr: expanded.repr });
+                    this.toggleSel({ loci, repr: normalized.repr });
                 }
             } else {
                 if (!ButtonsType.has(buttons, ButtonsType.Flag.Secondary)) return;
-                for (let p of this.providers) p(expanded, MarkerAction.Toggle);
+                for (let p of this.providers) p(normalized, MarkerAction.Toggle);
             }
         }