Spaces:
Running
Running
| import { fabric } from 'fabric'; | |
| const ARROW_ANGLE = 30; | |
| const CHEVRON_SIZE_RATIO = 2.7; | |
| const TRIANGLE_SIZE_RATIO = 1.7; | |
| const RADIAN_CONVERSION_VALUE = 180; | |
| const ArrowLine = fabric.util.createClass( | |
| fabric.Line, | |
| /** @lends Convolute.prototype */ { | |
| /** | |
| * Line type | |
| * @param {String} type | |
| * @default | |
| */ | |
| type: 'line', | |
| /** | |
| * Constructor | |
| * @param {Array} [points] Array of points | |
| * @param {Object} [options] Options object | |
| * @override | |
| */ | |
| initialize(points, options = {}) { | |
| this.callSuper('initialize', points, options); | |
| this.arrowType = options.arrowType; | |
| }, | |
| /** | |
| * Render ArrowLine | |
| * @private | |
| * @override | |
| */ | |
| _render(ctx) { | |
| const { x1: fromX, y1: fromY, x2: toX, y2: toY } = this.calcLinePoints(); | |
| const linePosition = { | |
| fromX, | |
| fromY, | |
| toX, | |
| toY, | |
| }; | |
| this.ctx = ctx; | |
| ctx.lineWidth = this.strokeWidth; | |
| this._renderBasicLinePath(linePosition); | |
| this._drawDecoratorPath(linePosition); | |
| this._renderStroke(ctx); | |
| }, | |
| /** | |
| * Render Basic line path | |
| * @param {Object} linePosition - line position | |
| * @param {number} option.fromX - line start position x | |
| * @param {number} option.fromY - line start position y | |
| * @param {number} option.toX - line end position x | |
| * @param {number} option.toY - line end position y | |
| * @private | |
| */ | |
| _renderBasicLinePath({ fromX, fromY, toX, toY }) { | |
| this.ctx.beginPath(); | |
| this.ctx.moveTo(fromX, fromY); | |
| this.ctx.lineTo(toX, toY); | |
| }, | |
| /** | |
| * Render Arrow Head | |
| * @param {Object} linePosition - line position | |
| * @param {number} option.fromX - line start position x | |
| * @param {number} option.fromY - line start position y | |
| * @param {number} option.toX - line end position x | |
| * @param {number} option.toY - line end position y | |
| * @private | |
| */ | |
| _drawDecoratorPath(linePosition) { | |
| this._drawDecoratorPathType('head', linePosition); | |
| this._drawDecoratorPathType('tail', linePosition); | |
| }, | |
| /** | |
| * Render Arrow Head | |
| * @param {string} type - 'head' or 'tail' | |
| * @param {Object} linePosition - line position | |
| * @param {number} option.fromX - line start position x | |
| * @param {number} option.fromY - line start position y | |
| * @param {number} option.toX - line end position x | |
| * @param {number} option.toY - line end position y | |
| * @private | |
| */ | |
| _drawDecoratorPathType(type, linePosition) { | |
| switch (this.arrowType[type]) { | |
| case 'triangle': | |
| this._drawTrianglePath(type, linePosition); | |
| break; | |
| case 'chevron': | |
| this._drawChevronPath(type, linePosition); | |
| break; | |
| default: | |
| break; | |
| } | |
| }, | |
| /** | |
| * Render Triangle Head | |
| * @param {string} type - 'head' or 'tail' | |
| * @param {Object} linePosition - line position | |
| * @param {number} option.fromX - line start position x | |
| * @param {number} option.fromY - line start position y | |
| * @param {number} option.toX - line end position x | |
| * @param {number} option.toY - line end position y | |
| * @private | |
| */ | |
| _drawTrianglePath(type, linePosition) { | |
| const decorateSize = this.ctx.lineWidth * TRIANGLE_SIZE_RATIO; | |
| this._drawChevronPath(type, linePosition, decorateSize); | |
| this.ctx.closePath(); | |
| }, | |
| /** | |
| * Render Chevron Head | |
| * @param {string} type - 'head' or 'tail' | |
| * @param {Object} linePosition - line position | |
| * @param {number} option.fromX - line start position x | |
| * @param {number} option.fromY - line start position y | |
| * @param {number} option.toX - line end position x | |
| * @param {number} option.toY - line end position y | |
| * @param {number} decorateSize - decorate size | |
| * @private | |
| */ | |
| _drawChevronPath(type, { fromX, fromY, toX, toY }, decorateSize) { | |
| const { ctx } = this; | |
| if (!decorateSize) { | |
| decorateSize = this.ctx.lineWidth * CHEVRON_SIZE_RATIO; | |
| } | |
| const [standardX, standardY] = type === 'head' ? [fromX, fromY] : [toX, toY]; | |
| const [compareX, compareY] = type === 'head' ? [toX, toY] : [fromX, fromY]; | |
| const angle = | |
| (Math.atan2(compareY - standardY, compareX - standardX) * RADIAN_CONVERSION_VALUE) / | |
| Math.PI; | |
| const rotatedPosition = (changeAngle) => | |
| this.getRotatePosition(decorateSize, changeAngle, { | |
| x: standardX, | |
| y: standardY, | |
| }); | |
| ctx.moveTo(...rotatedPosition(angle + ARROW_ANGLE)); | |
| ctx.lineTo(standardX, standardY); | |
| ctx.lineTo(...rotatedPosition(angle - ARROW_ANGLE)); | |
| }, | |
| /** | |
| * return position from change angle. | |
| * @param {number} distance - change distance | |
| * @param {number} angle - change angle | |
| * @param {Object} referencePosition - reference position | |
| * @returns {Array} | |
| * @private | |
| */ | |
| getRotatePosition(distance, angle, referencePosition) { | |
| const radian = (angle * Math.PI) / RADIAN_CONVERSION_VALUE; | |
| const { x, y } = referencePosition; | |
| return [distance * Math.cos(radian) + x, distance * Math.sin(radian) + y]; | |
| }, | |
| } | |
| ); | |
| export default ArrowLine; | |