有如下的一份数据,如何使用 G6 让两个节点之间连多条边?
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 150,
label: 'node1',
}, {
id: 'node2',
x: 300,
y: 150,
label: 'node2'
}],
edges: [{
source: 'node1',
target: 'node2'
},
{
source: 'node2',
target: 'node1'
}
]
};
接到上面这个问题后,我们马上就开始动手,二话不说先撸出了下面这段代码。
const graph = new G6.Graph({
container: GRAPH_CONTAINER,
width: 500,
height: 500,
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9'
},
labelCfg: {
style: {
fontSize: 12
}
}
},
defaultEdge: {
style: {
stroke: '#e2e2e2'
}
}
});
graph.data(data);
graph.render();
看起来心情还不错,给节点和边都设置了样式。
运行上面的代码以后,却发现现实是如此的残酷,说好的两条边哪去了,怎么还只有一条呢?难道是线太直了,果断换成贝塞尔曲线。
const graph = new G6.Graph({
// ...
defaultEdge: {
shape: 'cubic',
style: {
stroke: '#e2e2e2'
}
}
});
// ...
难道是弯过头,怎么还是这个鬼样子?
对了,G6 还支持
quadratic
类型的边,也拿来试试吧。
const graph = new G6.Graph({
// ...
defaultEdge: {
shape: 'quadratic',
style: {
stroke: '#e2e2e2'
}
}
});
// ...
神奇,居然可以了。
这个时候,突然想起,G6 3.1 版本新增了一个 polyline
类型的边。
const graph = new G6.Graph({
// ...
defaultEdge: {
shape: 'polyline',
style: {
stroke: '#e2e2e2'
}
}
});
// ...
拖动下节点看看。
这什么鬼东西,看来还是
quadratic
香啊。
这个时候,PD 突然说到,两个节点之间需要显示3条边,what,这不 so easy 吗?
年轻的你很开心地把数据改成了这样:
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 150,
label: 'node1',
}, {
id: 'node2',
x: 300,
y: 150,
label: 'node2'
}],
edges: [{
source: 'node1',
target: 'node2'
},
{
source: 'node2',
target: 'node1'
},
{
source: 'node2',
target: 'node1'
}
]
};
然而,执行结果却很骨感。
新加的边哪去了?
就正在你 百思不得骑姐其解 的时候,突然灵光一闪,想到了原来 G6 还是强大的黑科技「自定义边」。
有了这个黑科技,什么样的需求,那还不是分分钟的事。
当然了,在使用「自定义边」的之前,有两件事还是需要明确下的:
- 两个节点之间同方向超过一条边的,总需要有个标识来区分;
- 需要有一个值控制边的弯曲度,以防边重叠。
我们就在边数据中添加一个 edgeType 用于区分不同的边。有了这个约定以后,就可以开始动手撸码了。
自定义边后的效果如下:完善的自定义边的代码如下:
const edgeTypeColorMap = {
type1: ['#531dab', '#391085', '#391085'],
type2: ['#d9d9d9', '#bfbfbf', '#8c8c8c'],
type3: ['#d3adf7', '#b37feb', '#9254de']
}
const defaultConf = {
style: {
lineAppendWidth: 5,
lineDash: [0, 0],
lineDashOffset: 0,
opacity: 1,
labelCfg: {
style: {
fillOpacity: 1
}
}
},
/**
* 绘制边
* @override
* @param {Object} cfg 边的配置项
* @param {G.Group} group 边的容器
* @return {G.Shape} 图形
*/
drawShape(cfg, group) {
const item = group.get('item')
const shapeStyle = this.getShapeStyle(cfg, item);
const shape = group.addShape('path', {
className: 'edge-path',
attrs: shapeStyle
});
return shape;
},
drawLabel(cfg, group) {
const labelCfg = cfg.labelCfg || {}
const labelStyle = this.getLabelStyle(cfg, labelCfg, group)
const text = group.addShape('text', {
attrs: {
...labelStyle,
text: cfg.label,
fontSize: 12,
fill: '#404040',
cursor: 'pointer'
},
className: 'edge-label'
})
return text
},
/**
* 获取图形的配置项
* @internal 仅在定义这一类节点使用,用户创建和更新节点
* @param {Object} cfg 节点的配置项
* @return {Object} 图形的配置项
*/
getShapeStyle(cfg, item) {
const { startPoint, endPoint } = cfg
const type = item.get('type')
const defaultStyle = this.getStateStyle('default', true, item)
if(type === 'node') {
return Object.assign({}, cfg.style, defaultStyle);
}
const controlPoints = this.getControlPoints(cfg);
let points = [ startPoint ]; // 添加起始点
// 添加控制点
if (controlPoints) {
points = points.concat(controlPoints);
}
// 添加结束点
points.push(endPoint);
const path = this.getPath(points);
const style = Object.assign({}, { path }, cfg.style, defaultStyle);
return style;
},
getControlPoints(cfg) {
let controlPoints = cfg.controlPoints; // 指定controlPoints
if (!controlPoints || !controlPoints.length) {
const { startPoint, endPoint } = cfg;
const innerPoint = G6.Util.getControlPoint(startPoint, endPoint, 0.5, cfg.edgeOffset || 30);
controlPoints = [ innerPoint ];
}
return controlPoints;
},
/**
* 获取三次贝塞尔曲线的path
*
* @param {array} points 起始点和两个控制点
* @returns
*/
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;
},
/**
* 根据不同状态,获取不同状态下的样式值
* @param {string} name
* @param {string} value
* @param {Item} item
*/
getStateStyle(name, value, item) {
const model = item.getModel()
const { style = {} } = model
const defaultStyle = Object.assign({}, this.style)
// 更新颜色
return {
...defaultStyle,
lineWidth: 1,
stroke: edgeTypeColorMap[model.edgeType] && edgeTypeColorMap[model.edgeType][0],
...style
}
},
/**
* 拖动时更新path及边的label
*
* @param {object} cfg 边的model
* @param {Edge} item 边的实例
*/
update(cfg, item) {
const { data, style,
startPoint, endPoint, labelCfg = {} } = cfg
const group = item.getContainer()
const model = data || cfg
const defaultStyle = Object.assign({}, this.style, {
lineWidth: 1,
stroke: edgeTypeColorMap[model.edgeType] && edgeTypeColorMap[model.edgeType][0]
}, style)
const { opacity, onlyHideText } = defaultStyle
// 更新 path
const keyShape = item.getKeyShape();
const controlPoints = this.getControlPoints(cfg);
keyShape.attr({
path: [
['M', startPoint.x, startPoint.y],
['Q', controlPoints[0].x, controlPoints[0].y, endPoint.x, endPoint.y]
],
...defaultStyle
});
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
const text = group.findByClassName('edge-label');
const attrs = {
...labelStyle,
fillOpacity: onlyHideText ? 0 : opacity === 0 ? opacity : 1,
fill: '#404040'
}
if(text) {
text.resetMatrix();
text.attr(attrs);
}
}
};
G6.registerEdge('quadratic-label-edge', defaultConf, 'quadratic');
const GRAPH_CONTAINER = 'container';
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 150,
label: 'node1',
}, {
id: 'node2',
x: 300,
y: 150,
label: 'node2'
}],
edges: [{
source: 'node1',
target: 'node2',
edgeType: 'type1'
},
{
source: 'node2',
target: 'node1',
edgeType: 'type2'
},
{
source: 'node2',
target: 'node1',
edgeType: 'type3',
edgeOffset: -20
}
]
};
const graph = new G6.Graph({
container: GRAPH_CONTAINER,
width: 500,
height: 500,
modes: {
default: [{
type: 'drag-node',
delegate: false
}]
},
defaultNode: {
style: {
fill: '#DEE9FF',
stroke: '#5B8FF9'
},
labelCfg: {
style: {
fontSize: 12
}
}
},
defaultEdge: {
shape: 'quadratic-label-edge',
}
});
graph.data(data);
graph.render();
到这里为止,我们也就实现了让两个节点之间展示多条边的功能。
G6 是 AntV 团队的图可视化引擎,更多动态请关注我们的 GitHub,求关注,求 Star。