Browse Source

added distanceLabel, angleLabel, dihedralLabel

Alexander Rose 5 years ago
parent
commit
b19f53d380

+ 4 - 22
src/mol-repr/shape/loci/angle.ts

@@ -22,7 +22,7 @@ import { radToDeg, arcLength } from '../../../mol-math/misc';
 import { Circle } from '../../../mol-geo/primitive/circle';
 import { transformPrimitive } from '../../../mol-geo/primitive/primitive';
 import { MarkerActions, MarkerAction } from '../../../mol-util/marker-action';
-import { bundleLabel } from '../../../mol-theme/label';
+import { angleLabel } from '../../../mol-theme/label';
 
 export interface AngleData {
     triples: Loci.Bundle<3>[]
@@ -141,14 +141,8 @@ function getCircle(state: AngleState, segmentLength?: number) {
 
 const tmpState = getAngleState()
 
-function angleLabel(triple: Loci.Bundle<3>) {
-    setAngleState(triple, tmpState, 1)
-    const angle = radToDeg(tmpState.angle).toFixed(2)
-    return `Angle ${angle}\u00B0`
-}
-
 function getAngleName(data: AngleData) {
-    return data.triples.length === 1 ? angleLabel(data.triples[0]) : `${data.triples.length} Angles`
+    return data.triples.length === 1 ? angleLabel(data.triples[0], { measureOnly: true }) : `${data.triples.length} Angles`
 }
 
 //
@@ -216,13 +210,7 @@ function buildSectorMesh(data: AngleData, props: AngleProps, mesh?: Mesh): Mesh
 function getSectorShape(ctx: RuntimeContext, data: AngleData, props: AngleProps, shape?: Shape<Mesh>) {
     const mesh = buildSectorMesh(data, props, shape && shape.geometry);
     const name = getAngleName(data)
-    const getLabel = function (groupId: number ) {
-        const triple = data.triples[groupId]
-        return [
-            angleLabel(triple),
-            bundleLabel(triple)
-        ].join('</br>')
-    }
+    const getLabel = (groupId: number ) => angleLabel(data.triples[groupId])
     return Shape.create(name, data, mesh, () => props.color, () => 1, getLabel)
 }
 
@@ -247,13 +235,7 @@ function buildText(data: AngleData, props: AngleProps, text?: Text): Text {
 function getTextShape(ctx: RuntimeContext, data: AngleData, props: AngleProps, shape?: Shape<Text>) {
     const text = buildText(data, props, shape && shape.geometry);
     const name = getAngleName(data)
-    const getLabel = function (groupId: number ) {
-        const triple = data.triples[groupId]
-        return [
-            angleLabel(triple),
-            bundleLabel(triple)
-        ].join('</br>')
-    }
+    const getLabel = (groupId: number ) => angleLabel(data.triples[groupId])
     return Shape.create(name, data, text, () => props.textColor, () => props.textSize, getLabel)
 }
 

+ 4 - 21
src/mol-repr/shape/loci/dihedral.ts

@@ -22,7 +22,7 @@ import { arcLength, halfPI, radToDeg } from '../../../mol-math/misc';
 import { Circle } from '../../../mol-geo/primitive/circle';
 import { transformPrimitive } from '../../../mol-geo/primitive/primitive';
 import { MarkerActions, MarkerAction } from '../../../mol-util/marker-action';
-import { bundleLabel } from '../../../mol-theme/label';
+import { dihedralLabel } from '../../../mol-theme/label';
 
 export interface DihedralData {
     quads: Loci.Bundle<4>[]
@@ -183,14 +183,9 @@ function getCircle(state: DihedralState, segmentLength?: number) {
 }
 
 const tmpState = getDihedralState()
-function dihedralLabel(quad: Loci.Bundle<4>) {
-    setDihedralState(quad, tmpState, 1)
-    const angle = radToDeg(tmpState.angle).toFixed(2)
-    return `Dihedral ${angle}\u00B0`
-}
 
 function getDihedralName(data: DihedralData) {
-    return data.quads.length === 1 ? dihedralLabel(data.quads[0]) : `${data.quads.length} Dihedrals`
+    return data.quads.length === 1 ? dihedralLabel(data.quads[0], { measureOnly: true }) : `${data.quads.length} Dihedrals`
 }
 
 //
@@ -276,13 +271,7 @@ function buildSectorMesh(data: DihedralData, props: DihedralProps, mesh?: Mesh):
 function getSectorShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Mesh>) {
     const mesh = buildSectorMesh(data, props, shape && shape.geometry);
     const name = getDihedralName(data)
-    const getLabel = function (groupId: number ) {
-        const quad = data.quads[groupId]
-        return [
-            dihedralLabel(quad),
-            bundleLabel(quad)
-        ].join('</br>')
-    }
+    const getLabel = (groupId: number ) => dihedralLabel(data.quads[groupId])
     return Shape.create(name, data, mesh, () => props.color, () => 1, getLabel)
 }
 
@@ -307,13 +296,7 @@ function buildText(data: DihedralData, props: DihedralProps, text?: Text): Text
 function getTextShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Text>) {
     const text = buildText(data, props, shape && shape.geometry);
     const name = getDihedralName(data)
-    const getLabel = function (groupId: number ) {
-        const quad = data.quads[groupId]
-        return [
-            dihedralLabel(quad),
-            bundleLabel(quad)
-        ].join('</br>')
-    }
+    const getLabel = (groupId: number ) => dihedralLabel(data.quads[groupId])
     return Shape.create(name, data, text, () => props.textColor, () => props.textSize, getLabel)
 }
 

+ 4 - 21
src/mol-repr/shape/loci/distance.ts

@@ -17,7 +17,7 @@ import { LinesBuilder } from '../../../mol-geo/geometry/lines/lines-builder';
 import { TextBuilder } from '../../../mol-geo/geometry/text/text-builder';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { MarkerActions, MarkerAction } from '../../../mol-util/marker-action';
-import { bundleLabel } from '../../../mol-theme/label';
+import { distanceLabel } from '../../../mol-theme/label';
 
 export interface DistanceData {
     pairs: Loci.Bundle<2>[]
@@ -88,13 +88,8 @@ function setDistanceState(pair: Loci.Bundle<2>, state: DistanceState) {
 
 const tmpState = getDistanceState()
 
-function distanceLabel(pair: Loci.Bundle<2>, unitLabel: string) {
-    setDistanceState(pair, tmpState)
-    return `Distance ${tmpState.distance.toFixed(2)} ${unitLabel}`
-}
-
 function getDistanceName(data: DistanceData, unitLabel: string) {
-    return data.pairs.length === 1 ? distanceLabel(data.pairs[0], unitLabel) : `${data.pairs.length} Distances`
+    return data.pairs.length === 1 ? `Distance ${distanceLabel(data.pairs[0], { unitLabel, measureOnly: true })}` : `${data.pairs.length} Distances`
 }
 
 //
@@ -111,13 +106,7 @@ function buildLines(data: DistanceData, props: DistanceProps, lines?: Lines): Li
 function getLinesShape(ctx: RuntimeContext, data: DistanceData, props: DistanceProps, shape?: Shape<Lines>) {
     const lines = buildLines(data, props, shape && shape.geometry);
     const name = getDistanceName(data, props.unitLabel)
-    const getLabel = function (groupId: number ) {
-        const pair = data.pairs[groupId]
-        return [
-            distanceLabel(pair, props.unitLabel),
-            bundleLabel(pair)
-        ].join('</br>')
-    }
+    const getLabel = (groupId: number ) => distanceLabel(data.pairs[groupId], props)
     return Shape.create(name, data, lines, () => props.linesColor, () => props.linesSize, getLabel)
 }
 
@@ -137,13 +126,7 @@ function buildText(data: DistanceData, props: DistanceProps, text?: Text): Text
 function getTextShape(ctx: RuntimeContext, data: DistanceData, props: DistanceProps, shape?: Shape<Text>) {
     const text = buildText(data, props, shape && shape.geometry);
     const name = getDistanceName(data, props.unitLabel)
-    const getLabel = function (groupId: number ) {
-        const pair = data.pairs[groupId]
-        return [
-            distanceLabel(pair, props.unitLabel),
-            bundleLabel(pair)
-        ].join('</br>')
-    }
+    const getLabel = (groupId: number ) => distanceLabel(data.pairs[groupId], props)
     return Shape.create(name, data, text, () => props.textColor, () => props.textSize, getLabel)
 }
 

+ 69 - 28
src/mol-theme/label.ts

@@ -10,11 +10,15 @@ 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 { Vec3 } from '../mol-math/linear-algebra';
+import { radToDeg } from '../mol-math/misc';
 
 export type LabelGranularity = 'element' | 'conformation' | 'residue' | 'chain' | 'structure'
 
 export const DefaultLabelOptions = {
     granularity: 'element' as LabelGranularity,
+    condensed: false,
+    reverse: false,
     countsOnly: false,
     hidePrefix: false,
     htmlStyling: true,
@@ -63,40 +67,40 @@ function getResidueCount(unit: Unit.Atomic) {
 
 export function structureElementStatsLabel(stats: StructureElement.Stats, options: Partial<LabelOptions> = {}): string {
     const o = { ...DefaultLabelOptions, ...options }
-    const label = _structureElementStatsLabel(stats, o.countsOnly, o.hidePrefix)
+    const label = _structureElementStatsLabel(stats, o.countsOnly, o.hidePrefix, o.condensed)
     return o.htmlStyling ? label : stripTags(label)
 }
 
-function _structureElementStatsLabel(stats: StructureElement.Stats, countsOnly = false, hidePrefix = false): string {
+function _structureElementStatsLabel(stats: StructureElement.Stats, countsOnly = false, hidePrefix = false, condensed = false): string {
     const { structureCount, chainCount, residueCount, conformationCount, elementCount } = stats
 
     if (!countsOnly && elementCount === 1 && residueCount === 0 && chainCount === 0) {
-        return elementLabel(stats.firstElementLoc, { hidePrefix, granularity: 'element' })
+        return elementLabel(stats.firstElementLoc, { hidePrefix, condensed, granularity: 'element' })
     } else if (!countsOnly && elementCount === 0 && residueCount === 1 && chainCount === 0) {
-        return elementLabel(stats.firstResidueLoc, { hidePrefix, granularity: 'residue' })
+        return elementLabel(stats.firstResidueLoc, { hidePrefix, condensed, granularity: 'residue' })
     } else if (!countsOnly && elementCount === 0 && residueCount === 0 && chainCount === 1) {
         const { unit } = stats.firstChainLoc
         const granularity = (Unit.isAtomic(unit) && getResidueCount(unit) === 1) ? 'residue' : 'chain'
-        return elementLabel(stats.firstChainLoc, { hidePrefix, granularity })
+        return elementLabel(stats.firstChainLoc, { hidePrefix, condensed, granularity })
     } else if (!countsOnly) {
         const label: string[] = []
         if (structureCount > 0) {
-            label.push(structureCount === 1 ? elementLabel(stats.firstStructureLoc, { hidePrefix, granularity: 'structure' }) : otherLabel(structureCount, stats.firstStructureLoc, 'structure', false))
+            label.push(structureCount === 1 ? elementLabel(stats.firstStructureLoc, { hidePrefix, condensed, granularity: 'structure' }) : otherLabel(structureCount, stats.firstStructureLoc, 'structure', false))
         }
         if (chainCount > 0) {
-            label.push(chainCount === 1 ? elementLabel(stats.firstChainLoc, { granularity: 'chain' }) : otherLabel(chainCount, stats.firstChainLoc, 'chain', false))
+            label.push(chainCount === 1 ? elementLabel(stats.firstChainLoc, { condensed, granularity: 'chain' }) : otherLabel(chainCount, stats.firstChainLoc, 'chain', false))
             hidePrefix = true;
         }
         if (residueCount > 0) {
-            label.push(residueCount === 1 ? elementLabel(stats.firstResidueLoc, { granularity: 'residue', hidePrefix }) : otherLabel(residueCount, stats.firstResidueLoc, 'residue', hidePrefix))
+            label.push(residueCount === 1 ? elementLabel(stats.firstResidueLoc, { condensed, granularity: 'residue', hidePrefix }) : otherLabel(residueCount, stats.firstResidueLoc, 'residue', hidePrefix))
             hidePrefix = true;
         }
         if (conformationCount > 0) {
-            label.push(conformationCount === 1 ? elementLabel(stats.firstConformationLoc, { granularity: 'conformation', hidePrefix }) : otherLabel(conformationCount, stats.firstConformationLoc, 'conformation', hidePrefix))
+            label.push(conformationCount === 1 ? elementLabel(stats.firstConformationLoc, { condensed, granularity: 'conformation', hidePrefix }) : otherLabel(conformationCount, stats.firstConformationLoc, 'conformation', hidePrefix))
             hidePrefix = true;
         }
         if (elementCount > 0) {
-            label.push(elementCount === 1 ? elementLabel(stats.firstElementLoc, { granularity: 'element', hidePrefix }) : otherLabel(elementCount, stats.firstElementLoc, 'element', hidePrefix))
+            label.push(elementCount === 1 ? elementLabel(stats.firstElementLoc, { condensed, granularity: 'element', hidePrefix }) : otherLabel(elementCount, stats.firstElementLoc, 'element', hidePrefix))
         }
         return label.join('<small> + </small>')
     } else {
@@ -111,20 +115,21 @@ function _structureElementStatsLabel(stats: StructureElement.Stats, countsOnly =
 }
 
 export function bondLabel(bond: Bond.Location, options: Partial<LabelOptions> = {}): string {
+    return bundleLabel({ loci: [
+        StructureElement.Loci(bond.aStructure, [{ unit: bond.aUnit, indices: OrderedSet.ofSingleton(bond.aIndex) }]),
+        StructureElement.Loci(bond.bStructure, [{ unit: bond.bUnit, indices: OrderedSet.ofSingleton(bond.bIndex) }])
+    ]}, options)
+}
+
+export function bundleLabel(bundle: Loci.Bundle<any>, options: Partial<LabelOptions> = {}): string {
     const o = { ...DefaultLabelOptions, ...options }
-    const locA = StructureElement.Location.create(bond.aStructure, bond.aUnit, bond.aUnit.elements[bond.aIndex])
-    const locB = StructureElement.Location.create(bond.bStructure, bond.bUnit, bond.bUnit.elements[bond.bIndex])
-    const labelA = _elementLabel(locA, o.granularity, o.hidePrefix)
-    const labelB = _elementLabel(locB, o.granularity, o.hidePrefix)
-    let offset = 0
-    for (let i = 0, il = Math.min(labelA.length, labelB.length); i < il; ++i) {
-        if (labelA[i] === labelB[i]) offset += 1
-        else break
-    }
-    return `${labelA.join(' | ')} \u2014 ${labelB.slice(offset).join(' | ')}`
+    const label = _bundleLabel(bundle, o)
+    return o.htmlStyling ? label : stripTags(label)
 }
 
-export function bundleLabel(bundle: Loci.Bundle<any>, options: Partial<LabelOptions> = {}) {
+export function _bundleLabel(bundle: Loci.Bundle<any>, options: LabelOptions) {
+    const { granularity, hidePrefix, reverse, condensed } = options
+
     let isSingleElements = true
     for (const l of bundle.loci) {
         if (!StructureElement.Loci.is(l) || StructureElement.Loci.size(l) !== 1) {
@@ -134,12 +139,15 @@ export function bundleLabel(bundle: Loci.Bundle<any>, options: Partial<LabelOpti
     }
 
     if (isSingleElements) {
-        const o = { ...DefaultLabelOptions, ...options }
         const locations = (bundle.loci as StructureElement.Loci[]).map(l => {
             const { unit, indices } = l.elements[0]
             return StructureElement.Location.create(l.structure, unit, unit.elements[OrderedSet.start(indices)])
         })
-        const labels = locations.map(l => _elementLabel(l, o.granularity, o.hidePrefix))
+        const labels = locations.map(l => _elementLabel(l, granularity, hidePrefix, reverse || condensed))
+
+        if (condensed) {
+            return labels.map(l => l[0].replace(/\[.*\]/g, '').trim()).join(' \u2014 ')
+        }
 
         let offset = 0
         for (let i = 0, il = Math.min(...labels.map(l => l.length)); i < il; ++i) {
@@ -164,21 +172,23 @@ export function bundleLabel(bundle: Loci.Bundle<any>, options: Partial<LabelOpti
             return labels.map(l => l.join(' | ')).join('</br>')
         }
     } else {
-        return bundle.loci.map(l => lociLabel(l)).join('</br>')
+        const labels = bundle.loci.map(l => lociLabel(l, options))
+        return labels.join(condensed ? ' \u2014 ' : '</br>')
     }
 }
 
 export function elementLabel(location: StructureElement.Location, options: Partial<LabelOptions> = {}): string {
     const o = { ...DefaultLabelOptions, ...options }
-    const label = _elementLabel(location, o.granularity, o.hidePrefix).join(' | ')
+    const _label = _elementLabel(location, o.granularity, o.hidePrefix, o.reverse || o.condensed)
+    const label = o.condensed ? _label[0].replace(/\[.*\]/g, '').trim() : _label.join(' | ')
     return o.htmlStyling ? label : stripTags(label)
 }
 
-function _elementLabel(location: StructureElement.Location, granularity: LabelGranularity = 'element', hidePrefix = false): string[] {
+function _elementLabel(location: StructureElement.Location, granularity: LabelGranularity = 'element', hidePrefix = false, reverse = false): string[] {
     const label: string[] = [];
     if (!hidePrefix) {
         let entry = location.unit.model.entry;
-        if (entry.length > 30) entry = entry.substr(0, 27) + '...';
+        if (entry.length > 30) entry = entry.substr(0, 27) + '\u2026'; // ellipsis
         label.push(`<small>${entry}</small>`) // entry
         if (granularity !== 'structure') {
             label.push(`<small>Model ${location.unit.model.modelNum}</small>`) // model
@@ -194,7 +204,7 @@ function _elementLabel(location: StructureElement.Location, granularity: LabelGr
         label.push('Unknown')
     }
 
-    return label
+    return reverse ? label.reverse() : label
 }
 
 function _atomicElementLabel(location: StructureElement.Location<Unit.Atomic>, granularity: LabelGranularity): string[] {
@@ -269,4 +279,35 @@ function _coarseElementLabel(location: StructureElement.Location<Unit.Spheres |
     }
 
     return label.reverse()
+}
+
+//
+
+export function distanceLabel(pair: Loci.Bundle<2>, options: Partial<LabelOptions & { measureOnly: boolean, unitLabel: string }> = {}) {
+    const o = { ...DefaultLabelOptions, measureOnly: false, unitLabel: '\u212B', ...options }
+    const [cA, cB] = pair.loci.map(l => Loci.getCenter(l)!)
+    const distance = `${Vec3.distance(cA, cB).toFixed(2)} ${o.unitLabel}`
+    if (o.measureOnly) return distance
+    const label = bundleLabel(pair, o)
+    return o.condensed ? `${distance} | ${label}` : `Distance ${distance}</br>${label}`
+}
+
+export function angleLabel(triple: Loci.Bundle<3>, options: Partial<LabelOptions & { measureOnly: boolean }> = {}) {
+    const o = { ...DefaultLabelOptions, measureOnly: false, ...options }
+    const [cA, cB, cC] = triple.loci.map(l => Loci.getCenter(l)!)
+    const vAB = Vec3.sub(Vec3(), cA, cB)
+    const vCB = Vec3.sub(Vec3(), cC, cB)
+    const angle = `${radToDeg(Vec3.angle(vAB, vCB)).toFixed(2)}\u00B0`
+    if (o.measureOnly) return angle
+    const label = bundleLabel(triple, o)
+    return o.condensed ? `${angle} | ${label}` : `Angle ${angle}</br>${label}`
+}
+
+export function dihedralLabel(quad: Loci.Bundle<4>, options: Partial<LabelOptions & { measureOnly: boolean }> = {}) {
+    const o = { ...DefaultLabelOptions, measureOnly: false, ...options }
+    const [cA, cB, cC, cD] = quad.loci.map(l => Loci.getCenter(l)!)
+    const dihedral = `${radToDeg(Vec3.dihedralAngle(cA, cB, cC, cD)).toFixed(2)}\u00B0`
+    if (o.measureOnly) return dihedral
+    const label = bundleLabel(quad, o)
+    return o.condensed ? `${dihedral} | ${label}` : `Dihedral ${dihedral}</br>${label}`
 }