Home Manual Reference Source Repository

src/shapes/base-shape.js

import ns from '../core/namespace';


/**
 * Is an abstract class or interface to be overriden in order to define new
 * shapes. Shapes define the way a given datum should be rendered, they are
 * the smallest unit of rendering into a timeline.
 *
 * All the life cycle of `Shape` instances is handled into the `Layer` instance
 * they are attach to. As a consequence, they should be mainly considered as
 * private objects. The only place they should be interacted with is in `Behavior`
 * definitions, to test which element of the shape is the target of the
 * interaction and define the interaction according to that test.
 *
 * Depending of its implementation a `Shape` can be used along with `entity` or
 * `collection` data type. Some shapes are then created to use data considered
 * as a single entity (Waveform, TracePath, Line), while others are defined to
 * be used with data seen as a collection, each shape rendering a single entry
 * of the collection. The shapes working with entity type data should therefore
 * be used in an `entity` configured `Layer`. Note that if they are registered
 * as "commonShape" in a `collection` type `Layer`, they will behave the exact
 * same way. These kind of shapes are noted: "entity shape".
 *
 * ### Available `collection` shapes:
 * - Marker / Annotated Marker
 * - Segment / Annotated Segment
 * - Dot
 * - TraceDots
 *
 * ### Available `entity` shapes:
 * - Line
 * - Tick (for axis)
 * - Waveform
 * - TracePath
 */
export default class BaseShape {
  /**
   * @param {Object} options - override default configuration
   */
  constructor(options = {}) {
    /** @type {Element} - Svg element to be returned by the `render` method. */
    this.$el = null;
    /** @type {String} - Svg namespace. */
    this.ns = ns;
    /** @type {Object} - Object containing the global parameters of the shape */
    this.params = Object.assign({}, this._getDefaults(), options);
    // create accessors methods and set default accessor functions
    const accessors = this._getAccessorList();
    this._createAccessors(accessors);
    this._setDefaultAccessors(accessors);
  }

  /**
   * Destroy the shape and clean references. Interface method called from the `layer`.
   */
  destroy() {
    // this.group = null;
    this.$el = null;
  }

  /**
   * Interface method to override when extending this base class. The method
   * is called by the `Layer~render` method. Returns the name of the shape,
   * used as a class in the element group (defaults to `'shape'`).
   *
   * @return {String}
   */
  getClassName() { return 'shape'; }

  /**
   * @todo not implemented
   * allow to install defs in the track svg element. Should be called when
   * adding the `Layer` to the `Track`.
   */
  // setSvgDefinition(defs) {}

  /**
   * Returns the defaults for global configuration of the shape.
   * @protected
   * @return {Object}
   */
  _getDefaults() {
    return {};
  }

  /**
   * Returns an object where keys are the accessors methods names to create
   * and values are the default values for each given accessor.
   *
   * @protected
   * @todo rename ?
   * @return {Object}
   */
  _getAccessorList() { return {}; }


  /**
   * Interface method called by Layer when creating a shape. Install the
   * given accessors on the shape, overriding the default accessors.
   *
   * @param {Object<String, function>} accessors
   */
  install(accessors) {
    for (let key in accessors) { this[key] = accessors[key]; }
  }

  /**
   * Generic method to create accessors. Adds getters en setters to the
   * prototype if not already present.
   */
  _createAccessors(accessors) {
    this._accessors = {};
    // add it to the prototype
    const proto = Object.getPrototypeOf(this);
    // create a getter / setter for each accessors
    // setter : `this.x = callback`
    // getter : `this.x(datum)`
    Object.keys(accessors).forEach((name) => {
      if (proto.hasOwnProperty(name)) { return; }

      Object.defineProperty(proto, name, {
        get: function() { return this._accessors[name]; },
        set: function(func) {
          this._accessors[name] = func;
        }
      });
    });
  }

  /**
   * Create a function to be used as a default accessor for each accesors
   */
  _setDefaultAccessors(accessors) {
    Object.keys(accessors).forEach((name) => {
      const defaultValue = accessors[name];
      let accessor = function(d, v = null) {
        if (v === null) { return d[name] || defaultValue; }
        d[name] = v;
      };
      // set accessor as the default one
      this[name] = accessor;
    });
  }

  /**
   * Interface method called by `Layer~render`. Creates the DOM structure of
   * the shape.
   *
   * @param {Object} renderingContext - the renderingContext of the layer
   *    which owns this shape.
   * @return {Element} - the DOM element to insert in the item's group.
   */
  render(renderingContext) {}

  /**
   * Interface method called by `Layer~update`. Updates the DOM structure of the shape.
   *
   * @param {Object} renderingContext - The `renderingContext` of the layer
   *    which owns this shape.
   * @param {Object|Array} datum - The datum associated to the shape.
   */
  update(renderingContext, datum) {}

  /**
   * Interface method to override called by `Layer~getItemsInArea`. Defines if
   * the shape is considered to be the given area. Arguments are passed in pixel domain.
   *
   * @param {Object} renderingContext - the renderingContext of the layer which
   *    owns this shape.
   * @param {Object|Array} datum - The datum associated to the shape.
   * @param {Number} x1 - The x component of the top-left corner of the area to test.
   * @param {Number} y1 - The y component of the top-left corner of the area to test.
   * @param {Number} x2 - The x component of the bottom-right corner of the area to test.
   * @param {Number} y2 - The y component of the bottom-right corner of the area to test.
   * @return {Boolean} - Returns `true` if the is considered to be in the given area, `false` otherwise.
   */
  inArea(renderingContext, datum, x1, y1, x2, y2) {}
}