diff --git a/src/mol-model-props/computed/chemistry/geometry.ts b/src/mol-model-props/computed/chemistry/geometry.ts index fc196302f110aeef6ef5f1c116526a61712d61cb..67b5a17e60bf70e723ba9c3a3b55ac466f7eca11 100644 --- a/src/mol-model-props/computed/chemistry/geometry.ts +++ b/src/mol-model-props/computed/chemistry/geometry.ts @@ -27,6 +27,29 @@ export const enum AtomGeometry { Unknown = 8 } +export function geometryLabel(geometry: AtomGeometry): string { + switch (geometry) { + case AtomGeometry.Spherical: + return 'Spherical' + case AtomGeometry.Terminal: + return 'Terminal' + case AtomGeometry.Linear: + return 'Linear' + case AtomGeometry.Trigonal: + return 'Trigonal' + case AtomGeometry.Tetrahedral: + return 'Tetrahedral' + case AtomGeometry.TrigonalBiPyramidal: + return 'Trigonal Bi-Pyramidal' + case AtomGeometry.Octahedral: + return 'Octahedral' + case AtomGeometry.SquarePlanar: + return 'Square Planar' + case AtomGeometry.Unknown: + return 'Unknown' + } +} + export function assignGeometry (totalCoordination: number): AtomGeometry { switch (totalCoordination) { case 0: return AtomGeometry.Spherical diff --git a/src/mol-model-props/computed/interactions/common.ts b/src/mol-model-props/computed/interactions/common.ts index ff8f94a2827d3ac60e3673cbff6f144684f3b013..04c197b8028ab936a1e3964e7fa65c93a9a5dd75 100644 --- a/src/mol-model-props/computed/interactions/common.ts +++ b/src/mol-model-props/computed/interactions/common.ts @@ -31,6 +31,31 @@ export const enum InteractionType { BackboneHydrogenBond = 10 } +export function interactionTypeLabel(type: InteractionType): string { + switch (type) { + case InteractionType.HydrogenBond: + case InteractionType.WaterHydrogenBond: + case InteractionType.BackboneHydrogenBond: + return 'Hydrogen Bond' + case InteractionType.Hydrophobic: + return 'Hydrophobic Contact' + case InteractionType.HalogenBond: + return 'Halogen Bond' + case InteractionType.IonicInteraction: + return 'Ionic Interaction' + case InteractionType.MetalCoordination: + return 'Metal Coordination' + case InteractionType.CationPi: + return 'Cation-Pi Interaction' + case InteractionType.PiStacking: + return 'Pi Stacking' + case InteractionType.WeakHydrogenBond: + return 'Weak Hydrogen Bond' + case InteractionType.Unknown: + return 'Unknown Interaction' + } +} + export const enum FeatureType { None = 0, PositiveCharge = 1, @@ -48,6 +73,39 @@ export const enum FeatureType { IonicTypeMetal = 13 } +export function featureTypeLabel(type: FeatureType): string { + switch (type) { + case FeatureType.None: + return 'None' + case FeatureType.PositiveCharge: + return 'Positive Charge' + case FeatureType.NegativeCharge: + return 'Negative Charge' + case FeatureType.AromaticRing: + return 'Aromatic Ring' + case FeatureType.HydrogenDonor: + return 'Hydrogen Donor' + case FeatureType.HydrogenAcceptor: + return 'Hydrogen Acceptor' + case FeatureType.HalogenDonor: + return 'Halogen Donor' + case FeatureType.HalogenAcceptor: + return 'Halogen Acceptor' + case FeatureType.Hydrophobic: + return 'Hydrophobic' + case FeatureType.WeakHydrogenDonor: + return 'Weak Hydrogen Donor' + case FeatureType.IonicTypePartner: + return 'Ionic Type Partner' + case FeatureType.DativeBondPartner: + return 'Dative Bond Partner' + case FeatureType.TransitionMetal: + return 'Transition Metal' + case FeatureType.IonicTypeMetal: + return 'Ionic Type Metal' + } +} + export const enum FeatureGroup { None = 0, QuaternaryAmine = 1, @@ -60,4 +118,31 @@ export const enum FeatureGroup { Guanidine = 8, Acetamidine = 9, Carboxylate = 10 +} + +export function featureGroupLabel(group: FeatureGroup): string { + switch (group) { + case FeatureGroup.None: + return 'None' + case FeatureGroup.QuaternaryAmine: + return 'Quaternary Amine' + case FeatureGroup.TertiaryAmine: + return 'Tertiary Amine' + case FeatureGroup.Sulfonium: + return 'Sulfonium' + case FeatureGroup.SulfonicAcid: + return 'Sulfonic Acid' + case FeatureGroup.Sulfate: + return 'Sulfate' + case FeatureGroup.Phosphate: + return 'Phosphate' + case FeatureGroup.Halocarbon: + return 'Halocarbon' + case FeatureGroup.Guanidine: + return 'Guanidine' + case FeatureGroup.Acetamidine: + return 'Acetamidine' + case FeatureGroup.Carboxylate: + return 'Carboxylate' + } } \ No newline at end of file diff --git a/src/mol-model-props/computed/interactions/features.ts b/src/mol-model-props/computed/interactions/features.ts index e8315ae1b4b8aff57a6cafbc1c478c1b2c04ec73..849960e1510b4ab562e0eaefd0870e302927e137 100644 --- a/src/mol-model-props/computed/interactions/features.ts +++ b/src/mol-model-props/computed/interactions/features.ts @@ -13,21 +13,24 @@ import { FeatureGroup, FeatureType } from './common'; export { Features } interface Features { + /** number of features */ + readonly count: number /** center x coordinate, in invariant coordinate space */ readonly x: ArrayLike<number> /** center y coordinate, in invariant coordinate space */ readonly y: ArrayLike<number> /** center z coordinate, in invariant coordinate space */ readonly z: ArrayLike<number> - /** number of features */ - readonly count: number readonly types: ArrayLike<FeatureType> readonly groups: ArrayLike<FeatureGroup> readonly offsets: ArrayLike<number> /** elements of this feature, range for feature i is offsets[i] to offsets[i + 1] */ readonly members: ArrayLike<StructureElement.UnitIndex> + /** lookup3d based on center coordinates, in invariant coordinate space */ readonly lookup3d: GridLookup3D + /** maps unit elements to features, range for unit element i is offsets[i] to offsets[i + 1] */ + readonly elementsIndex: Features.ElementsIndex } namespace Features { @@ -42,11 +45,22 @@ namespace Features { readonly offsets: ArrayLike<number> } - export function createElementsIndex(features: Features, elementsCount: number): ElementsIndex { + export type Data = { + count: number + x: ArrayLike<number> + y: ArrayLike<number> + z: ArrayLike<number> + types: ArrayLike<FeatureType> + groups: ArrayLike<FeatureGroup> + offsets: ArrayLike<number> + members: ArrayLike<StructureElement.UnitIndex> + } + + export function createElementsIndex(data: Data, elementsCount: number): ElementsIndex { const offsets = new Int32Array(elementsCount + 1) const bucketFill = new Int32Array(elementsCount) const bucketSizes = new Int32Array(elementsCount) - const { members, count, offsets: featureOffsets } = features + const { members, count, offsets: featureOffsets } = data for (let i = 0; i < count; ++i) ++bucketSizes[members[i]] let offset = 0 @@ -68,6 +82,21 @@ namespace Features { return { indices: indices as unknown as ArrayLike<FeatureIndex>, offsets } } + + export function create(elementsCount: number, data: Data): Features { + let lookup3d: GridLookup3D + let elementsIndex: ElementsIndex + + return { + ...data, + get lookup3d() { + return lookup3d || (lookup3d = GridLookup3D({ x: data.x, y: data.y, z: data.z, indices: OrderedSet.ofBounds(0, data.count) })) + }, + get elementsIndex() { + return elementsIndex || (elementsIndex = createElementsIndex(data, elementsCount)) + }, + } + } } export { FeaturesBuilder } @@ -77,7 +106,7 @@ interface FeaturesBuilder { pushMember: (x: number, y: number, z: number, member: StructureElement.UnitIndex) => void addState: (type: FeatureType, group: FeatureGroup) => void addOne: (type: FeatureType, group: FeatureGroup, x: number, y: number, z: number, member: StructureElement.UnitIndex) => void - getFeatures: () => Features + getFeatures: (elementsCount: number) => Features } namespace FeaturesBuilder { @@ -121,20 +150,19 @@ namespace FeaturesBuilder { ChunkedArray.add(offsets, members.elementCount) ChunkedArray.add(members, member) }, - getFeatures: () => { + getFeatures: (elementsCount: number) => { ChunkedArray.add(offsets, members.elementCount) const x = ChunkedArray.compact(xCenters, true) as ArrayLike<number> const y = ChunkedArray.compact(yCenters, true) as ArrayLike<number> const z = ChunkedArray.compact(zCenters, true) as ArrayLike<number> const count = xCenters.elementCount - return { + return Features.create(elementsCount, { x, y, z, count, types: ChunkedArray.compact(types, true) as ArrayLike<FeatureType>, groups: ChunkedArray.compact(groups, true) as ArrayLike<FeatureGroup>, offsets: ChunkedArray.compact(offsets, true) as ArrayLike<number>, members: ChunkedArray.compact(members, true) as ArrayLike<StructureElement.UnitIndex>, - lookup3d: GridLookup3D({ x, y, z, indices: OrderedSet.ofBounds(0, count) }), - } + }) } } } diff --git a/src/mol-model-props/computed/interactions/interactions.ts b/src/mol-model-props/computed/interactions/interactions.ts index 8f3adea55c8425bcd53f11e748c1d8ca4d11933e..fe0a53513f37b222ce33668a2a9f374a03b745e3 100644 --- a/src/mol-model-props/computed/interactions/interactions.ts +++ b/src/mol-model-props/computed/interactions/interactions.ts @@ -10,7 +10,7 @@ import { RuntimeContext } from '../../../mol-task'; import { addUnitHydrogenDonors, addUnitWeakHydrogenDonors, addUnitHydrogenAcceptors, addUnitHydrogenBonds, HydrogenBondsParams, addStructureHydrogenBonds } from './hydrogen-bonds'; import { Features, FeaturesBuilder } from './features'; import { ValenceModelProvider } from '../valence-model'; -import { InteractionsIntraLinks, InteractionsInterLinks, InteractionType } from './common'; +import { InteractionsIntraLinks, InteractionsInterLinks } from './common'; import { IntraLinksBuilder, InterLinksBuilder } from './builder'; import { IntMap } from '../../../mol-data/int'; import { Vec3 } from '../../../mol-math/linear-algebra'; @@ -94,31 +94,6 @@ namespace Interactions { export function isLociEmpty(loci: Loci) { return loci.links.length === 0 ? true : false } - - export function typeLabel(type: InteractionType): string { - switch (type) { - case InteractionType.HydrogenBond: - case InteractionType.WaterHydrogenBond: - case InteractionType.BackboneHydrogenBond: - return 'Hydrogen Bond' - case InteractionType.Hydrophobic: - return 'Hydrophobic Contact' - case InteractionType.HalogenBond: - return 'Halogen Bond' - case InteractionType.IonicInteraction: - return 'Ionic Interaction' - case InteractionType.MetalCoordination: - return 'Metal Coordination' - case InteractionType.CationPi: - return 'Cation-Pi Interaction' - case InteractionType.PiStacking: - return 'Pi Stacking' - case InteractionType.WeakHydrogenBond: - return 'Weak Hydrogen Bond' - case InteractionType.Unknown: - return 'Unknown Interaction' - } - } } export const InteractionsParams = { @@ -150,16 +125,16 @@ export async function computeInteractions(runtime: RuntimeContext, structure: St } function findIntraUnitLinksAndFeatures(structure: Structure, unit: Unit, props: InteractionsProps) { - - const featuresBuilder = FeaturesBuilder.create() + const count = unit.elements.length + const featuresBuilder = FeaturesBuilder.create(count, count / 2) if (Unit.isAtomic(unit)) { addUnitHydrogenDonors(structure, unit, featuresBuilder) addUnitWeakHydrogenDonors(structure, unit, featuresBuilder) addUnitHydrogenAcceptors(structure, unit, featuresBuilder) } - const features = featuresBuilder.getFeatures() + const features = featuresBuilder.getFeatures(count) - const linksBuilder = IntraLinksBuilder.create(features, unit.elements.length) + const linksBuilder = IntraLinksBuilder.create(features, count) if (Unit.isAtomic(unit)) { addUnitHydrogenBonds(structure, unit, features, linksBuilder, props.hydrogenBonds) } diff --git a/src/mol-plugin/behavior/dynamic/custom-props.ts b/src/mol-plugin/behavior/dynamic/custom-props.ts index bc0dd592af040343dd18d9744fe3754435c591e1..38578d8b8a0f91bb339e21058cdbc282a2ea7f14 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props.ts @@ -8,6 +8,7 @@ export { AccessibleSurfaceArea } from './custom-props/computed/accessible-surface-area' export { Interactions } from './custom-props/computed/interactions' export { SecondaryStructure } from './custom-props/computed/secondary-structure' +export { ValenceModel } from './custom-props/computed/valence-model' export { PDBeStructureQualityReport } from './custom-props/pdbe/structure-quality-report' export { RCSBAssemblySymmetry } from './custom-props/rcsb/assembly-symmetry' \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts b/src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts index c13d0718301c81041c8d2f3ed720a267a00787a2..c5ad24210dc9ff3ca43e82bc1de8cec0211a0868 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts @@ -7,32 +7,105 @@ import { PluginBehavior } from '../../../behavior'; import { ParamDefinition as PD } from '../../../../../mol-util/param-definition'; import { InteractionsProvider } from '../../../../../mol-model-props/computed/interactions'; +import { Structure } from '../../../../../mol-model/structure'; +import { StateSelection } from '../../../../../mol-state'; +import { PluginStateObject } from '../../../../state/objects'; +import StructureElement from '../../../../../mol-model/structure/structure/element'; +import { OrderedSet } from '../../../../../mol-data/int'; +import { featureGroupLabel, featureTypeLabel } from '../../../../../mol-model-props/computed/interactions/common'; +import { Loci } from '../../../../../mol-model/loci'; -export const Interactions = PluginBehavior.create<{ autoAttach: boolean }>({ +export const Interactions = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({ name: 'computed-interactions-prop', category: 'custom-props', display: { name: 'Interactions' }, - ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> { + ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> { private provider = InteractionsProvider + private getStructures(structure: Structure) { + const structures: Structure[] = [] + const root = this.ctx.helpers.substructureParent.get(structure) + if (root) { + const state = this.ctx.state.dataState + const selections = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure, root.transform.ref)); + for (const s of selections) { + if (s.obj) structures.push(s.obj.data) + } + } + return structures + } + + private label = (loci: Loci): string | undefined => { + if (!this.params.showTooltip) return void 0; + + switch (loci.kind) { + case 'element-loci': + if (loci.elements.length === 0) return void 0; + + const labels: string[] = [] + const structures = this.getStructures(loci.structure) + + for (const s of structures) { + const interactions = this.provider.getValue(s).value + if (!interactions) continue; + + const l = StructureElement.Loci.remap(loci, s) + if (l.elements.length !== 1) continue + + const e = l.elements[0] + if (OrderedSet.size(e.indices) !== 1) continue + + const features = interactions.unitsFeatures.get(e.unit.id) + if (!features) continue; + + const typeLabels: string[] = [] + const groupLabels: string[] = [] + const label: string[] = [] + + const idx = OrderedSet.start(e.indices) + const { types, groups, elementsIndex: { indices, offsets } } = features + for (let i = offsets[idx], il = offsets[idx + 1]; i < il; ++i) { + const f = indices[i] + const type = types[f] + const group = groups[f] + if (type) typeLabels.push(featureTypeLabel(type)) + if (group) groupLabels.push(featureGroupLabel(group)) + } + + if (typeLabels.length) label.push(`<small>Types</small> ${typeLabels.join(', ')}`) + if (groupLabels.length) label.push(`<small>Groups</small> ${groupLabels.join(', ')}`) + if (label.length) labels.push(`Interaction Feature: ${label.join(' | ')}`) + } + + return labels.length ? labels.join('<br/>') : undefined; + + default: return void 0; + } + } + update(p: { autoAttach: boolean, showTooltip: boolean }) { let updated = ( - this.params.autoAttach !== p.autoAttach + this.params.autoAttach !== p.autoAttach || + this.params.showTooltip !== p.showTooltip ) this.params.autoAttach = p.autoAttach; + this.params.showTooltip = p.showTooltip; this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach); return updated; } register(): void { this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach); + this.ctx.lociLabels.addProvider(this.label); } unregister() { this.ctx.customStructureProperties.unregister(this.provider.descriptor.name); + this.ctx.lociLabels.removeProvider(this.label); } }, params: () => ({ - autoAttach: PD.Boolean(false) + autoAttach: PD.Boolean(false), + showTooltip: PD.Boolean(true) }) }); \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/custom-props/computed/valence-model.ts b/src/mol-plugin/behavior/dynamic/custom-props/computed/valence-model.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f7f34ae9b93c9adf2f1c965c80dd04996418ec2 --- /dev/null +++ b/src/mol-plugin/behavior/dynamic/custom-props/computed/valence-model.ts @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PluginBehavior } from '../../../behavior'; +import { ParamDefinition as PD } from '../../../../../mol-util/param-definition'; +import { ValenceModelProvider } from '../../../../../mol-model-props/computed/valence-model'; +import { Loci } from '../../../../../mol-model/loci'; +import { PluginStateObject } from '../../../../state/objects'; +import { StateSelection } from '../../../../../mol-state'; +import { Structure, StructureElement } from '../../../../../mol-model/structure'; +import { OrderedSet } from '../../../../../mol-data/int'; +import { geometryLabel } from '../../../../../mol-model-props/computed/chemistry/geometry'; + +export const ValenceModel = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({ + name: 'computed-valence-model-prop', + category: 'custom-props', + display: { name: 'Valence Model' }, + ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> { + private provider = ValenceModelProvider + + private getStructures(structure: Structure) { + const structures: Structure[] = [] + const root = this.ctx.helpers.substructureParent.get(structure) + if (root) { + const state = this.ctx.state.dataState + const selections = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure, root.transform.ref)); + for (const s of selections) { + if (s.obj) structures.push(s.obj.data) + } + } + return structures + } + + private label = (loci: Loci): string | undefined => { + if (!this.params.showTooltip) return void 0; + + switch (loci.kind) { + case 'element-loci': + if (loci.elements.length === 0) return void 0; + + const labels: string[] = [] + const structures = this.getStructures(loci.structure) + + for (const s of structures) { + const valenceModel = this.provider.getValue(s).value + if (!valenceModel) continue; + + const l = StructureElement.Loci.remap(loci, s) + if (l.elements.length !== 1) continue + + const e = l.elements[0] + if (OrderedSet.size(e.indices) !== 1) continue + + const vm = valenceModel.get(e.unit.id) + if (!vm) continue; + + const idx = OrderedSet.start(e.indices) + const charge = vm.charge[idx] + const idealGeometry = vm.idealGeometry[idx] + const implicitH = vm.implicitH[idx] + const totalH = vm.totalH[idx] + + labels.push(`Valence Model: <small>Charge</small> ${charge} | <small>Ideal Geometry</small> ${geometryLabel(idealGeometry)} | <small>Implicit H</small> ${implicitH} | <small>Total H</small> ${totalH}`) + } + + return labels.length ? labels.join('<br/>') : undefined; + + default: return void 0; + } + } + + update(p: { autoAttach: boolean, showTooltip: boolean }) { + let updated = ( + this.params.autoAttach !== p.autoAttach || + this.params.showTooltip !== p.showTooltip + ) + this.params.autoAttach = p.autoAttach; + this.params.showTooltip = p.showTooltip; + this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach); + return updated; + } + + register(): void { + this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach); + this.ctx.lociLabels.addProvider(this.label); + } + + unregister() { + this.ctx.customStructureProperties.unregister(this.provider.descriptor.name); + this.ctx.lociLabels.removeProvider(this.label); + } + }, + params: () => ({ + autoAttach: PD.Boolean(false), + showTooltip: PD.Boolean(true) + }) +}); \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 242f8ecf468172fc9abc39b19b54857ae9b7580f..a0290122233d5b104399c40987aac1bbb4e1054e 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -71,6 +71,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Behavior(PluginBehaviors.CustomProps.AccessibleSurfaceArea), PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions), PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure), + PluginSpec.Behavior(PluginBehaviors.CustomProps.ValenceModel), PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true, showTooltip: true }), PluginSpec.Behavior(PluginBehaviors.CustomProps.RCSBAssemblySymmetry, { autoAttach: true }), PluginSpec.Behavior(StructureRepresentationInteraction) diff --git a/src/mol-plugin/util/substructure-parent-helper.ts b/src/mol-plugin/util/substructure-parent-helper.ts index eef04b2f0b03cdea05909edd79ce65e309c702b7..29f7c8e3e2315eb0e3846e6bb80c8e3146339b60 100644 --- a/src/mol-plugin/util/substructure-parent-helper.ts +++ b/src/mol-plugin/util/substructure-parent-helper.ts @@ -15,6 +15,7 @@ class SubstructureParentHelper { private root = new Map<Structure, { ref: string, count: number }>(); private tracked = new Map<string, Structure>(); + /** Returns the root node of given structure if existing */ get(s: Structure): StateObjectCell<PluginStateObject.Molecule.Structure> | undefined { const r = this.root.get(s); if (!r) return; diff --git a/src/mol-theme/label.ts b/src/mol-theme/label.ts index fe4cd0145bf39a131d7062b4a8c36c9e90ea4b46..0538f07e91abcbbef98f36d7230783e3e3828ee5 100644 --- a/src/mol-theme/label.ts +++ b/src/mol-theme/label.ts @@ -11,6 +11,7 @@ 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' @@ -132,10 +133,10 @@ export function interactionLabel(location: Interactions.Location): string { if (location.unitA === location.unitB) { const links = interactions.unitsLinks.get(location.unitA.id) const idx = links.getDirectedEdgeIndex(location.indexA, location.indexB) - return Interactions.typeLabel(links.edgeProps.type[idx]) + return interactionTypeLabel(links.edgeProps.type[idx]) } else { const idx = interactions.links.getEdgeIndex(indexA, unitA, indexB, unitB) - return Interactions.typeLabel(interactions.links.edges[idx].props.type) + return interactionTypeLabel(interactions.links.edges[idx].props.type) } }