UNPKG

butterfly-dag

Version:

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

414 lines (378 loc) 13.5 kB
'use strict'; import _ from 'lodash'; import {_calcOrientation} from './_utils.js'; function _drawFlowSegment(segments, offset) { let current = null; let next = null; let cornerRadius = 0; // 拐角角度,按道理可以配置 let lw = 1; // strokeWidth,线条的粗细 let _segLength = (s) => { return Math.sqrt(Math.pow(s[0] - s[2], 2) + Math.pow(s[1] - s[3], 2)); }; let result = []; let _drawStraight = (d) => { return ['M', d.x1 + offset.x, d.y1 + offset.y, 'L', d.x2 + offset.x, d.y2 + offset.y]; }; let _drawArc = (d) => { // 有重复,可抽象,计算象限 let _quadrant = (p1, p2) => { if (p2[0] > p1[0]) { return (p2[1] > p1[1]) ? 2 : 1; } else if (p2[0] === p1[0]) { return p2[1] > p1[1] ? 2 : 1; } else { return (p2[1] > p1[1]) ? 3 : 4; } }; // 计算两点之间的直线的梯度 let _gradient = (p1, p2) => { if (p2[0] === p1[0]) { return p2[1] > p1[0] ? Infinity : -Infinity; } else if (p2[1] === p1[1]) { return p2[0] > p1[0] ? 0 : -0; } else { return (p2[1] - p1[1]) / (p2[0] - p1[0]); } }; // Calculates the angle between the two points let _theta = (p1, p2) => { let m = _gradient(p1, p2); let t = Math.atan(m); let s = _quadrant(p1, p2); if ((s === 4 || s === 3)) { t += Math.PI; } if (t < 0) { t += (2 * Math.PI); } return t; }; let _calcAngle = function (x, y) { return _theta([d.cx, d.cy], [x, y]); }; let startAngle = _calcAngle(x1, y1); let endAngle = _calcAngle(x2, y2); let TWO_PI = 2 * Math.PI; if (endAngle < 0) { endAngle += TWO_PI; } if (startAngle < 0) { startAngle += TWO_PI; } // we now have startAngle and endAngle as positive numbers, meaning the // absolute difference (|d|) between them is the sweep (s) of this arc, unless the // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|. let ea = endAngle < startAngle ? endAngle + TWO_PI : endAngle; let sweep = Math.abs(ea - startAngle); let anticlockwise = d.ac; if (anticlockwise) { sweep = TWO_PI - sweep; } return ['M', d.x1 + offset.x, d.y1 + offset.y, 'A', d.r, d.r, '0', ',', d.x2 + offset.x, d.y2 + offset.y]; }; // let offsetX = sourcePoint.pos[0] < targetPoint.pos[0] ? sourcePoint.pos[0] : targetPoint.pos[0]; // let offsetY = sourcePoint.pos[1] < targetPoint.pos[1] ? sourcePoint.pos[1] : targetPoint.pos[1]; for (let i = 0; i < segments.length - 1; i++) { current = current || _.cloneDeep(segments[i]); next = _.cloneDeep(segments[i + 1]); if (cornerRadius > 0 && current[4] !== next[4]) { let radiusToUse = Math.min(cornerRadius, _segLength(current), _segLength(next)); // right angle. adjust current segment's end point, and next segment's start point. current[2] -= current[5] * radiusToUse; current[3] -= current[6] * radiusToUse; next[0] += next[5] * radiusToUse; next[1] += next[6] * radiusToUse; let ac = (current[6] === next[5] && next[5] === 1) || ((current[6] === next[5] && next[5] === 0) && current[5] !== next[6]) || (current[6] === next[5] && next[5] === -1); let sgny = next[1] > current[3] ? 1 : -1; let sgnx = next[0] > current[2] ? 1 : -1; let sgnEqual = sgny === sgnx; let cx = (sgnEqual && ac || (!sgnEqual && !ac)) ? next[0] : current[2]; let cy = (sgnEqual && ac || (!sgnEqual && !ac)) ? current[3] : next[1]; let _line1 = _drawStraight({ x1: current[0], y1: current[1], x2: current[2], y2: current[3] }); let _line2 = _drawArc({ r: radiusToUse, x1: current[2], y1: current[3], x2: next[0], y2: next[1], cx: cx, cy: cy, ac: ac }); result = result.concat(_line1); result = result.concat(_line2); } else { // dx + dy are used to adjust for line width. let dx = (current[2] === current[0]) ? 0 : (current[2] > current[0]) ? (lw / 2) : -(lw / 2); let dy = (current[3] === current[1]) ? 0 : (current[3] > current[1]) ? (lw / 2) : -(lw / 2); let _line = _drawStraight({ x1: current[0] - dx, y1: current[1] - dy, x2: current[2] + dx, y2: current[3] + dy }); result = result.concat(_line); } current = next; } if (next !== null) { let _line = _drawStraight({ x1: next[0], y1: next[1], x2: next[2], y2: next[3] }); result = result.concat(_line); } return result.join(' '); } function drawFlow(sourcePoint, targetPoint, orientationLimit) { if (!sourcePoint.orientation) { sourcePoint.orientation = _calcOrientation(targetPoint.pos[0], targetPoint.pos[1], sourcePoint.pos[0], sourcePoint.pos[1], orientationLimit); } if (!targetPoint.orientation) { targetPoint.orientation = _calcOrientation(sourcePoint.pos[0], sourcePoint.pos[1], targetPoint.pos[0], targetPoint.pos[1], orientationLimit); } let stub = 30; // 每部分折线的最小长度 let midpoint = 0.5; // 折线中点 let w = Math.abs(sourcePoint.pos[0] - targetPoint.pos[0]); let h = Math.abs(sourcePoint.pos[1] - targetPoint.pos[1]); let sx = targetPoint.pos[0] < sourcePoint.pos[0] ? w : 0; let sy = targetPoint.pos[1] < sourcePoint.pos[1] ? h : 0; let tx = targetPoint.pos[0] < sourcePoint.pos[0] ? 0 : w; let ty = targetPoint.pos[1] < sourcePoint.pos[1] ? 0 : h; let offsetX = sourcePoint.pos[0] < targetPoint.pos[0] ? sourcePoint.pos[0] : targetPoint.pos[0]; let offsetY = sourcePoint.pos[1] < targetPoint.pos[1] ? sourcePoint.pos[1] : targetPoint.pos[1]; // 小心有可能so[0]和to[0]同时为0;或者是so[1]和to[1]同时为0 let so = sourcePoint.orientation; let to = targetPoint.orientation; // 拿来判断是对面,垂直还是正交 let oProduct = ((so[0] * to[0]) + (so[1] * to[1])); let sourceAxis = so[0] === 0 ? 'y' : 'x'; let startStubX = sx + (so[0] * stub); let startStubY = sy + (so[1] * stub); let endStubX = tx + (to[0] * stub); let endStubY = ty + (to[1] * stub); let isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (stub + stub); let isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (stub + stub); // 判断方向 let anchorOrientation = null; if (oProduct === -1) { anchorOrientation = 'opposite'; } else if (oProduct === 0) { anchorOrientation = 'perpendicular'; } else if (oProduct === 1) { anchorOrientation = 'orthogonal'; } // 计算折线的方法 let _commonStubCalculator = () => { return [startStubX, startStubY, endStubX, endStubY]; }; let stubCalculators = { perpendicular: _commonStubCalculator, orthogonal: _commonStubCalculator, opposite: (axis) => { let idx = axis === 'x' ? 0 : 1; let areInProximity = { x: () => { return ( (so[idx] === 1 && ( ( (startStubX > endStubX) && (tx > startStubX) ) || ( (sx > endStubX) && (tx > sx))))) || ( (so[idx] === -1 && ( ( (startStubX < endStubX) && (tx < startStubX) ) || ( (sx < endStubX) && (tx < sx))))); }, y: () => { return ( (so[idx] === 1 && ( ( (startStubY > endStubY) && (ty > startStubY) ) || ( (sy > endStubY) && (ty > sy))))) || ( (so[idx] === -1 && ( ( (startStubY < endStubY) && (ty < startStubY) ) || ( (sy < endStubY) && (ty < sy))))); } }; // 判断是否需要折线 if (areInProximity[axis]()) { // 这判断可以设置总是有折线 return { x: [(sx + tx) / 2, startStubY, (sx + tx) / 2, endStubY], y: [startStubX, (sy + ty) / 2, endStubX, (sy + ty) / 2] }[axis]; } else { return [startStubX, startStubY, endStubX, endStubY]; } } }; // 加工线条处理的方法 let lastx = null; let lasty = null; let segments = []; let _sgn = (n) => { return n < 0 ? -1 : n === 0 ? 0 : 1; }; let _addSegment = (x, y) => { if (lastx === x && lasty === y) { return; } let lx = lastx == null ? sx : lastx; let ly = lasty == null ? sy : lasty; let o = lx === x ? 'v': 'h'; let sgnx = _sgn(x - lx); let sgny = _sgn(y - ly); lastx = x; lasty = y; segments.push([lx, ly, x, y, o, sgnx, sgny]); }; // 开始实现 let stubs = stubCalculators[anchorOrientation](sourceAxis); let idx = sourceAxis === 'x' ? 0 : 1; let oidx = sourceAxis === 'x' ? 0 : 1; let ss = stubs[idx]; let oss = stubs[oidx]; let es = stubs[idx + 2]; let oes = stubs[oidx + 2]; _addSegment(stubs[0], stubs[1]); let midx = startStubX + (endStubX - startStubX) * midpoint; let midy = startStubY + (endStubY - startStubY) * midpoint; let orientations = { x: [ 0, 1 ], y: [ 1, 0 ] }; let _lineCalculators = { perpendicular: (axis) => { let sis = { x: [ [[1, 2, 3, 4], null, [2, 1, 4, 3]], null, [[4, 3, 2, 1], null, [3, 4, 1, 2]] ], y: [ [[3, 2, 1, 4], null, [2, 3, 4, 1]], null, [[4, 1, 2, 3], null, [1, 4, 3, 2]] ] }; let stubs = { x: [[startStubX, endStubX], null, [endStubX, startStubX]], y: [[startStubY, endStubY], null, [endStubY, startStubY]] }; let midLines = { x: [[midx, startStubY], [midx, endStubY]], y: [[startStubX, midy], [endStubX, midy]] }; let linesToEnd = { x: [[endStubX, startStubY]], y: [[startStubX, endStubY]] }; let startToEnd = { x: [[startStubX, endStubY], [endStubX, endStubY]], y: [[endStubX, startStubY], [endStubX, endStubY]] }; let startToMidToEnd = { x: [[startStubX, midy], [endStubX, midy], [endStubX, endStubY]], y: [[midx, startStubY], [midx, endStubY], [endStubX, endStubY]] }; let otherStubs = { x: [startStubY, endStubY], y: [startStubX, endStubX] }; let soIdx = orientations[axis][0]; let toIdx = orientations[axis][1]; let _so = so[soIdx] + 1; let _to = to[toIdx] + 1; let otherFlipped = (to[toIdx] === -1 && (otherStubs[axis][1] < otherStubs[axis][0])) || (to[toIdx] === 1 && (otherStubs[axis][1] > otherStubs[axis][0])); let stub1 = stubs[axis][_so][0]; let stub2 = stubs[axis][_so][1]; let segmentIndexes = sis[axis][_so][_to]; // 计算一下节点象限 let _quadrant = (p1, p2) => { if (p2.pos[0] > p1.pos[0]) { return (p2.pos[1] > p1.pos[1]) ? 2 : 1; } else if (p2.pos[0] == p1.pos[0]) { return p2.pos[1] > p1.pos[1] ? 2 : 1; } else { return (p2.pos[1] > p1.pos[1]) ? 3 : 4; } }; var segment = _quadrant(sourcePoint, targetPoint); if (segment === segmentIndexes[3] || (segment === segmentIndexes[2] && otherFlipped)) { return midLines[axis]; } else if (segment === segmentIndexes[2] && stub2 < stub1) { return linesToEnd[axis]; } else if ((segment === segmentIndexes[2] && stub2 >= stub1) || (segment === segmentIndexes[1] && !otherFlipped)) { return startToMidToEnd[axis]; } else if (segment === segmentIndexes[0] || (segment === segmentIndexes[1] && otherFlipped)) { return startToEnd[axis]; } }, orthogonal: (axis, startStub, otherStartStub, endStub, otherEndStub) => { let extent = { x: so[0] === -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub), y: so[1] === -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub) }[axis]; return { x: [ [extent, otherStartStub], [extent, otherEndStub], [endStub, otherEndStub] ], y: [ [otherStartStub, extent], [otherEndStub, extent], [otherEndStub, endStub] ] }[axis]; }, opposite: (axis, ss, oss, es) => { let otherAxis = {x: 'y', y: 'x'}[axis]; let dim = {x: 'height', y: 'width'}[axis]; let comparator = axis === 'x' ? isXGreaterThanStubTimes2 : isYGreaterThanStubTimes2; // 考虑下自连的情况, 现在很不严禁 if (sourcePoint.pos[0] === targetPoint.pos[0] && sourcePoint.pos[1] === targetPoint.pos[1]) { // } else if (!comparator || (so[idx] === 1 && ss > es) || (so[idx] === -1 && ss < es)) { return { x: [ [ss, midy], [es, midy] ], y: [ [midx, ss], [midx, es] ] }[axis]; } else if ((so[idx] === 1 && ss < es) || (so[idx] === -1 && ss > es)) { return { x: [ [midx, sy], [midx, ty] ], y: [ [sx, midy], [tx, midy] ] }[axis]; } } }; // 计算剩余线条 var p = _lineCalculators[anchorOrientation](sourceAxis, ss, oss, es, oes); if (p) { for (let i = 0; i < p.length; p++) { _addSegment(p[i][0], p[i][1]); } } // line to end stub _addSegment(stubs[2], stubs[3]); // end stub to end (common) _addSegment(tx, ty); // 实际操作svg return _drawFlowSegment(segments, { x: offsetX, y: offsetY }); } export default drawFlow;