Home Reference Source Test Repository

src/js/data/label.js

/**
 * Data representation of text objects. Not called Text due to JavaScript already having a Text class.
 * @class Label
 */
class Label {

  // graph data
  x;
  y;
  parentObject;
  content = '';

  // state
  isSelected = false;
  textMetric;

  // appearance
  color = '#000000';
  selectedColor = '#ff0000';
  textAlign = 'start';

  // context.font properties
  // https://developer.mozilla.org/en-US/docs/Web/CSS/font
  // font = size | family
  //      = style | size | family
  //      = style | variant | weight | size/line-height | family
  //      = style | variant | weight | stretch | size/line-height | family
  fontStyle = 'normal'; // normal | italic | oblique
  fontVariant = 'normal'; // normal | small-caps
  fontWeight = 'normal'; // normal | bold | lighter | bolder | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
  fontStretch = 'normal'; // normal | semi-condensed | condensed | extra-condensed | ultra-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded
  fontSize = '14px'; // xx-small | x-small | small | medium | large | x-large | xx-large | larger | smaller | <length> | <percentage>
  lineHeight = 'normal'; // normal | <number> | <length> | <percentage>
  fontFamily = 'Open sans'; // <family-name> | <generic-name>
  // <length> units
  // https://developer.mozilla.org/en-US/docs/Web/CSS/length

  /**
   * Constructs a Label instance.
   * @param  {number} x - x-coordinate of the label.
   * @param  {number} y - y-coordinate of the label.
   * @param  {(Node|Edge)} parentObject - Node or Edge associated with the Label.
   * @constructs Label
   */
  constructor(x, y, parentObject) {
    this.x = x;
    this.y = y;
    this.parentObject = parentObject;
  }

  /**
   * Check if the label contains the given point.
   * @param  {number} x - x-coordinate of the point.
   * @param  {number} y - y-coordinate of the point.
   * @return {boolean} - Whether or not the label contains the point.
   */
  containsPoint(x, y) {
    if (this.content === '') {
      return false;
    }
    // textMetric.actualBoundingBox* not yet supported in stable Chrome
    let padding = 5;
    let left = this.x - padding;
    let right = this.x + this.textMetric.width + padding;

    let top = this.y - parseInt(this.fontSize, 10) - padding;
    let bottom = this.y + padding;

    let containsPoint = (left <= x && x <= right) && (top <= y && y <= bottom);
    return containsPoint;
  }

  /**
   * Returns the font properties concatenated to match the CanvasRenderingContext2D.font property.
   * @return {string} - String that can be used to set the canvas font property.
   */
  get font() {
    return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${this.fontStretch} ${this.fontSize}/${this.lineHeight} "${this.fontFamily}", sans-serif`;
  }

  /**
   * Update the label's position.
   * @param {number} x - The new x-coordinate.
   * @param {number} y - The new y-coordinate.
   */
  setPos(x, y) {
    this.x = x;
    this.y = y;
  }

  /**
   * Draw the label on the given canvas context.
   * @param  {CanvasRenderingContext2D} context - Canvas 2D context.
   */
  draw(context) {
    if (this.content === '') {
      return;
    }

    this.textMetric = context.measureText(this.content);

    // set font style
    context.font = this.font;
    context.textAlign = this.textAlign;
    context.fillStyle = this.color;

    context.fillText(this.content, this.x, this.y);

    if (this.isSelected) {
      this.drawBox(context);
    }
  }

  /**
   * Draw a box around the label's selection area on the given canvas context.
   * @param  {CanvasRenderingContext2D} context - Canvas 2D context.
   */
  drawBox(context) {
    // textMetric.actualBoundingBox* not yet supported in stable Chrome
    let padding = 5;
    let left = this.x - padding;
    let right = this.x + this.textMetric.width + padding;

    let top = this.y - parseInt(this.fontSize, 10) - padding;
    let bottom = this.y + padding;

    context.strokeStyle = this.selectedColor;

    context.beginPath();
    context.moveTo(left, top);
    context.lineTo(left, bottom);
    context.lineTo(right, bottom);
    context.lineTo(right, top);
    context.lineTo(left, top);
    context.stroke();
  }
}

export { Label };
export default Label;