UNPKG

butterfly-dag

Version:

一个基于数据驱动的节点式编排组件库,让你有方便快捷定制可视化流程图表

467 lines (446 loc) 16.1 kB
'use strict'; const _ = require('lodash'); const $ = require('jquery'); import ArrowUtil from '../utils/arrow'; import * as DrawUtil from '../utils/link'; import LinkAnimateUtil from '../utils/link/link_animate' import './baseEdge.less'; import Edge from '../interface/edge'; class BaseEdge extends Edge { constructor(opts) { super(opts); this.id = _.get(opts, 'id'); this.targetNode = _.get(opts, 'targetNode'); this._targetType = _.get(opts, '_targetType'); this.targetEndpoint = _.get(opts, 'targetEndpoint'); this.sourceNode = _.get(opts, 'sourceNode'); this._sourceType = _.get(opts, '_sourceType'); this.sourceEndpoint = _.get(opts, 'sourceEndpoint'); this.type = _.get(opts, 'type', 'endpoint'); this.orientationLimit = _.get(opts, 'orientationLimit'); this.shapeType = _.get(opts, 'shapeType', 'Straight'); this.label = _.get(opts, 'label'); this.arrow = _.get(opts, 'arrow'); this.arrowShapeType = _.get(opts, 'arrowShapeType', 'default'); this.arrowPosition = _.get(opts, 'arrowPosition', 0.5); this.arrowOffset = _.get(opts, 'arrowOffset', 0), this.labelPosition = _.get(opts, 'labelPosition', 0.5); this.labelOffset = _.get(opts, 'labelOffset', 0), this.isExpandWidth = _.get(opts, 'isExpandWidth', false); this.defaultAnimate = _.get(opts, 'defaultAnimate', false); this.dom = null; this.labelDom = null; this.arrowDom = null; this.eventHandlerDom = null; this._hasEventListener = false; this._coordinateService = null; // 鸭子辨识手动判断类型 this.__type = 'edge'; this._path = null; // 业务和库内addEdges写法上有区别,需要兼容 this.options = _.get(opts, 'options') || opts; this._isDeletingEdge = opts._isDeletingEdge; this._global = opts._global; this._on = opts._on; this._emit = opts._emit; // 性能优化 this._labelWidth = 0; this._labelHeight = 0; // 函数节流 this._updateTimer = null; this._UPDATE_INTERVAL = 20; // 线段起始位置 this._sourcePoint = null; this._targetPoint = null; // 线段的z-index this._zIndex = 0; // 曼哈顿线可拖动变量 this.draggable = _.get(opts, 'draggable', false);; this._breakPoints = []; if (this.options.breakPoints && this.options.breakPoints.length > 0) { this._breakPoints = this.options.breakPoints; this._breakPoints[0].type === 'start'; this._breakPoints[this._breakPoints.length - 1].type === 'end'; delete this.options.breakPoints; } this._hasDragged = false; } _init(obj) { if (this._isInited) { return; } if (obj._coordinateService) { this._coordinateService = obj._coordinateService; } this._isInited = true; this.dom = this.draw({ id: this.id, dom: this.dom, options: this.options }); this.labelDom = this.drawLabel(this.label); this.arrowDom = this.drawArrow(this.arrow); if (!this._hasEventListener) { this._addEventListener(); this._hasEventListener = true; } } draw(obj) { let path = document.createElementNS('http://www.w3.org/2000/svg', 'path') path.setAttribute('class', 'butterflies-link'); if (this.isExpandWidth) { // 扩大线选中范围 this.eventHandlerDom = document.createElementNS('http://www.w3.org/2000/svg', 'path'); this.eventHandlerDom.setAttribute('class', 'butterflies-link-event-handler'); } return path; } mounted() { if(this.defaultAnimate) { this.addAnimate(); } } _calcPath(sourcePoint, targetPoint) { if (!sourcePoint) { sourcePoint = { pos: [ // this.type === 'endpoint' ? this.sourceEndpoint._posLeft + this.sourceEndpoint._width / 2 : this.sourceNode.left + this.sourceNode.dom.offsetWidth / 2, // this.type === 'endpoint' ? this.sourceEndpoint._posTop + this.sourceEndpoint._height / 2 : this.sourceNode.top + this.sourceNode.dom.offsetHeight / 2 this.type === 'endpoint' ? this.sourceEndpoint._posLeft + this.sourceEndpoint._width / 2 : this.sourceNode.left + this.sourceNode.getWidth(true) / 2, this.type === 'endpoint' ? this.sourceEndpoint._posTop + this.sourceEndpoint._height / 2 : this.sourceNode.top + this.sourceNode.getHeight(true) / 2 ], orientation: (this.type === 'endpoint' && this.sourceEndpoint.orientation) ? this.sourceEndpoint.orientation : undefined }; } if (!targetPoint) { targetPoint = { pos: [ // this.type === 'endpoint' ? this.targetEndpoint._posLeft + this.targetEndpoint._width / 2 : this.targetNode.left + this.targetNode.dom.offsetWidth / 2, // this.type === 'endpoint' ? this.targetEndpoint._posTop + this.targetEndpoint._height / 2 : this.targetNode.top + this.targetNode.dom.offsetHeight / 2 this.type === 'endpoint' ? this.targetEndpoint._posLeft + this.targetEndpoint._width / 2 : this.targetNode.left + this.targetNode.getWidth(true) / 2, this.type === 'endpoint' ? this.targetEndpoint._posTop + this.targetEndpoint._height / 2 : this.targetNode.top + this.targetNode.getHeight(true) / 2 ], orientation: (this.type === 'endpoint' && this.targetEndpoint.orientation) ? this.targetEndpoint.orientation : undefined }; } this._sourcePoint = sourcePoint; this._targetPoint = targetPoint; let path = ''; if (this.calcPath) { path = this.calcPath(sourcePoint, targetPoint); } else if (this.shapeType === 'Bezier') { path = DrawUtil.drawBezier(sourcePoint, targetPoint); } else if (this.shapeType === 'Straight') { path = DrawUtil.drawStraight(sourcePoint, targetPoint); } else if (this.shapeType === 'Flow') { path = DrawUtil.drawFlow(sourcePoint, targetPoint, this.orientationLimit); } else if (this.shapeType === 'Manhattan') { let obj = DrawUtil.drawManhattan(sourcePoint, targetPoint, { breakPoints: this._breakPoints, hasDragged: this._hasDragged, draggable: this.draggable }); path = obj.path; obj.breakPoints[0].type = 'start'; obj.breakPoints[obj.breakPoints.length - 1].type = 'end'; this._breakPoints = obj.breakPoints; } else if (this.shapeType === 'AdvancedBezier') { path = DrawUtil.drawAdvancedBezier(sourcePoint, targetPoint); } else if (/^Bezier2-[1-3]$/.test(this.shapeType)) { path = DrawUtil.drawSecondBezier(sourcePoint, targetPoint, this.shapeType); } else if(this.shapeType === 'BrokenLine'){ path = DrawUtil.drawBrokenLine(sourcePoint, targetPoint); } this._path = path; return path; } redrawLabel() { const length = this.dom.getTotalLength(); if(!length) { return; } let labelLenth = length * this.labelPosition + this.labelOffset; let point = this.dom.getPointAtLength(labelLenth); $(this.labelDom) .css('left', point.x - this.labelDom.offsetWidth / 2) .css('top', point.y - this.labelDom.offsetHeight / 2); } drawLabel(label) { let isDom = typeof HTMLElement === 'object' ? (obj) => { return obj instanceof HTMLElement; } : (obj) => { return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'; }; if (label) { if (isDom(label)) { $(label).addClass('butterflies-label'); return label; } else { let dom = document.createElement('span'); dom.className = 'butterflies-label'; dom.innerText = label; return dom; } } } updateLabel(label) { let labelDom = this.drawLabel(label); if (this.labelDom) { $(this.labelDom).off(); $(this.labelDom).remove(); } this.label = label; this.labelDom = labelDom; // 防止异步渲染,计算不出label长宽 setTimeout(() => { this.redrawLabel(); }); this.emit('InnerEvents', { type: 'edge:updateLabel', data: this }); } redrawArrow(path) { const length = this.dom.getTotalLength(); if(!length) { return; } this.arrowFinalPosition = (length * this.arrowPosition + this.arrowOffset) / length; if (this.arrowFinalPosition > 1) { this.arrowFinalPosition = 1; } if (this.arrowFinalPosition < 0) { this.arrowFinalPosition = 0; } // 防止箭头窜出线条 if (1 - this.arrowFinalPosition < ArrowUtil.ARROW_TYPE.length / length) { this.arrowFinalPosition = (length * this.arrowFinalPosition - ArrowUtil.ARROW_TYPE.length) / length; } // 贝塞尔曲线是反着画的,需要调整 if (this.shapeType === 'Bezier') { this.arrowFinalPosition = 1 - this.arrowFinalPosition; } let point = this.dom.getPointAtLength(length * this.arrowFinalPosition); let x = point.x; let y = point.y; let _x = x; let _y = y; let vector = ArrowUtil.calcSlope({ shapeType: this.shapeType, dom: this.dom, arrowPosition: this.arrowFinalPosition, path: path }); let deg = Math.atan2(vector.y, vector.x) / Math.PI * 180; let arrowObj = ArrowUtil.ARROW_TYPE[this.arrowShapeType]; let arrowWidth = arrowObj.width || 8; let arrowHeight = arrowObj.height || 8; if (arrowObj.type === 'pathString') { this.arrowDom.setAttribute('d', arrowObj.content); } else if (arrowObj.type === 'svg') { if (vector.x === 0) { _y -= arrowHeight / 2; } else { _x -= arrowWidth / 2; _y -= arrowHeight / 2; } } this.arrowDom.setAttribute('transform', `rotate(${deg}, ${x}, ${y})translate(${_x}, ${_y})`); } drawArrow(arrow) { if (arrow) { let arrowObj = ArrowUtil.ARROW_TYPE[this.arrowShapeType]; let arrowWidth = arrowObj.width || 8; let arrowHeight = arrowObj.height || 8; let dom = undefined; if (arrowObj.type === 'pathString') { dom = document.createElementNS('http://www.w3.org/2000/svg', 'path'); } else if (arrowObj.type === 'svg') { dom = document.createElementNS('http://www.w3.org/2000/svg', 'image'); dom.setAttribute('href', arrowObj.content); dom.setAttribute('width', `${arrowWidth}px`); dom.setAttribute('height', `${arrowHeight}px`); } dom.setAttribute('class', 'butterflies-arrow'); return dom; } } redraw(sourcePoint, targetPoint, options) { // 检查线段是否有变化 let _oldPath = this._path; let _newPath = this._calcPath(sourcePoint, targetPoint); if (_oldPath === _newPath) { return ; } this.dom.setAttribute('d', _newPath); if (this.isExpandWidth) { this.eventHandlerDom.setAttribute('d', _newPath); $(this.eventHandlerDom).insertAfter(this.dom); } // 函数节流 if (!this._updateTimer) { this._updateTimer = setTimeout(() => { // 重新计算label if (this.labelDom) { this.redrawLabel(); } // 重新计算arrow if (this.arrowDom) { this.redrawArrow(_newPath); } // 重新计算动画path if (this.animateDom) { this.redrawAnimate(_newPath); } this._updateTimer = null; }, this._UPDATE_INTERVAL); } this.updated && this.updated(); } isConnect() { return true; } addAnimate(options) { this.animateDom = LinkAnimateUtil.addAnimate(this.dom, this._path, _.assign({},{ num: 1, // 现在只支持1个点点 radius: 3, color: '#776ef3' }, options), this.animateDom); } redrawAnimate(path) { LinkAnimateUtil.addAnimate(this.dom, this._path, { _isContinue: true }, this.animateDom); } emit(type, data) { super.emit(type, data); this._emit(type, data); } on(type, callback) { super.on(type, callback); this._on(type, callback); } remove() { this.emit('InnerEvents', { type: 'edge:delete', data: this }); } setZIndex(index) { this.emit('InnerEvents', { type: 'edge:setZIndex', edge: this, index: index }) } destroy(isNotEventEmit) { if (this.labelDom) { $(this.labelDom).off(); $(this.labelDom).remove(); } if (this.arrowDom) { $(this.arrowDom).remove(); } if (this.eventHandlerDom) { $(this.eventHandlerDom).off(); $(this.eventHandlerDom).remove(); } if (this.animateDom) { $(this.animateDom).remove(); } $(this.dom).remove(); if (this.id && !isNotEventEmit) { this.removeAllListeners(); } } // 曼哈顿线的拐点 getBreakPoints() { return this._breakPoints; } _addEventListener() { let _clickEvent = (e) => { e.preventDefault(); e.stopPropagation(); this.emit('system.link.click', { edge: this }); this.emit('events', { type: 'link:click', edge: this }); }; let _mouseDownEvent = (e) => { let clickX = e.clientX; let clickY = e.clientY; if (this.shapeType === 'Manhattan' && this.draggable) { let x = this._coordinateService._terminal2canvas('x', clickX); let y = this._coordinateService._terminal2canvas('y', clickY); //把 _coordinateService 传进来 let targetPath = DrawUtil.findManhattanPoint(this._breakPoints, {x, y}); this.emit('InnerEvents', { type: 'link:dragBegin', edge: this, path: targetPath }); } else { // 单纯为了抛错事件给canvas,为了让canvas的dragtype不为空,不会触发canvas:click事件 this.emit('InnerEvents', { type: 'link:mouseDown', edge: this, }); } } if (this.isExpandWidth) { $(this.eventHandlerDom).on('click', _clickEvent); $(this.eventHandlerDom).on('mousedown', _mouseDownEvent); } else { $(this.dom).on('click', _clickEvent); $(this.dom).on('mousedown', _mouseDownEvent); } } _create(opts) { this.id = _.get(opts, 'id') || this.id; this.targetNode = _.get(opts, 'targetNode') || this.targetNode; this._targetType = _.get(opts, '_targetType') || this._targetType; this.targetEndpoint = _.get(opts, 'targetEndpoint') || this.targetEndpoint; this.sourceNode = _.get(opts, 'sourceNode') || this.sourceNode; this._sourceType = _.get(opts, '_sourceType') || this._sourceType; this.sourceEndpoint = _.get(opts, 'sourceEndpoint') || this.sourceEndpoint; this.type = _.get(opts, 'type') || this.type; _.set(this, 'options.targetNode', _.get(this, 'targetNode.id')); _.set(this, 'options.targetEndpoint', _.get(this, 'targetEndpoint.id')); this.redraw(); } _updatePath(path, pos) { // 增加拐点 if (this._breakPoints[path.index].type === 'start') { let newBreakPoint = _.cloneDeep(this._breakPoints[path.index]); this._breakPoints.unshift(newBreakPoint); path.index++; delete this._breakPoints[path.index].type; } else if (this._breakPoints[path.index + 1].type === 'end') { let newBreakPoint = _.cloneDeep(this._breakPoints[path.index + 1]); this._breakPoints.push(newBreakPoint); delete this._breakPoints[path.index + 1].type; } if (path.direction === 'vertical') { this._breakPoints[path.index].x = pos.x; this._breakPoints[path.index + 1].x = pos.x; } else { this._breakPoints[path.index].y = pos.y; this._breakPoints[path.index + 1].y = pos.y; } this._hasDragged = true; this.redraw(); // 减少拐点 for(let i = 0; i < this._breakPoints.length - 1; i++) { let point1 = this._breakPoints[i]; let point2 = this._breakPoints[i + 1]; if (point1.x === point2.x && point1.y === point2.y && point1.type !== 'start' && point2.type !== 'end') { this._breakPoints.splice(i, 2); path.index = path.index - 2 > 0 ? path.index - 2: 0; } } } } export default BaseEdge;