Browse Source

refactored DataLoci, DataLocation and improved props

- moved nci theme & repr to props
- better validaiton clash loci handling
Alexander Rose 5 years ago
parent
commit
b83fed4701

+ 46 - 41
src/mol-model-props/computed/interactions/interactions.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,7 +8,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Structure, Unit } from '../../../mol-model/structure';
 import { Features, FeaturesBuilder } from './features';
 import { ValenceModelProvider } from '../valence-model';
-import { InteractionsIntraContacts, InteractionsInterContacts, FeatureType } from './common';
+import { InteractionsIntraContacts, InteractionsInterContacts, FeatureType, interactionTypeLabel } from './common';
 import { IntraContactsBuilder, InterContactsBuilder } from './contacts-builder';
 import { IntMap } from '../../../mol-data/int';
 import { addUnitContacts, ContactTester, addStructureContacts, ContactProvider, ContactsParams, ContactsProps } from './contacts';
@@ -20,6 +20,10 @@ import { SetUtils } from '../../../mol-util/set';
 import { MetalCoordinationProvider, MetalProvider, MetalBindingProvider } from './metal';
 import { refineInteractions } from './refine';
 import { CustomProperty } from '../../common/custom-property';
+import { DataLocation } from '../../../mol-model/location';
+import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
+import { Sphere3D } from '../../../mol-math/geometry';
+import { DataLoci } from '../../../mol-model/loci';
 
 export { Interactions }
 
