konvajs/konva

View on GitHub
src/shapes/Arrow.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { Factory } from '../Factory';
import { Line, LineConfig } from './Line';
import { GetSet } from '../types';
import { getNumberValidator } from '../Validators';
import { _registerNode } from '../Global';
import { Path } from './Path';
import { Context } from '../Context';

export interface ArrowConfig extends LineConfig {
  points: number[];
  tension?: number;
  closed?: boolean;
  pointerLength?: number;
  pointerWidth?: number;
  pointerAtBeginning?: boolean;
  pointerAtEnding?: boolean;
}

/**
 * Arrow constructor
 * @constructor
 * @memberof Konva
 * @augments Konva.Line
 * @param {Object} config
 * @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3].
 * @param {Number} [config.tension] Higher values will result in a more curvy line.  A value of 0 will result in no interpolation.
 *   The default is 0
 * @param {Number} config.pointerLength Arrow pointer length. Default value is 10.
 * @param {Number} config.pointerWidth Arrow pointer width. Default value is 10.
 * @param {Boolean} config.pointerAtBeginning Do we need to draw pointer on beginning position?. Default false.
 * @param {Boolean} config.pointerAtEnding Do we need to draw pointer on ending position?. Default true.
 * @@shapeParams
 * @@nodeParams
 * @example
 * var line = new Konva.Line({
 *   points: [73, 70, 340, 23, 450, 60, 500, 20],
 *   stroke: 'red',
 *   tension: 1,
 *   pointerLength : 10,
 *   pointerWidth : 12
 * });
 */
export class Arrow extends Line<ArrowConfig> {
  _sceneFunc(ctx: Context) {
    super._sceneFunc(ctx);
    var PI2 = Math.PI * 2;
    var points = this.points();

    var tp = points;
    var fromTension = this.tension() !== 0 && points.length > 4;
    if (fromTension) {
      tp = this.getTensionPoints();
    }
    var length = this.pointerLength();

    var n = points.length;

    var dx, dy;
    if (fromTension) {
      const lp = [
        tp[tp.length - 4],
        tp[tp.length - 3],
        tp[tp.length - 2],
        tp[tp.length - 1],
        points[n - 2],
        points[n - 1],
      ];
      const lastLength = Path.calcLength(
        tp[tp.length - 4],
        tp[tp.length - 3],
        'C',
        lp
      );
      const previous = Path.getPointOnQuadraticBezier(
        Math.min(1, 1 - length / lastLength),
        lp[0],
        lp[1],
        lp[2],
        lp[3],
        lp[4],
        lp[5]
      );

      dx = points[n - 2] - previous.x;
      dy = points[n - 1] - previous.y;
    } else {
      dx = points[n - 2] - points[n - 4];
      dy = points[n - 1] - points[n - 3];
    }

    var radians = (Math.atan2(dy, dx) + PI2) % PI2;

    var width = this.pointerWidth();

    if (this.pointerAtEnding()) {
      ctx.save();
      ctx.beginPath();
      ctx.translate(points[n - 2], points[n - 1]);
      ctx.rotate(radians);
      ctx.moveTo(0, 0);
      ctx.lineTo(-length, width / 2);
      ctx.lineTo(-length, -width / 2);
      ctx.closePath();
      ctx.restore();
      this.__fillStroke(ctx);
    }

    if (this.pointerAtBeginning()) {
      ctx.save();
      ctx.beginPath();
      ctx.translate(points[0], points[1]);
      if (fromTension) {
        dx = (tp[0] + tp[2]) / 2 - points[0];
        dy = (tp[1] + tp[3]) / 2 - points[1];
      } else {
        dx = points[2] - points[0];
        dy = points[3] - points[1];
      }

      ctx.rotate((Math.atan2(-dy, -dx) + PI2) % PI2);
      ctx.moveTo(0, 0);
      ctx.lineTo(-length, width / 2);
      ctx.lineTo(-length, -width / 2);
      ctx.closePath();
      ctx.restore();
      this.__fillStroke(ctx);
    }
  }

  __fillStroke(ctx: Context) {
    // here is a tricky part
    // we need to disable dash for arrow pointers
    var isDashEnabled = this.dashEnabled();
    if (isDashEnabled) {
      // manually disable dash for head
      // it is better not to use setter here,
      // because it will trigger attr change event
      this.attrs.dashEnabled = false;
      ctx.setLineDash([]);
    }

    ctx.fillStrokeShape(this);

    // restore old value
    if (isDashEnabled) {
      this.attrs.dashEnabled = true;
    }
  }

  getSelfRect() {
    const lineRect = super.getSelfRect();
    const offset = this.pointerWidth() / 2;
    return {
      x: lineRect.x - offset,
      y: lineRect.y - offset,
      width: lineRect.width + offset * 2,
      height: lineRect.height + offset * 2,
    };
  }

  pointerLength: GetSet<number, this>;
  pointerWidth: GetSet<number, this>;
  pointerAtEnding: GetSet<boolean, this>;
  pointerAtBeginning: GetSet<boolean, this>;
}

Arrow.prototype.className = 'Arrow';
_registerNode(Arrow);

/**
 * get/set pointerLength
 * @name Konva.Arrow#pointerLength
 * @method
 * @param {Number} Length of pointer of arrow. The default is 10.
 * @returns {Number}
 * @example
 * // get length
 * var pointerLength = line.pointerLength();
 *
 * // set length
 * line.pointerLength(15);
 */

Factory.addGetterSetter(Arrow, 'pointerLength', 10, getNumberValidator());
/**
 * get/set pointerWidth
 * @name Konva.Arrow#pointerWidth
 * @method
 * @param {Number} Width of pointer of arrow.
 *   The default is 10.
 * @returns {Number}
 * @example
 * // get width
 * var pointerWidth = line.pointerWidth();
 *
 * // set width
 * line.pointerWidth(15);
 */

Factory.addGetterSetter(Arrow, 'pointerWidth', 10, getNumberValidator());
/**
 * get/set pointerAtBeginning
 * @name Konva.Arrow#pointerAtBeginning
 * @method
 * @param {Number} Should pointer displayed at beginning of arrow. The default is false.
 * @returns {Boolean}
 * @example
 * // get value
 * var pointerAtBeginning = line.pointerAtBeginning();
 *
 * // set value
 * line.pointerAtBeginning(true);
 */

Factory.addGetterSetter(Arrow, 'pointerAtBeginning', false);
/**
 * get/set pointerAtEnding
 * @name Konva.Arrow#pointerAtEnding
 * @method
 * @param {Number} Should pointer displayed at ending of arrow. The default is true.
 * @returns {Boolean}
 * @example
 * // get value
 * var pointerAtEnding = line.pointerAtEnding();
 *
 * // set value
 * line.pointerAtEnding(false);
 */

Factory.addGetterSetter(Arrow, 'pointerAtEnding', true);