konva
Version:
HTML5 2d canvas library for interactive graphics, design editors, whiteboards, and diagrams.
205 lines (204 loc) • 7.36 kB
JavaScript
import { Factory } from "../Factory.js";
import { Line } from "./Line.js";
import { getNumberValidator } from "../Validators.js";
import { _registerNode } from "../Global.js";
import { Path } from "./Path.js";
/**
* 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 {
_sceneFunc(ctx) {
super._sceneFunc(ctx);
const points = this.points();
const n = points.length;
if (n < 4)
return;
const length = this.pointerLength();
const width = this.pointerWidth();
if (this.pointerAtEnding()) {
ctx.save();
ctx.beginPath();
ctx.translate(points[n - 2], points[n - 1]);
ctx.rotate(this._getPointerAngle(false));
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]);
ctx.rotate(this._getPointerAngle(true));
ctx.moveTo(0, 0);
ctx.lineTo(-length, width / 2);
ctx.lineTo(-length, -width / 2);
ctx.closePath();
ctx.restore();
this.__fillStroke(ctx);
}
}
_getPointerAngle(atBeginning) {
const points = this.points();
const n = points.length;
const fromTension = this.tension() !== 0 && n > 4;
const tp = fromTension ? this.getTensionPoints() : points;
const ex = atBeginning ? points[0] : points[n - 2], ey = atBeginning ? points[1] : points[n - 1];
let dx = 0, dy = 0;
if (fromTension && atBeginning) {
dx = ex - (tp[0] + tp[2]) / 2;
dy = ey - (tp[1] + tp[3]) / 2;
}
else if (fromTension) {
const x = tp[tp.length - 4], y = tp[tp.length - 3];
const controlX = tp[tp.length - 2], controlY = tp[tp.length - 1];
const lastLength = Path.calcLength(x, y, 'Q', [
controlX,
controlY,
ex,
ey,
]);
const previous = Path.getPointOnQuadraticBezier(lastLength ? Math.max(0, 1 - this.pointerLength() / lastLength) : 0, x, y, controlX, controlY, ex, ey);
dx = ex - previous.x;
dy = ey - previous.y;
}
// repeated endpoints give a zero tangent, so walk towards the other end
// of the line until a distinct point is found
for (let i = atBeginning ? 0 : tp.length - 2; !dx && !dy && i >= 0 && i < tp.length; i += atBeginning ? 2 : -2) {
dx = ex - tp[i];
dy = ey - tp[i + 1];
}
const turn = Math.PI * 2;
return (Math.atan2(dy, dx) + turn) % turn;
}
__fillStroke(ctx) {
// here is a tricky part
// we need to disable dash for arrow pointers
const 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 points = this.points();
if (points.length < 4)
return lineRect;
let minX = lineRect.x, minY = lineRect.y;
let maxX = minX + lineRect.width, maxY = minY + lineRect.height;
for (const beginning of [false, true]) {
if (!(beginning ? this.pointerAtBeginning() : this.pointerAtEnding()))
continue;
const index = beginning ? 0 : points.length - 2;
const angle = this._getPointerAngle(beginning);
const cos = Math.cos(angle), sin = Math.sin(angle);
const x = points[index] - this.pointerLength() * cos;
const y = points[index + 1] - this.pointerLength() * sin;
const dx = Math.abs((this.pointerWidth() * sin) / 2);
const dy = Math.abs((this.pointerWidth() * cos) / 2);
minX = Math.min(minX, x - dx);
minY = Math.min(minY, y - dy);
maxX = Math.max(maxX, x + dx);
maxY = Math.max(maxY, y + dy);
}
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
}
Arrow.prototype.className = 'Arrow';
Arrow.prototype._attrsAffectingSize = [
...Line.prototype._attrsAffectingSize,
'pointerLength',
'pointerWidth',
'pointerAtBeginning',
'pointerAtEnding',
];
_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 {Boolean} pointerAtBeginning Whether to draw a pointer at the beginning of the 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 {Boolean} pointerAtEnding Whether to draw a pointer at the end of the arrow. The default is true.
* @returns {Boolean}
* @example
* // get value
* var pointerAtEnding = line.pointerAtEnding();
*
* // set value
* line.pointerAtEnding(false);
*/
Factory.addGetterSetter(Arrow, 'pointerAtEnding', true);