@@ -33,9 +37,7 @@ interface Interactions {
 }
 
 namespace Interactions {
-    export interface Location {
-        readonly kind: 'interaction-location'
-        interactions: Interactions
+    export interface Element {
         unitA: Unit
         /** Index into features of unitA */
         indexA: Features.FeatureIndex
@@ -43,62 +45,65 @@ namespace Interactions {
         /** Index into features of unitB */
         indexB: Features.FeatureIndex
     }
+    export interface Location extends DataLocation<Interactions, Element> {}
 
-    export function Location(interactions?: Interactions, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location {
-        return { kind: 'interaction-location', interactions: interactions as any, unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any };
+    export function Location(interactions: Interactions, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location {
+        return DataLocation('interactions', interactions, { unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any });
     }
 
     export function isLocation(x: any): x is Location {
-        return !!x && x.kind === 'interaction-location';
+        return !!x && x.kind === 'data-location' && x.tag === 'interactions';
     }
 
     export function areLocationsEqual(locA: Location, locB: Location) {
         return (
-            locA.interactions === locB.interactions &&
-            locA.indexA === locB.indexA && locA.indexB === locB.indexB &&
-            locA.unitA === locB.unitA && locA.unitB === locB.unitB
+            locA.data === locB.data &&
+            locA.element.indexA === locB.element.indexA &&
+            locA.element.indexB === locB.element.indexB &&
+            locA.element.unitA === locB.element.unitA &&
+            locA.element.unitB === locB.element.unitB
         )
     }
 
-    export interface Loci {
-        readonly kind: 'interaction-loci'
-        readonly structure: Structure
-        readonly interactions: Interactions
-        readonly contacts: ReadonlyArray<{
-            unitA: Unit
-            /** Index into features of unitA */
-            indexA: Features.FeatureIndex
-            unitB: Unit
-            /** Index into features of unitB */
-            indexB: Features.FeatureIndex
-        }>
+    function _label(interactions: Interactions, element: Element): string {
+        const { unitA, indexA, unitB, indexB } = element
+        const { contacts, unitsContacts } = interactions
+        if (unitA === unitB) {
+            const contacts = unitsContacts.get(unitA.id)
+            const idx = contacts.getDirectedEdgeIndex(indexA, indexB)
+            return interactionTypeLabel(contacts.edgeProps.type[idx])
+        } else {
+            const idx = contacts.getEdgeIndex(indexA, unitA, indexB, unitB)
+            return interactionTypeLabel(contacts.edges[idx].props.type)
+        }
     }
 
-    export function Loci(structure: Structure, interactions: Interactions, contacts: Loci['contacts']): Loci {
-        return { kind: 'interaction-loci', structure, interactions, contacts };
+    export function locationLabel(location: Location): string {
+        return _label(location.data, location.element)
+    }
+
+    type StructureInteractions = { readonly structure: Structure, readonly interactions: Interactions }
+    export interface Loci extends DataLoci<StructureInteractions, Element> { }
+
+    export function Loci(structure: Structure, interactions: Interactions, elements: ReadonlyArray<Element>): Loci {
+        return DataLoci('interactions', { structure, interactions }, elements, (boundingSphere) => getBoundingSphere(interactions, elements, boundingSphere), () => getLabel(interactions, elements));
     }
 
     export function isLoci(x: any): x is Loci {
-        return !!x && x.kind === 'interaction-loci';
+        return !!x && x.kind === 'data-loci' && x.tag === 'interactions';
     }
 
-    export function areLociEqual(a: Loci, b: Loci) {
-        if (a.structure !== b.structure) return false
-        if (a.interactions !== b.interactions) return false
-        if (a.contacts.length !== b.contacts.length) return false
-        for (let i = 0, il = a.contacts.length; i < il; ++i) {
-            const contactA = a.contacts[i]
-            const contactB = b.contacts[i]
-            if (contactA.unitA !== contactB.unitA) return false
-            if (contactA.unitB !== contactB.unitB) return false
-            if (contactA.indexA !== contactB.indexA) return false
-            if (contactA.indexB !== contactB.indexB) return false
-        }
-        return true
+    export function getBoundingSphere(interactions: Interactions, elements: ReadonlyArray<Element>, boundingSphere: Sphere3D) {
+        const { unitsFeatures } = interactions
+        return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
+            const e = elements[i]
+            Features.setPosition(pA, e.unitA, e.indexA, unitsFeatures.get(e.unitA.id))
+            Features.setPosition(pB, e.unitB, e.indexB, unitsFeatures.get(e.unitB.id))
+        }, boundingSphere)
     }
 
-    export function isLociEmpty(loci: Loci) {
-        return loci.contacts.length === 0 ? true : false
+    export function getLabel(interactions: Interactions, elements: ReadonlyArray<Element>) {
+        return elements.length > 0 ? _label(interactions, elements[0]) : ''
     }
 }
 

+ 15 - 14
src/mol-repr/structure/visual/interactions-inter-unit-cylinder.ts → src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts

@@ -5,21 +5,21 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { VisualContext } from '../../visual';
+import { VisualContext } from '../../../mol-repr/visual';
 import { Structure, StructureElement } from '../../../mol-model/structure';
 import { Theme } from '../../../mol-theme/theme';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from './util/link';
-import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../complex-visual';
-import { VisualUpdateState } from '../../util';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
+import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
+import { VisualUpdateState } from '../../../mol-repr/util';
 import { PickingId } from '../../../mol-geo/geometry/picking';
 import { EmptyLoci, Loci } from '../../../mol-model/loci';
 import { Interval } from '../../../mol-data/int';
-import { Interactions } from '../../../mol-model-props/computed/interactions/interactions';
-import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
+import { Interactions } from '../interactions/interactions';
+import { InteractionsProvider } from '../interactions';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
-import { InteractionFlag } from '../../../mol-model-props/computed/interactions/common';
+import { InteractionFlag } from '../interactions/common';
 
 const tmpLoc = StructureElement.Location.create()
 
@@ -110,12 +110,12 @@ function getInteractionLoci(pickingId: PickingId, structure: Structure, id: numb
 function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
     let changed = false
     if (Interactions.isLoci(loci)) {
-        if (!Structure.areEquivalent(loci.structure, structure)) return false
+        if (!Structure.areEquivalent(loci.data.structure, structure)) return false
         const interactions = InteractionsProvider.get(structure).value!
-        if (loci.interactions !== interactions) return false
+        if (loci.data.interactions !== interactions) return false
         const { contacts } = interactions
 
-        for (const c of loci.contacts) {
+        for (const c of loci.elements) {
             const idx = contacts.getEdgeIndex(c.indexA, c.unitA, c.indexB, c.unitB)
             if (idx !== -1) {
                 if (apply(Interval.ofSingleton(idx))) changed = true
@@ -131,12 +131,13 @@ function createInteractionsIterator(structure: Structure): LocationIterator {
     const groupCount = contacts.edgeCount
     const instanceCount = 1
     const location = Interactions.Location(interactions)
+    const { element } = location
     const getLocation = (groupIndex: number) => {
         const c = contacts.edges[groupIndex]
-        location.unitA = c.unitA
-        location.indexA = c.indexA
-        location.unitB = c.unitB
-        location.indexB = c.indexB
+        element.unitA = c.unitA
+        element.indexA = c.indexA
+        element.unitB = c.unitB
+        element.indexB = c.indexB
         return location
     }
     return LocationIterator(groupCount, instanceCount, getLocation, true)

+ 20 - 19
src/mol-repr/structure/visual/interactions-intra-unit-cylinder.ts → src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts

@@ -11,15 +11,15 @@ import { Interval } from '../../../mol-data/int';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { PickingId } from '../../../mol-geo/geometry/picking';
-import { VisualContext } from '../../visual';
+import { VisualContext } from '../../../mol-repr/visual';
 import { Theme } from '../../../mol-theme/theme';
-import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
-import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from './util/link';
-import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
-import { VisualUpdateState } from '../../util';
+import { InteractionsProvider } from '../interactions';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
+import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
+import { VisualUpdateState } from '../../../mol-repr/util';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
-import { Interactions } from '../../../mol-model-props/computed/interactions/interactions';
-import { InteractionFlag } from '../../../mol-model-props/computed/interactions/common';
+import { Interactions } from '../interactions/interactions';
+import { InteractionFlag } from '../interactions/common';
 
 async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
@@ -95,10 +95,10 @@ function getInteractionLoci(pickingId: PickingId, structureGroup: StructureGroup
         const { structure, group } = structureGroup
         const unit = structure.unitMap.get(group.units[instanceId].id)
         const interactions = InteractionsProvider.get(structure).value!
-        const contacts = interactions.unitsContacts.get(unit.id)
+        const { a, b } = interactions.unitsContacts.get(unit.id)
         return Interactions.Loci(structure, interactions, [
-            { unitA: unit, indexA: contacts.a[groupId], unitB: unit, indexB: contacts.b[groupId] },
-            { unitA: unit, indexA: contacts.b[groupId], unitB: unit, indexB: contacts.a[groupId] },
+            { unitA: unit, indexA: a[groupId], unitB: unit, indexB: b[groupId] },
+            { unitA: unit, indexA: b[groupId], unitB: unit, indexB: a[groupId] },
         ])
     }
     return EmptyLoci
@@ -108,16 +108,16 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
     let changed = false
     if (Interactions.isLoci(loci)) {
         const { structure, group } = structureGroup
-        if (!Structure.areEquivalent(loci.structure, structure)) return false
+        if (!Structure.areEquivalent(loci.data.structure, structure)) return false
         const interactions = InteractionsProvider.get(structure).value!
-        if (loci.interactions !== interactions) return false
+        if (loci.data.interactions !== interactions) return false
         const unit = group.units[0]
         const contacts = interactions.unitsContacts.get(unit.id)
         const groupCount = contacts.edgeCount * 2
-        for (const c of loci.contacts) {
-            const unitIdx = group.unitIndexMap.get(c.unitA.id)
+        for (const e of loci.elements) {
+            const unitIdx = group.unitIndexMap.get(e.unitA.id)
             if (unitIdx !== undefined) {
-                const idx = contacts.getDirectedEdgeIndex(c.indexA, c.indexB)
+                const idx = contacts.getDirectedEdgeIndex(e.indexA, e.indexB)
                 if (idx !== -1) {
                     if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true
                 }
@@ -135,12 +135,13 @@ function createInteractionsIterator(structureGroup: StructureGroup): LocationIte
     const groupCount = contacts.edgeCount * 2
     const instanceCount = group.units.length
     const location = Interactions.Location(interactions)
+    const { element } = location
     const getLocation = (groupIndex: number, instanceIndex: number) => {
         const instanceUnit = group.units[instanceIndex]
-        location.unitA = instanceUnit
-        location.indexA = contacts.a[groupIndex]
-        location.unitB = instanceUnit
-        location.indexB = contacts.b[groupIndex]
+        element.unitA = instanceUnit
+        element.indexA = contacts.a[groupIndex]
+        element.unitB = instanceUnit
+        element.indexB = contacts.b[groupIndex]
         return location
     }
     return LocationIterator(groupCount, instanceCount, getLocation)

+ 7 - 7
src/mol-repr/structure/representation/interactions.ts → src/mol-model-props/computed/representations/interactions.ts

@@ -5,15 +5,15 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../representation';
+import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
-import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../representation';
-import { InteractionsIntraUnitParams, InteractionsIntraUnitVisual } from '../visual/interactions-intra-unit-cylinder';
-import { UnitKindOptions, UnitKind } from '../visual/util/common';
-import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
-import { InteractionsInterUnitParams, InteractionsInterUnitVisual } from '../visual/interactions-inter-unit-cylinder';
-import { CustomProperty } from '../../../mol-model-props/common/custom-property';
+import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
+import { InteractionsIntraUnitParams, InteractionsIntraUnitVisual } from './interactions-intra-unit-cylinder';
+import { UnitKindOptions, UnitKind } from '../../../mol-repr/structure/visual/util/common';
+import { InteractionsProvider } from '../interactions';
+import { InteractionsInterUnitParams, InteractionsInterUnitVisual } from './interactions-inter-unit-cylinder';
+import { CustomProperty } from '../../common/custom-property';
 
 const InteractionsVisuals = {
     'intra-unit': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InteractionsIntraUnitParams>) => UnitsRepresentation('Intra-unit interactions cylinder', ctx, getParams, InteractionsIntraUnitVisual),

+ 18 - 17
src/mol-theme/color/interaction-type.ts → src/mol-model-props/computed/themes/interaction-type.ts

@@ -1,19 +1,19 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Location } from '../../mol-model/location';
-import { Color, ColorMap } from '../../mol-util/color';
-import { ParamDefinition as PD } from '../../mol-util/param-definition'
-import { InteractionsProvider } from '../../mol-model-props/computed/interactions';
-import { ThemeDataContext } from '../theme';
-import { ColorTheme, LocationColor } from '../color';
-import { InteractionType } from '../../mol-model-props/computed/interactions/common';
-import { TableLegend } from '../../mol-util/legend';
-import { Interactions } from '../../mol-model-props/computed/interactions/interactions';
-import { CustomProperty } from '../../mol-model-props/common/custom-property';
+import { Location } from '../../../mol-model/location';
+import { Color, ColorMap } from '../../../mol-util/color';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition'
+import { InteractionsProvider } from '../interactions';
+import { ThemeDataContext } from '../../../mol-theme/theme';
+import { ColorTheme, LocationColor } from '../../../mol-theme/color';
+import { InteractionType } from '../interactions/common';
+import { TableLegend } from '../../../mol-util/legend';
+import { Interactions } from '../interactions/interactions';
+import { CustomProperty } from '../../common/custom-property';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Assigns colors according the interaction type of a link.'
@@ -78,14 +78,15 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value
     if (interactions && interactions.value) {
         color = (location: Location) => {
             if (Interactions.isLocation(location)) {
-                const { interactions, unitA, indexA, unitB, indexB } = location
-                if (location.unitA === location.unitB) {
-                    const links = interactions.unitsContacts.get(location.unitA.id)
-                    const idx = links.getDirectedEdgeIndex(location.indexA, location.indexB)
+                const { unitsContacts, contacts } = location.data
+                const { unitA, unitB, indexA, indexB } = location.element
+                if (unitA === unitB) {
+                    const links = unitsContacts.get(unitA.id)
+                    const idx = links.getDirectedEdgeIndex(indexA, indexB)
                     return typeColor(links.edgeProps.type[idx])
                 } else {
-                    const idx = interactions.contacts.getEdgeIndex(indexA, unitA, indexB, unitB)
-                    return typeColor(interactions.contacts.edges[idx].props.type)
+                    const idx = contacts.getEdgeIndex(indexA, unitA, indexB, unitB)
+                    return typeColor(contacts.edges[idx].props.type)
                 }
             }
             return DefaultColor

+ 59 - 8
src/mol-model-props/rcsb/representations/validation-report-clashes.ts

@@ -5,13 +5,13 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
+import { Unit, Structure, StructureElement, Bond } from '../../../mol-model/structure';
 import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { PickingId } from '../../../mol-geo/geometry/picking';
-import { EmptyLoci, Loci, createDataLoci } from '../../../mol-model/loci';
-import { Interval, OrderedSet } from '../../../mol-data/int';
+import { EmptyLoci, Loci, DataLoci } from '../../../mol-model/loci';
+import { Interval } from '../../../mol-data/int';
 import { RepresentationContext, RepresentationParamsGetter, Representation } from '../../../mol-repr/representation';
 import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
 import { UnitKind, UnitKindOptions } from '../../../mol-repr/structure/visual/util/common';
@@ -20,11 +20,14 @@ import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '.
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
 import { VisualUpdateState } from '../../../mol-repr/util';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
-import { ClashesProvider } from '../validation-report';
+import { ClashesProvider, IntraUnitClashes, InterUnitClashes } from '../validation-report';
 import { CustomProperty } from '../../common/custom-property';
 import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
 import { Color } from '../../../mol-util/color';
 import { MarkerActions } from '../../../mol-util/marker-action';
+import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
+import { Sphere3D } from '../../../mol-math/geometry';
+import { bondLabel } from '../../../mol-theme/label';
 
 //
 
@@ -81,6 +84,30 @@ export function IntraUnitClashVisual(materialId: number): UnitsVisual<IntraUnitC
     }, materialId)
 }
 
+function getIntraClashBoundingSphere(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[], boundingSphere: Sphere3D) {
+    return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
+        unit.conformation.position(unit.elements[clashes.a[elements[i]]], pA)
+        unit.conformation.position(unit.elements[clashes.b[elements[i]]], pB)
+    }, boundingSphere)
+}
+
+function getIntraClashLabel(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
+    const idx = elements[0]
+    if (idx === undefined) return ''
+    const { edgeProps: { id, magnitude, distance } } = clashes
+    const mag = magnitude[idx].toFixed(2)
+    const dist = distance[idx].toFixed(2)
+
+    return [
+        `RCSB Clash id: ${id[idx]} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
+        bondLabel(Bond.Location(unit, clashes.a[idx], unit, clashes.b[idx]))
+    ].join('</br>')
+}
+
+function IntraClashLoci(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
+    return DataLoci('intra-clashes', { unit, clashes }, elements, (boundingSphere: Sphere3D) =>  getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere), () => getIntraClashLabel(unit, clashes, elements))
+}
+
 function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
     const { objectId, instanceId, groupId } = pickingId
     if (id === objectId) {
@@ -88,7 +115,7 @@ function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup,
         const unit = group.units[instanceId]
         if (Unit.isAtomic(unit)) {
             const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
-            return createDataLoci(clashes, 'clashes', OrderedSet.ofSingleton(groupId))
+            return IntraClashLoci(unit, clashes, [groupId])
         }
     }
     return EmptyLoci
@@ -168,12 +195,36 @@ export function InterUnitClashVisual(materialId: number): ComplexVisual<InterUni
     }, materialId)
 }
 
+function getInterClashBoundingSphere(clashes: InterUnitClashes, elements: number[], boundingSphere: Sphere3D) {
+    return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
+        const c = clashes.edges[elements[i]]
+        c.unitA.conformation.position(c.unitA.elements[c.indexA], pA)
+        c.unitB.conformation.position(c.unitB.elements[c.indexB], pB)
+    }, boundingSphere)
+}
+
+function getInterClashLabel(clashes: InterUnitClashes, elements: number[]) {
+    const idx = elements[0]
+    if (idx === undefined) return ''
+    const c = clashes.edges[idx]
+    const mag = c.props.magnitude.toFixed(2)
+    const dist = c.props.distance.toFixed(2)
+
+    return [
+        `RCSB Clash id: ${c.props.id} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
+        bondLabel(Bond.Location(c.unitA, c.indexA, c.unitB, c.indexB))
+    ].join('</br>')
+}
+
+function InterClashLoci(clashes: InterUnitClashes, elements: number[]) {
+    return DataLoci('inter-clashes', clashes, elements, (boundingSphere: Sphere3D) =>  getInterClashBoundingSphere(clashes, elements, boundingSphere), () => getInterClashLabel(clashes, elements))
+}
+
 function getInterClashLoci(pickingId: PickingId, structure: Structure, id: number) {
     const { objectId, groupId } = pickingId
     if (id === objectId) {
-        structure
-        groupId
-        // TODO
+        const clashes = ClashesProvider.get(structure).value!.interUnit
+        return InterClashLoci(clashes, [groupId])
     }
     return EmptyLoci
 }

+ 7 - 8
src/mol-model/location.ts

@@ -7,7 +7,6 @@
 import { StructureElement } from './structure'
 import { Bond } from './structure/structure/unit/bonds'
 import { ShapeGroup } from './shape/shape';
-import { Interactions } from '../mol-model-props/computed/interactions/interactions';
 
 /** A null value Location */
 export const NullLocation = { kind: 'null-location' as 'null-location' }
@@ -17,17 +16,17 @@ export function isNullLocation(x: any): x is NullLocation {
 }
 
 /** A generic data Location */
-export interface DataLocation<T = unknown> {
+export interface DataLocation<T = unknown, E = unknown> {
     readonly kind: 'data-location',
-    data: T,
-    tag: string
-    index: number
+    readonly tag: string
+    readonly data: T,
+    element: E
 }
-export function DataLocation<T = unknown>(data?: T, tag = '', index = 0): DataLocation<T> {
-    return { kind: 'data-location', data: data!, tag, index }
+export function DataLocation<T = unknown, E = unknown>(tag: string, data: T, element: E): DataLocation<T, E> {
+    return { kind: 'data-location', tag, data, element }
 }
 export function isDataLocation(x: any): x is DataLocation {
     return !!x && x.kind === 'data-location';
 }
 
-export type Location = StructureElement.Location | Bond.Location | Interactions.Location | ShapeGroup.Location | DataLocation | NullLocation
+export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | DataLocation | NullLocation

+ 20 - 48
src/mol-model/loci.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,14 +8,11 @@ import { StructureElement } from './structure'
 import { Bond } from './structure/structure/unit/bonds'
 import { Shape, ShapeGroup } from './shape';
 import { Sphere3D } from '../mol-math/geometry';
-import { CentroidHelper } from '../mol-math/geometry/centroid-helper';
 import { Vec3 } from '../mol-math/linear-algebra';
-import { OrderedSet } from '../mol-data/int';
 import { Structure } from './structure/structure';
 import { PrincipalAxes } from '../mol-math/linear-algebra/matrix/principal-axes';
 import { ParamDefinition } from '../mol-util/param-definition';
-import { Interactions } from '../mol-model-props/computed/interactions/interactions';
-import { Features } from '../mol-model-props/computed/interactions/features';
+import { shallowEqual } from '../mol-util';
 
 /** A Loci that includes every loci */
 export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
@@ -32,28 +29,36 @@ export function isEmptyLoci(x?: Loci): x is EmptyLoci {
 }
 
 /** A generic data loci */
-export interface DataLoci<T = unknown> {
+export interface DataLoci<T = unknown, E = unknown> {
     readonly kind: 'data-loci',
-    readonly data: T,
     readonly tag: string
-    readonly indices: OrderedSet<number>
+    readonly data: T,
+    readonly elements: ReadonlyArray<E>
+
+    getBoundingSphere(boundingSphere: Sphere3D): Sphere3D
+    getLabel(): string
 }
 export function isDataLoci(x?: Loci): x is DataLoci {
     return !!x && x.kind === 'data-loci';
 }
 export function areDataLociEqual(a: DataLoci, b: DataLoci) {
-    return a.data === b.data && a.tag === b.tag && OrderedSet.areEqual(a.indices, b.indices)
+    if (a.data !== b.data || a.tag !== b.tag) return false
+    if (a.elements.length !== b.elements.length) return false
+    for (let i = 0, il = a.elements.length; i < il; ++i) {
+        if (!shallowEqual(a.elements[i], b.elements[i])) return false
+    }
+    return true
 }
 export function isDataLociEmpty(loci: DataLoci) {
-    return OrderedSet.size(loci.indices) === 0 ? true : false
+    return loci.elements.length === 0 ? true : false
 }
-export function createDataLoci<T = unknown>(data: T, tag: string, indices: OrderedSet<number>): DataLoci<T> {
-    return { kind: 'data-loci', data, tag, indices }
+export function DataLoci<T = unknown, E = unknown>(tag: string, data: T, elements: ReadonlyArray<E>, getBoundingSphere: DataLoci<T, E>['getBoundingSphere'], getLabel: DataLoci<T, E>['getLabel']): DataLoci<T, E> {
+    return { kind: 'data-loci', tag, data, elements, getBoundingSphere, getLabel }
 }
 
 export { Loci }
 
-type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | Interactions.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci
+type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci
 
 namespace Loci {
     interface FiniteArray<T, L extends number = number> extends ReadonlyArray<T> { length: L };
@@ -74,9 +79,6 @@ namespace Loci {
         if (Bond.isLoci(lociA) && Bond.isLoci(lociB)) {
             return Bond.areLociEqual(lociA, lociB)
         }
-        if (Interactions.isLoci(lociA) && Interactions.isLoci(lociB)) {
-            return Interactions.areLociEqual(lociA, lociB)
-        }
         if (Shape.isLoci(lociA) && Shape.isLoci(lociB)) {
             return Shape.areLociEqual(lociA, lociB)
         }
@@ -97,7 +99,6 @@ namespace Loci {
         if (Structure.isLoci(loci)) return Structure.isLociEmpty(loci)
         if (StructureElement.Loci.is(loci)) return StructureElement.Loci.isEmpty(loci)
         if (Bond.isLoci(loci)) return Bond.isLociEmpty(loci)
-        if (Interactions.isLoci(loci)) return Interactions.isLociEmpty(loci)
         if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci)
         if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci)
         return false
@@ -111,21 +112,15 @@ namespace Loci {
                 loci = Structure.remapLoci(loci, data)
             } else if (Bond.isLoci(loci)) {
                 loci = Bond.remapLoci(loci, data)
-            } else if (Interactions.isLoci(loci)) {
-                // TODO might be too expensive
-                // loci = Interactions.remapLoci(loci, data)
             }
         }
         return loci
     }
 
-    const sphereHelper = new CentroidHelper(), tmpPos = Vec3();
-
     export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D): Sphere3D | undefined {
         if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
 
         if (!boundingSphere) boundingSphere = Sphere3D()
-        sphereHelper.reset();
 
         if (loci.kind === 'structure-loci') {
             return Sphere3D.copy(boundingSphere, loci.structure.boundary.sphere)
@@ -133,39 +128,19 @@ namespace Loci {
             return Sphere3D.copy(boundingSphere, StructureElement.Loci.getBoundary(loci).sphere);
         } else if (loci.kind === 'bond-loci') {
             return Bond.getBoundingSphere(loci, boundingSphere)
-        } else if (loci.kind === 'interaction-loci') {
-            const { unitsFeatures } = loci.interactions
-            for (const e of loci.contacts) {
-                Features.setPosition(tmpPos, e.unitA, e.indexA, unitsFeatures.get(e.unitA.id))
-                sphereHelper.includeStep(tmpPos)
-                Features.setPosition(tmpPos, e.unitB, e.indexB, unitsFeatures.get(e.unitB.id))
-                sphereHelper.includeStep(tmpPos);
-            }
-            sphereHelper.finishedIncludeStep();
-            for (const e of loci.contacts) {
-                Features.setPosition(tmpPos, e.unitA, e.indexA, unitsFeatures.get(e.unitA.id))
-                sphereHelper.radiusStep(tmpPos)
-                Features.setPosition(tmpPos, e.unitB, e.indexB, unitsFeatures.get(e.unitB.id))
-                sphereHelper.radiusStep(tmpPos);
-            }
         } else if (loci.kind === 'shape-loci') {
             return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
         } else if (loci.kind === 'group-loci') {
             return ShapeGroup.getBoundingSphere(loci, boundingSphere)
         } else if (loci.kind === 'data-loci') {
-            // TODO maybe add loci.getBoundingSphere()???
-            return void 0;
+            return loci.getBoundingSphere(boundingSphere)
         }
-
-        Vec3.copy(boundingSphere.center, sphereHelper.center)
-        boundingSphere.radius = Math.sqrt(sphereHelper.radiusSq)
-        return boundingSphere
     }
 
     const tmpSphere3D = Sphere3D.zero()
     export function getCenter(loci: Loci, center?: Vec3): Vec3 | undefined {
         const boundingSphere = getBoundingSphere(loci, tmpSphere3D)
-        return boundingSphere ? Vec3.copy(center || Vec3.zero(), boundingSphere.center) : undefined
+        return boundingSphere ? Vec3.copy(center || Vec3(), boundingSphere.center) : undefined
     }
 
     export function getPrincipalAxes(loci: Loci): PrincipalAxes | undefined {
@@ -178,9 +153,6 @@ namespace Loci {
         } else if (loci.kind === 'bond-loci') {
             // TODO
             return void 0;
-        } else if (loci.kind === 'interaction-loci') {
-            // TODO
-            return void 0;
         } else if (loci.kind === 'shape-loci') {
             // TODO
             return void 0;

+ 7 - 1
src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -15,6 +15,8 @@ import { OrderedSet } from '../../../../../mol-data/int';
 import { featureGroupLabel, featureTypeLabel } from '../../../../../mol-model-props/computed/interactions/common';
 import { Loci } from '../../../../../mol-model/loci';
 import { arraySetAdd } from '../../../../../mol-util/array';
+import { InteractionTypeColorThemeProvider } from '../../../../../mol-model-props/computed/themes/interaction-type';
+import { InteractionsRepresentationProvider } from '../../../../../mol-model-props/computed/representations/interactions';
 
 export const Interactions = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
     name: 'computed-interactions-prop',
@@ -97,12 +99,16 @@ export const Interactions = PluginBehavior.create<{ autoAttach: boolean, showToo
 
         register(): void {
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
+            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('interaction-type', InteractionTypeColorThemeProvider)
             this.ctx.lociLabels.addProvider(this.label);
+            this.ctx.structureRepresentation.registry.add('interactions', InteractionsRepresentationProvider)
         }
 
         unregister() {
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
+            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('interaction-type')
             this.ctx.lociLabels.removeProvider(this.label);
+            this.ctx.structureRepresentation.registry.remove('interactions')
         }
     },
     params: () => ({

+ 2 - 17
src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts

@@ -6,7 +6,7 @@
 
 import { ParamDefinition as PD } from '../../../../../mol-util/param-definition'
 import { PluginBehavior } from '../../../behavior';
-import { ValidationReport, ValidationReportProvider, IntraUnitClashes } from '../../../../../mol-model-props/rcsb/validation-report';
+import { ValidationReport, ValidationReportProvider } from '../../../../../mol-model-props/rcsb/validation-report';
 import { RandomCoilIndexColorThemeProvider } from '../../../../../mol-model-props/rcsb/themes/random-coil-index';
 import { GeometryQualityColorThemeProvider } from '../../../../../mol-model-props/rcsb/themes/geometry-quality';
 import { Loci } from '../../../../../mol-model/loci';
@@ -23,24 +23,10 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
     ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
         private provider = ValidationReportProvider
 
-        private labelClashes = (loci: Loci): string | undefined => {
-            if (!this.params.showTooltip) return;
-
-            if (loci.kind === 'data-loci' && loci.tag === 'clashes') {
-                const idx = OrderedSet.start(loci.indices)
-                const clashes = loci.data as IntraUnitClashes
-                const { edgeProps: { id, magnitude, distance } } = clashes
-                const mag = magnitude[idx].toFixed(2)
-                const dist = distance[idx].toFixed(2)
-                return `RCSB Clash id: ${id[idx]} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`
-            }
-        }
-
         register(): void {
             this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
 
             this.ctx.lociLabels.addProvider(geometryQualityLabelProvider);
-            this.ctx.lociLabels.addProvider(this.labelClashes);
 
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(Tag.DensityFit, DensityFitColorThemeProvider)
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(Tag.GeometryQuality, GeometryQualityColorThemeProvider)
@@ -61,7 +47,6 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
 
             this.ctx.lociLabels.removeProvider(geometryQualityLabelProvider);
-            this.ctx.lociLabels.removeProvider(this.labelClashes);
 
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(Tag.DensityFit)
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(Tag.GeometryQuality)
@@ -80,7 +65,7 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
 function geometryQualityLabelProvider(loci: Loci): string | undefined {
     switch (loci.kind) {
         case 'element-loci':
-            if (loci.elements.length === 0) return void 0;
+            if (loci.elements.length === 0) return;
             const e = loci.elements[0];
             const geometryIssues = ValidationReportProvider.get(e.unit.model).value?.geometryIssues
             if (!geometryIssues) return

+ 5 - 3
src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 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>
@@ -20,6 +20,8 @@ import { ButtonsType, ModifiersKeys } from '../../../../mol-util/input/input-obs
 import { Binding } from '../../../../mol-util/binding';
 import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
 import { isEmptyLoci, Loci, EmptyLoci } from '../../../../mol-model/loci';
+import { InteractionsRepresentationProvider } from '../../../../mol-model-props/computed/representations/interactions';
+import { InteractionTypeColorThemeProvider } from '../../../../mol-model-props/computed/themes/interaction-type';
 
 const B = ButtonsType
 const M = ModifiersKeys
@@ -63,8 +65,8 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
 
     private createSurNciVisualParams(s: Structure) {
         return StructureRepresentation3DHelpers.createParams(this.plugin, s, {
-            repr: [BuiltInStructureRepresentations['interactions'], () => ({ })],
-            color: [BuiltInColorThemes['interaction-type'], () => ({ })],
+            repr: [InteractionsRepresentationProvider, () => ({ })],
+            color: [InteractionTypeColorThemeProvider, () => ({ })],
             size: [BuiltInSizeThemes.uniform, () => ({ })]
         });
     }

+ 5 - 6
src/mol-repr/structure/complex-representation.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
@@ -14,10 +14,9 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
 import { Theme } from '../../mol-theme/theme';
 import { Task } from '../../mol-task';
 import { PickingId } from '../../mol-geo/geometry/picking';
-import { EmptyLoci, Loci, isEveryLoci } from '../../mol-model/loci';
+import { EmptyLoci, Loci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
 import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
 import { Overpaint } from '../../mol-theme/overpaint';
-import { Interactions } from '../../mol-model-props/computed/interactions/interactions';
 
 export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number) => ComplexVisual<P>): StructureRepresentation<P> {
     let version = 0
@@ -68,14 +67,14 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
     function mark(loci: Loci, action: MarkerAction) {
         if (!_structure) return false
         if (!MarkerActions.is(_state.markerActions, action)) return false
-        if (Structure.isLoci(loci) || StructureElement.Loci.is(loci) || Bond.isLoci(loci) || Interactions.isLoci(loci)) {
+        if (Structure.isLoci(loci) || StructureElement.Loci.is(loci) || Bond.isLoci(loci)) {
             if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false
             // Remap `loci` from equivalent structure to the current `_structure`
             loci = Loci.remap(loci, _structure)
-            if (Loci.isEmpty(loci)) return false
-        } else if (!isEveryLoci(loci)) {
+        } else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
             return false
         }
+        if (Loci.isEmpty(loci)) return false
         return visual ? visual.mark(loci, action) : false
     }
 

+ 0 - 2
src/mol-repr/structure/registry.ts

@@ -19,7 +19,6 @@ import { MolecularSurfaceRepresentationProvider } from './representation/molecul
 import { EllipsoidRepresentationProvider } from './representation/ellipsoid';
 import { OrientationRepresentationProvider } from './representation/orientation';
 import { LabelRepresentationProvider } from './representation/label';
-import { InteractionsRepresentationProvider } from './representation/interactions';
 
 export class StructureRepresentationRegistry extends RepresentationRegistry<Structure, StructureRepresentationState> {
     constructor() {
@@ -39,7 +38,6 @@ export const BuiltInStructureRepresentations = {
     'ellipsoid': EllipsoidRepresentationProvider,
     'gaussian-surface': GaussianSurfaceRepresentationProvider,
     // 'gaussian-volume': GaussianVolumeRepresentationProvider, // TODO disabled for now, needs more work
-    'interactions': InteractionsRepresentationProvider,
     'label': LabelRepresentationProvider,
     'molecular-surface': MolecularSurfaceRepresentationProvider,
     'orientation': OrientationRepresentationProvider,

+ 5 - 6
src/mol-repr/structure/units-representation.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
@@ -17,10 +17,9 @@ import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-obj
 import { Theme } from '../../mol-theme/theme';
 import { Task } from '../../mol-task';
 import { PickingId } from '../../mol-geo/geometry/picking';
-import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci } from '../../mol-model/loci';
+import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci } from '../../mol-model/loci';
 import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
 import { Overpaint } from '../../mol-theme/overpaint';
-import { Interactions } from '../../mol-model-props/computed/interactions/interactions';
 import { Transparency } from '../../mol-theme/transparency';
 import { Mat4, EPSILON } from '../../mol-math/linear-algebra';
 
@@ -174,14 +173,14 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
     function mark(loci: Loci, action: MarkerAction) {
         if (!_structure) return false
         if (!MarkerActions.is(_state.markerActions, action)) return false
-        if (Structure.isLoci(loci) || StructureElement.Loci.is(loci) || Bond.isLoci(loci) || Interactions.isLoci(loci)) {
+        if (Structure.isLoci(loci) || StructureElement.Loci.is(loci) || Bond.isLoci(loci)) {
             if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false
             // Remap `loci` from equivalent structure to the current `_structure`
             loci = Loci.remap(loci, _structure)
-            if (Loci.isEmpty(loci)) return false
-        } else if (!isEveryLoci(loci)) {
+        } else if (!isEveryLoci(loci) && !isDataLoci(loci)) {
             return false
         }
+        if (Loci.isEmpty(loci)) return false
 
         let changed = false
         visuals.forEach(({ visual }) => {

+ 0 - 2
src/mol-theme/color.ts

@@ -33,7 +33,6 @@ import { ModelIndexColorThemeProvider } from './color/model-index';
 import { OccupancyColorThemeProvider } from './color/occupancy';
 import { OperatorNameColorThemeProvider } from './color/operator-name';
 import { OperatorHklColorThemeProvider } from './color/operator-hkl';
-import { InteractionTypeColorThemeProvider } from './color/interaction-type';
 
 export type LocationColor = (location: Location, isSecondary: boolean) => Color
 
@@ -83,7 +82,6 @@ export const BuiltInColorThemes = {
     'entity-source': EntitySourceColorThemeProvider,
     'hydrophobicity': HydrophobicityColorThemeProvider,
     'illustrative': IllustrativeColorThemeProvider,
-    'interaction-type': InteractionTypeColorThemeProvider,
     'model-index': ModelIndexColorThemeProvider,
     'molecule-type': MoleculeTypeColorThemeProvider,
     'occupancy': OccupancyColorThemeProvider,

+ 4 - 21
src/mol-theme/label.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
@@ -10,8 +10,6 @@ import { Loci } from '../mol-model/loci';
 import { OrderedSet } from '../mol-data/int';
 import { capitalize, stripTags } from '../mol-util/string';
 import { Column } from '../mol-data/db';
-import { Interactions } from '../mol-model-props/computed/interactions/interactions';
-import { interactionTypeLabel } from '../mol-model-props/computed/interactions/common';
 
 export type LabelGranularity = 'element' | 'conformation' | 'residue' | 'chain' | 'structure'
 
@@ -31,21 +29,18 @@ export function lociLabel(loci: Loci, options: Partial<LabelOptions> = {}): stri
             return structureElementStatsLabel(StructureElement.Stats.ofLoci(loci), options)
         case 'bond-loci':
             const bond = loci.bonds[0]
-            return bond ? bondLabel(bond) : 'Unknown'
-        case 'interaction-loci':
-            const contact = loci.contacts[0]
-            return contact ? interactionLabel(Interactions.Location(loci.interactions, contact.unitA, contact.indexA, contact.unitB, contact.indexB)) : 'Unknown'
+            return bond ? bondLabel(bond) : ''
         case 'shape-loci':
             return loci.shape.name
         case 'group-loci':
             const g = loci.groups[0]
-            return g ? loci.shape.getLabel(OrderedSet.start(g.ids), g.instance) : 'Unknown'
+            return g ? loci.shape.getLabel(OrderedSet.start(g.ids), g.instance) : ''
         case 'every-loci':
             return 'Everything'
         case 'empty-loci':
             return 'Nothing'
         case 'data-loci':
-            return ''
+            return loci.getLabel()
     }
 }
 
@@ -128,18 +123,6 @@ export function bondLabel(bond: Bond.Location): string {
     return `${labelA.join(' | ')} \u2014 ${labelB.slice(offset).join(' | ')}`
 }
 
-export function interactionLabel(location: Interactions.Location): string {
-    const { interactions, unitA, indexA, unitB, indexB } = location
-    if (location.unitA === location.unitB) {
-        const contacts = interactions.unitsContacts.get(location.unitA.id)
-        const idx = contacts.getDirectedEdgeIndex(location.indexA, location.indexB)
-        return interactionTypeLabel(contacts.edgeProps.type[idx])
-    } else {
-        const idx = interactions.contacts.getEdgeIndex(indexA, unitA, indexB, unitB)
-        return interactionTypeLabel(interactions.contacts.edges[idx].props.type)
-    }
-}
-
 export function elementLabel(location: StructureElement.Location, options: Partial<LabelOptions> = {}): string {
     const o = { ...DefaultLabelOptions, ...options }
     const label = _elementLabel(location, o.granularity, o.hidePrefix).join(' | ')

+ 1 - 1
src/tests/browser/render-structure.ts

@@ -21,7 +21,7 @@ import { throttleTime } from 'rxjs/operators';
 import { MarkerAction } from '../../mol-util/marker-action';
 import { EveryLoci } from '../../mol-model/loci';
 import { lociLabel } from '../../mol-theme/label';
-import { InteractionsRepresentationProvider } from '../../mol-repr/structure/representation/interactions';
+import { InteractionsRepresentationProvider } from '../../mol-model-props/computed/representations/interactions';
 import { InteractionsProvider } from '../../mol-model-props/computed/interactions';
 import { SecondaryStructureProvider } from '../../mol-model-props/computed/secondary-structure';
 import { SyncRuntimeContext } from '../../mol-task/execution/synchronous';