NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/component/structure-component.js

/**
 * @file Sturucture Component
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import Signal from '../../lib/signals.es6.js'

import { ComponentRegistry } from '../globals.js'
import { defaults } from '../utils.js'
import Component from './component.js'
import TrajectoryComponent from './trajectory-component.js'
import { makeTrajectory } from '../trajectory/trajectory-utils.js'
import Selection from '../selection/selection.js'
import StructureView from '../structure/structure-view.js'
import { superpose } from '../align/align-utils.js'

/**
 * Extends {@link ComponentSignals}
 *
 * @example
 * component.signals.representationAdded.add( function( representationComponent ){ ... } );
 *
 * @typedef {Object} StructureComponentSignals
 * @property {Signal<RepresentationComponent>} trajectoryAdded - when a trajectory is added
 * @property {Signal<RepresentationComponent>} trajectoryRemoved - when a trajectory is removed
 * @property {Signal<String>} defaultAssemblyChanged - on default assembly change
 */

/**
 * Component wrapping a {@link Structure} object
 *
 * @example
 * // get a structure component by loading a structure file into the stage
 * stage.loadFile( "rcsb://4opj" ).then( function( structureComponent ){
 *     structureComponent.addRepresentation( "cartoon" );
 *     structureComponent.autoView();
 * } );
 */
class StructureComponent extends Component {
  /**
   * Create structure component
   * @param {Stage} stage - stage object the component belongs to
   * @param {Structure} structure - structure object to wrap
   * @param {ComponentParameters} params - component parameters
   */
  constructor (stage, structure, params) {
    var p = params || {}
    p.name = defaults(p.name, structure.name)

    super(stage, p)

    /**
     * Events emitted by the component
     * @type {StructureComponentSignals}
     */
    this.signals = Object.assign(this.signals, {
      trajectoryAdded: new Signal(),
      trajectoryRemoved: new Signal(),
      defaultAssemblyChanged: new Signal()
    })

    /**
     * The wrapped structure
     * @type {Structure}
     */
    this.structure = structure

    this.trajList = []
    this.initSelection(p.sele)
    this.setDefaultAssembly(p.assembly || '')
  }

  /**
   * Component type
   * @type {String}
   */
  get type () { return 'structure' }

  /**
   * Initialize selection
   * @private
   * @param {String} sele - selection string
   * @return {undefined}
   */
  initSelection (sele) {
    /**
     * Selection for {@link StructureComponent#structureView}
     * @private
     * @type {Selection}
     */
    this.selection = new Selection(sele)

    /**
     * View on {@link StructureComponent#structure}.
     * Change its selection via {@link StructureComponent#setSelection}.
     * @type {StructureView}
     */
    this.structureView = new StructureView(
      this.structure, this.selection
    )

    this.selection.signals.stringChanged.add(() => {
      this.structureView.setSelection(this.selection)

      this.rebuildRepresentations()
      this.rebuildTrajectories()
    })
  }

  /**
   * Set selection of {@link StructureComponent#structureView}
   * @param {String} string - selection string
   * @return {StructureComponent} this object
   */
  setSelection (string) {
    this.selection.setString(string)

    return this
  }

  /**
   * Set the default assembly
   * @param {String} value - assembly name
   * @return {undefined}
   */
  setDefaultAssembly (value) {
    this.defaultAssembly = value
    this.reprList.forEach(repr => {
      repr.setParameters({ defaultAssembly: this.defaultAssembly })
    })
    this.signals.defaultAssemblyChanged.dispatch(value)
  }

  /**
   * Rebuild all representations
   * @return {undefined}
   */
  rebuildRepresentations () {
    this.reprList.forEach(repr => {
      repr.build()
    })
  }

  /**
   * Rebuild all trajectories
   * @return {undefined}
   */
  rebuildTrajectories () {
    this.trajList.slice().forEach(trajComp => {
      trajComp.trajectory.setStructure(this.structureView)
    })
  }

  /**
   * Add a new structure representation to the component
   * @param {String} type - the name of the representation, one of:
   *                        axes, backbone, ball+stick, base, cartoon, contact,
   *                        distance, helixorient, hyperball, label, licorice, line
   *                        surface, ribbon, rocket, rope, spacefill, trace, tube,
   *                        unitcell.
   * @param {StructureRepresentationParameters} params - representation parameters
   * @return {RepresentationComponent} the created representation wrapped into
   *                                   a representation component object
   */
  addRepresentation (type, params) {
    var p = params || {}
    p.defaultAssembly = this.defaultAssembly

    return super.addRepresentation(type, this.structureView, p)
  }

  /**
   * Add a new trajectory component to the structure
   * @param {String|Frames} trajPath - path or frames object
   * @param {TrajectoryComponentParameters|TrajectoryParameters} params - parameters
   * @return {TrajectoryComponent} the created trajectory component object
   */
  addTrajectory (trajPath, params) {
    var traj = makeTrajectory(trajPath, this.structureView, params)

    traj.signals.frameChanged.add(() => {
      this.updateRepresentations({ 'position': true })
    })

    var trajComp = new TrajectoryComponent(this.stage, traj, params, this)
    this.trajList.push(trajComp)
    this.signals.trajectoryAdded.dispatch(trajComp)

    return trajComp
  }

  removeTrajectory (traj) {
    var idx = this.trajList.indexOf(traj)
    if (idx !== -1) {
      this.trajList.splice(idx, 1)
    }

    traj.dispose()

    this.signals.trajectoryRemoved.dispatch(traj)
  }

  dispose () {
    // copy via .slice because side effects may change trajList
    this.trajList.slice().forEach(traj => {
      traj.dispose()
    })

    this.trajList.length = 0
    this.structure.dispose()

    super.dispose()
  }

  /**
   * Automatically center and zoom the component
   * @param  {String|Integer} [sele] - selection string or duration if integer
   * @param  {Integer} [duration] - duration of the animation, defaults to 0
   * @return {undefined}
   */
  autoView (sele, duration) {
    if (Number.isInteger(sele)) {
      duration = sele
      sele = undefined
    }

    this.stage.animationControls.zoomMove(
      this.getCenter(sele),
      this.getZoom(sele),
      defaults(duration, 0)
    )
  }

  getBoxUntransformed (sele) {
    var bb

    if (sele) {
      bb = this.structureView.getBoundingBox(new Selection(sele))
    } else {
      bb = this.structureView.boundingBox
    }

    return bb
  }

  getCenterUntransformed (sele) {
    if (sele && typeof sele === 'string') {
      return this.structure.atomCenter(new Selection(sele))
    } else {
      return this.structure.center
    }
  }

  superpose (component, align, sele1, sele2) {
    superpose(
      this.structureView, component.structureView, align, sele1, sele2
    )

    this.updateRepresentations({ 'position': true })

    return this
  }

  setVisibility (value) {
    super.setVisibility(value)

    this.trajList.forEach(traj => {
      // FIXME ???
      traj.setVisibility(value)
    })

    return this
  }
}

ComponentRegistry.add('structure', StructureComponent)
ComponentRegistry.add('structureview', StructureComponent)

export default StructureComponent