@antv/g6
Version:
graph visualization frame work
356 lines (338 loc) • 11.5 kB
JavaScript
/**
* @fileOverview 自定义边
* @description 自定义边中有大量逻辑同自定义节点重复,虽然可以提取成为 mixin ,但是考虑到代码的可读性,还是单独实现。
* @author dxq613@gmail.com
*/
const Shape = require('./shape');
const Util = require('../util');
const Global = require('../global');
const SingleShapeMixin = require('./single-shape-mixin');
const CLS_SHAPE = 'edge-shape';
// start,end 倒置,center 不变
function revertAlign(labelPosition) {
let textAlign = labelPosition;
if (labelPosition === 'start') {
textAlign = 'end';
} else if (labelPosition === 'end') {
textAlign = 'start';
}
return textAlign;
}
// 注册 Edge 的工厂方法
Shape.registerFactory('edge', {
defaultShapeType: 'line'
});
const singleEdgeDefinition = Util.mix({}, SingleShapeMixin, {
itemType: 'edge',
/**
* 文本的位置
* @type {String}
*/
labelPosition: 'center', // start, end, center
/**
* 文本是否跟着线自动旋转,默认 false
* @type {Boolean}
*/
labelAutoRotate: false,
/**
* 获取边的 path
* @internal 供扩展的边覆盖
* @param {Array} points 构成边的点的集合
* @return {Array} 构成 path 的数组
*/
getPath(points) {
const path = [];
Util.each(points, (point, index) => {
if (index === 0) {
path.push([ 'M', point.x, point.y ]);
} else {
path.push([ 'L', point.x, point.y ]);
}
});
return path;
},
getShapeStyle(cfg) {
const customOptions = this.getCustomConfig(cfg) || {};
const { style: defaultStyle } = this.options;
const { style: customStyle } = customOptions;
const strokeStyle = {
stroke: cfg.color
};
// 如果设置了color,则覆盖默认的stroke属性
const style = Util.deepMix({}, defaultStyle, customStyle, strokeStyle, cfg.style);
const size = cfg.size || Global.defaultEdge.size;
cfg = this.getPathPoints(cfg);
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const controlPoints = this.getControlPoints(cfg);
let points = [ startPoint ]; // 添加起始点
// 添加控制点
if (controlPoints) {
points = points.concat(controlPoints);
}
// 添加结束点
points.push(endPoint);
const path = this.getPath(points);
const styles = Util.mix({}, Global.defaultEdge.style, {
stroke: Global.defaultEdge.color,
lineWidth: size,
path
}, style);
return styles;
},
getLabelStyleByPosition(cfg, labelCfg, group) {
const labelPosition = labelCfg.position || this.labelPosition; // 文本的位置用户可以传入
const style = {};
const pathShape = group.findByClassName(CLS_SHAPE);
// 不对 pathShape 进行判空,如果线不存在,说明有问题了
let pointPercent;
if (labelPosition === 'start') {
pointPercent = 0;
} else if (labelPosition === 'end') {
pointPercent = 1;
} else {
pointPercent = 0.5;
}
const { refX, refY } = labelCfg; // 默认的偏移量
// 如果两个节点重叠,线就变成了一个点,这时候label的位置,就是这个点 + 绝对偏移
if (cfg.startPoint.x === cfg.endPoint.x && cfg.startPoint.y === cfg.endPoint.y) {
style.x = cfg.startPoint.x + refX ? refX : 0;
style.y = cfg.endPoint.y + refY ? refY : 0;
return style;
}
const autoRotate = Util.isNil(labelCfg.autoRotate) ? this.labelAutoRotate : labelCfg.autoRotate;
const offsetStyle = Util.getLabelPosition(pathShape, pointPercent, refX, refY, autoRotate);
style.x = offsetStyle.x;
style.y = offsetStyle.y;
style.rotate = offsetStyle.rotate;
style.textAlign = this._getTextAlign(labelPosition, offsetStyle.angle);
return style;
},
// 获取文本对齐方式
_getTextAlign(labelPosition, angle) {
let textAlign = 'center';
if (!angle) {
return labelPosition;
}
angle = angle % (Math.PI * 2); // 取模
if (labelPosition !== 'center') {
if ((angle >= 0 && angle <= Math.PI / 2) || (angle >= 3 / 2 * Math.PI && angle < 2 * Math.PI)) {
textAlign = labelPosition;
} else {
textAlign = revertAlign(labelPosition);
}
}
return textAlign;
},
/**
* @internal 获取边的控制点
* @param {Object} cfg 边的配置项
* @return {Array} 控制点的数组
*/
getControlPoints(cfg) {
return cfg.controlPoints;
},
/**
* @internal 处理需要重计算点和边的情况
* @param {Object} cfg 边的配置项
* @return {Object} 边的配置项
*/
getPathPoints(cfg) {
return cfg;
},
/**
* 绘制边
* @override
* @param {Object} cfg 边的配置项
* @param {G.Group} group 边的容器
* @return {G.Shape} 图形
*/
drawShape(cfg, group) {
const shapeStyle = this.getShapeStyle(cfg);
const shape = group.addShape('path', {
className: CLS_SHAPE,
attrs: shapeStyle
});
return shape;
},
drawLabel(cfg, group) {
const customStyle = this.getCustomConfig(cfg) || {};
const defaultConfig = customStyle.default || {};
const labelCfg = Util.deepMix({}, this.options.labelCfg, defaultConfig.labelCfg, cfg.labelCfg);
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
const label = group.addShape('text', {
attrs: labelStyle
});
return label;
}
});
// // 直线
Shape.registerEdge('single-line', singleEdgeDefinition);
// // 直线, 不支持控制点
Shape.registerEdge('line', {
// 控制点不生效
getControlPoints() {
return [];
}
}, 'single-line');
// // 折线,支持多个控制点
// Shape.registerEdge('polyline', {}, 'single-line');
// 直线
Shape.registerEdge('spline', {
getPath(points) {
const path = Util.getSpline(points);
return path;
}
}, 'single-line');
Shape.registerEdge('arc', {
curveOffset: 20,
clockwise: 1,
getControlPoints(cfg) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const midPoint = {
x: (startPoint.x + endPoint.x) / 2,
y: (startPoint.y + endPoint.y) / 2
};
let center;
let arcPoint;
// 根据给定点计算圆弧
if (cfg.controlPoints !== undefined) {
arcPoint = cfg.controlPoints[0];
center = Util.getCircleCenterByPoints(startPoint, arcPoint, endPoint);
// 根据控制点和直线关系决定 clockwise值
if (startPoint.x <= endPoint.x && startPoint.y > endPoint.y) {
this.clockwise = center.x > midPoint.x ? 1 : 0;
} else if (startPoint.x <= endPoint.x && startPoint.y < endPoint.y) {
this.clockwise = center.x > midPoint.x ? 0 : 1;
} else if (startPoint.x > endPoint.x && startPoint.y <= endPoint.y) {
this.clockwise = center.y < midPoint.y ? 1 : 0;
} else {
this.clockwise = center.y < midPoint.y ? 1 : 0;
}
// 若给定点和两端点共线,无法生成圆弧,绘制直线
if ((arcPoint.x - startPoint.x) / (arcPoint.y - startPoint.y)
=== (endPoint.x - startPoint.x) / (endPoint.y - startPoint.y)) {
return [];
}
} else { // 根据直线连线中点的的偏移计算圆弧
// 若用户给定偏移量则根据其计算,否则按照默认偏移值计算
if (cfg.curveOffset !== undefined) {
this.curveOffset = cfg.curveOffset;
}
if (this.curveOffset < 0) this.clockwise = 0;
else this.clockwise = 1;
const vec = {
x: endPoint.x - startPoint.x,
y: endPoint.y - startPoint.y
};
const edgeAngle = Math.atan2(vec.y, vec.x);
arcPoint = {
x: this.curveOffset * Math.cos((-Math.PI / 2 + edgeAngle)) + midPoint.x,
y: this.curveOffset * Math.sin((-Math.PI / 2 + edgeAngle)) + midPoint.y
};
center = Util.getCircleCenterByPoints(startPoint, arcPoint, endPoint);
}
const radius = Util.distance(startPoint, center);
const controlPoints = [{ x: radius, y: radius }];
return controlPoints;
},
getPath(points) {
const path = [];
path.push([ 'M', points[0].x, points[0].y ]);
// 控制点与端点共线
if (points.length === 2) {
path.push([ 'L', points[1].x, points[1].y ]);
} else {
path.push([ 'A', points[1].x, points[1].y, 0, 0, this.clockwise, points[2].x, points[2].y ]);
}
return path;
}
}, 'single-line');
Shape.registerEdge('quadratic', {
curvePosition: 0.5, // 弯曲的默认位置
curveOffset: -20, // 弯曲度,沿着startPoint, endPoint 的垂直向量(顺时针)方向,距离线的距离,距离越大越弯曲
getControlPoints(cfg) {
let controlPoints = cfg.controlPoints; // 指定controlPoints
if (!controlPoints || !controlPoints.length) {
const { startPoint, endPoint } = cfg;
const innerPoint = Util.getControlPoint(startPoint, endPoint, this.curvePosition, this.curveOffset);
controlPoints = [ innerPoint ];
}
return controlPoints;
},
getPath(points) {
const path = [];
path.push([ 'M', points[0].x, points[0].y ]);
path.push([ 'Q', points[1].x, points[1].y, points[2].x, points[2].y ]);
return path;
}
}, 'single-line');
Shape.registerEdge('cubic', {
curvePosition: [ 1 / 2, 1 / 2 ],
curveOffset: [ -20, 20 ],
getControlPoints(cfg) {
let controlPoints = cfg.controlPoints; // 指定controlPoints
if (!controlPoints || !controlPoints.length) {
const { startPoint, endPoint } = cfg;
const innerPoint1 = Util.getControlPoint(startPoint, endPoint, this.curvePosition[0], this.curveOffset[0]);
const innerPoint2 = Util.getControlPoint(startPoint, endPoint, this.curvePosition[1], this.curveOffset[1]);
controlPoints = [ innerPoint1, innerPoint2 ];
}
return controlPoints;
},
getPath(points) {
const path = [];
path.push([ 'M', points[0].x, points[0].y ]);
path.push([ 'C', points[1].x, points[1].y, points[2].x, points[2].y, points[3].x, points[3].y ]);
return path;
}
}, 'single-line');
// 垂直方向的三阶贝塞尔曲线,不再考虑用户外部传入的控制点
Shape.registerEdge('cubic-vertical', {
curvePosition: [ 1 / 2, 1 / 2 ],
getControlPoints(cfg) {
const { startPoint, endPoint } = cfg;
const innerPoint1 = {
x: startPoint.x,
y: (endPoint.y - startPoint.y) * this.curvePosition[0] + startPoint.y
};
const innerPoint2 = {
x: endPoint.x,
y: (endPoint.y - startPoint.y) * this.curvePosition[1] + startPoint.y
};
const controlPoints = [ innerPoint1, innerPoint2 ];
return controlPoints;
}
}, 'cubic');
// 水平方向的三阶贝塞尔曲线,不再考虑用户外部传入的控制点
Shape.registerEdge('cubic-horizontal', {
curvePosition: [ 1 / 2, 1 / 2 ],
getControlPoints(cfg) {
const { startPoint, endPoint } = cfg;
const innerPoint1 = {
x: (endPoint.x - startPoint.x) * this.curvePosition[0] + startPoint.x,
y: startPoint.y
};
const innerPoint2 = {
x: (endPoint.x - startPoint.x) * this.curvePosition[1] + startPoint.x,
y: endPoint.y
};
const controlPoints = [ innerPoint1, innerPoint2 ];
return controlPoints;
}
}, 'cubic');
Shape.registerEdge('loop', {
getPathPoints(cfg) {
return Util.getLoopCfgs(cfg);
},
getControlPoints(cfg) {
return cfg.controlPoints;
},
afterDraw(cfg) {
cfg.controlPoints = null;
},
afterUpdate(cfg) {
cfg.controlPoints = null;
}
}, 'cubic');