UNPKG

@antv/g6

Version:

graph visualization frame work

293 lines (243 loc) 8.82 kB
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /** * @fileOverview graphic util * @author huangtonger@aliyun.com */ var MathUtil = require('./math'); var BaseUtil = require('./base'); var Global = require('../global'); var PI = Math.PI; var sin = Math.sin; var cos = Math.cos; // 一共支持8个方向的自环,每个环占的角度是45度,在计算时再二分,为22.5度 var SELF_LINK_SIN = sin(PI / 8); var SELF_LINK_COS = cos(PI / 8); function traverse(data, fn) { if (fn(data) === false) { return; } BaseUtil.each(data.children, function (child) { traverse(child, fn); }); } var GraphicUtil = { getBBox: function getBBox(element, parent) { var bbox = element.getBBox(); var leftTop = { x: bbox.minX, y: bbox.minY }; var rightBottom = { x: bbox.maxX, y: bbox.maxY }; // 根据父元素变换矩阵 if (parent) { var matrix = parent.getMatrix(); leftTop = MathUtil.applyMatrix(leftTop, matrix); rightBottom = MathUtil.applyMatrix(rightBottom, matrix); } return { minX: leftTop.x, minY: leftTop.y, maxX: rightBottom.x, maxY: rightBottom.y }; }, // 获取某元素的自环边配置 getLoopCfgs: function getLoopCfgs(cfg) { var item = cfg.sourceNode || cfg.targetNode; var containerMatrix = item.get('group').getMatrix(); var bbox = item.getKeyShape().getBBox(); var loopCfg = cfg.loopCfg || {}; // 距离keyShape边的最高距离 var dist = loopCfg.dist || Math.max(bbox.width, bbox.height) * 2; // 自环边与keyShape的相对位置关系 var position = loopCfg.position || Global.loopPosition; var r = Math.max(bbox.width, bbox.height) / 2; var scaleRate = (r + dist) / r; // 中心取group上真实位置 var center = [containerMatrix[6], containerMatrix[7]]; var sinDelta = r * SELF_LINK_SIN; var cosDelta = r * SELF_LINK_COS; var startPoint = [cfg.startPoint.x, cfg.startPoint.y]; var endPoint = [cfg.endPoint.x, cfg.endPoint.y]; // 如果定义了锚点的,直接用锚点坐标,否则,根据自环的 cfg 计算 if (startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1]) { switch (position) { case 'top': startPoint = [center[0] - sinDelta, center[1] - cosDelta]; endPoint = [center[0] + sinDelta, center[1] - cosDelta]; break; case 'top-right': startPoint = [center[0] + sinDelta, center[1] - cosDelta]; endPoint = [center[0] + cosDelta, center[1] - sinDelta]; break; case 'right': startPoint = [center[0] + cosDelta, center[1] - sinDelta]; endPoint = [center[0] + cosDelta, center[1] + sinDelta]; break; case 'bottom-right': startPoint = [center[0] + cosDelta, center[1] + sinDelta]; endPoint = [center[0] + sinDelta, center[1] + cosDelta]; break; case 'bottom': startPoint = [center[0] + sinDelta, center[1] + cosDelta]; endPoint = [center[0] - sinDelta, center[1] + cosDelta]; break; case 'bottom-left': startPoint = [center[0] - sinDelta, center[1] + cosDelta]; endPoint = [center[0] - cosDelta, center[1] + sinDelta]; break; case 'left': startPoint = [center[0] - cosDelta, center[1] + sinDelta]; endPoint = [center[0] - cosDelta, center[1] - sinDelta]; break; case 'top-left': startPoint = [center[0] - cosDelta, center[1] - sinDelta]; endPoint = [center[0] - sinDelta, center[1] - cosDelta]; break; default: startPoint = [center[0] - sinDelta, center[1] - cosDelta]; endPoint = [center[0] + sinDelta, center[1] - cosDelta]; } // 如果逆时针画,交换起点和终点 if (loopCfg.clockwise === false) { var swap = [startPoint[0], startPoint[1]]; startPoint = [endPoint[0], endPoint[1]]; endPoint = [swap[0], swap[1]]; } } var startVec = [startPoint[0] - center[0], startPoint[1] - center[1]]; var startExtendVec = BaseUtil.vec2.scale([], startVec, scaleRate); var controlPoint1 = [center[0] + startExtendVec[0], center[1] + startExtendVec[1]]; var endVec = [endPoint[0] - center[0], endPoint[1] - center[1]]; var endExtendVec = BaseUtil.vec2.scale([], endVec, scaleRate); var controlPoint2 = [center[0] + endExtendVec[0], center[1] + endExtendVec[1]]; cfg.startPoint = { x: startPoint[0], y: startPoint[1] }; cfg.endPoint = { x: endPoint[0], y: endPoint[1] }; cfg.controlPoints = [{ x: controlPoint1[0], y: controlPoint1[1] }, { x: controlPoint2[0], y: controlPoint2[1] }]; return cfg; }, traverseTree: function traverseTree(data, fn) { if (typeof fn !== 'function') { return; } traverse(data, fn); }, radialLayout: function radialLayout(data, layout) { // 布局方式有 H / V / LR / RL / TB / BT var VERTICAL_LAYOUTS = ['V', 'TB', 'BT']; var min = { x: Infinity, y: Infinity }; var max = { x: -Infinity, y: -Infinity }; // 默认布局是垂直布局TB,此时x对应rad,y对应r var rScale = 'x'; var radScale = 'y'; if (layout && VERTICAL_LAYOUTS.indexOf(layout) >= 0) { // 若是水平布局,y对应rad,x对应r radScale = 'x'; rScale = 'y'; } var count = 0; this.traverseTree(data, function (node) { count++; if (node.x > max.x) { max.x = node.x; } if (node.x < min.x) { min.x = node.x; } if (node.y > max.y) { max.y = node.y; } if (node.y < min.y) { min.y = node.y; } }); var avgRad = PI * 2 / count; var radDiff = max[radScale] - min[radScale]; if (radDiff === 0) { return data; } this.traverseTree(data, function (node) { var radial = (node[radScale] - min[radScale]) / radDiff * (PI * 2 - avgRad) + avgRad; var r = Math.abs(rScale === 'x' ? node.x - data.x : node.y - data.y); node.x = r * Math.cos(radial); node.y = r * Math.sin(radial); }); return data; }, /** * 根据 label 所在线条的位置百分比,计算 label 坐标 * @param {object} pathShape G 的 path 实例,一般是 Edge 实例的 keyShape * @param {number} percent 范围 0 - 1 的线条百分比 * @param {number} refX x 轴正方向为基准的 label 偏移 * @param {number} refY y 轴正方向为基准的 label 偏移 * @param {boolean} rotate 是否根据线条斜率旋转文本 * @return {object} 文本的 x, y, 文本的旋转角度 */ getLabelPosition: function getLabelPosition(pathShape, percent, refX, refY, rotate) { var TAN_OFFSET = 0.0001; var vector = []; var point = pathShape.getPoint(percent); if (point === null) { return { x: 0, y: 0, angle: 0 }; } // 头尾最可能,放在最前面,使用 g path 上封装的方法 if (percent < TAN_OFFSET) { vector = pathShape.getStartTangent().reverse(); } else if (percent > 1 - TAN_OFFSET) { vector = pathShape.getEndTangent(); } else { // 否则取指定位置的点,与少量偏移的点,做微分向量 var offsetPoint = pathShape.getPoint(percent + TAN_OFFSET); vector.push([point.x, point.y]); vector.push([offsetPoint.x, offsetPoint.y]); } var rad = Math.atan2(vector[1][1] - vector[0][1], vector[1][0] - vector[0][0]); if (rad < 0) { rad += PI * 2; } if (refX) { point.x += cos(rad) * refX; point.y += sin(rad) * refX; } if (refY) { // 默认方向是 x 轴正方向,法线是 求出角度 - 90° var normal = rad - PI / 2; // 若法线角度在 y 轴负方向,切到正方向,保证 refY 相对于 y 轴正方向 if (rad > 1 / 2 * PI && rad < 3 * 1 / 2 * PI) { normal -= PI; } point.x += cos(normal) * refY; point.y += sin(normal) * refY; } // 需要原始的旋转角度计算 textAlign var result = { x: point.x, y: point.y, angle: rad }; if (rotate) { if (rad > 1 / 2 * PI && rad < 3 * 1 / 2 * PI) { rad -= PI; } return _extends({ rotate: rad }, result); } return result; } }; module.exports = GraphicUtil;