G6介绍
AntV 是蚂蚁金服全新一代数据可视化解决方案,致力于提供一套简单 方便、专业可靠、无限可能的数据可视化最佳实践。
G6 是一个简单、易用、完备的图可视化引擎,它在高定制能力的基础上,提供了一系列设计优雅、便于使用的图可视化解决方案。能帮助开发者搭建属于自己的图可视化、图分析、或图编辑器应用。
创建树图实例TreeGraph
TreeGraph 是 G6 专门为树图场景打造的图,所有的 G6 节点实例操作以及事件,行为监听都在 TreeGraph 实例上进行。TreeGraph 的初始化通过 new 进行实例化,实例化时需要传入需要的参数。
js
const graph = new G6.TreeGraph({
container: 'container', //图的 DOM 容器,可以传入该 DOM 的 id 或者直接传入容器的 HTML 节点对象。
width: 500, //单位为 'px'
height: 500,
defaultNode: {
type: "card-node",//自定义节点名
size: [100, 40],
},
defaultEdge: {
type: "cubic-horizontal",//内置边
style: {
stroke: "gray",
},
modes: {
default: ["zoom-canvas",{type: "drag-canvas",allowDragOnItem:true}],
edit: ['click-select']
},
layout: {
type: 'dendrogram',
direction: 'LR', // H / V / LR / RL / TB / BT
indent:20, //列间间距
nodeSep: 50,
rankSep: 100,
radial: true,
},
});
graph.data(data);//添加数据
graph.render();//渲染
graph.fitView();//让画布内容适应视口
graph.on("node:click", (e) => {
//当点击node时的回调函数
});
自定义节点
当内置节点或内置边不满足需求时,可以通过 G6.registerNode(nodeName, options, extendedNodeName) 方法自定义节点,使用registerEdge(edgeName, options, extendedEdgeName)自定义边,Combo使用G6.registerCombo(comboName, options, extendedComboName)
js
G6.registerNode(
'nodeName',
{
/**
* 绘制节点和边,包括节点和边上的文本,返回图形的 keyShape
* @param {Object} cfg 节点的配置项
* @param {G.Group} group 图形分组,节点中的图形对象的容器
* @return {G.Shape} 绘制的图形,通过 node.get('keyShape') 可以获取到
*/
draw(cfg, group) {},
/**
* 绘制完成以后的操作,用户可继承现有的节点或边,在 afterDraw() 方法中扩展图形或添加动画。
*/
afterDraw(cfg, group) {},
/**
* 更新节点或边,包括节点或边上的文本,重写后在更新时将会调用该方法替代 draw,起到性能优化的作用
* @override
* @param {Node} node 节点
*/
update(cfg, node) {},
/**
* 更新完以后的操作,如扩展图形或添加动画。,一般同 afterDraw 配合使用
* @param {Node} node 节点
*/
afterUpdate(cfg, node) {},
/**
* 用于响应外部对元素状态的改变。当外部调用 graph.setItemState(item, state, value) 时,该函数作出相关响应,主要是交互状态,业务状态请在 draw 方法中实现
* 单图形的节点仅考虑 selected、active 状态,有其他状态需求的用户自己复写这个方法
* @param {String} name 状态名称
* @param {Object} value 状态值
* @param {Node} node 节点
*/
setState(name, value, node) {
}},
/**
* 获取锚点(相关边的连入点)
* @param {Object} cfg 节点的配置项
* @return {Array|null} 锚点(相关边的连入点)的数组,如果为 null,则没有锚点
*/
getAnchorPoints(cfg) {},
},
'extendedNodeName',//可以继承内置节点进行扩展
);
图中的元素由多个图形(Shape)组成,在draw中,通过group.addShape()来添加图形。可选择的图形有:circle:圆、rect:矩形、ellipse:椭圆、polygon:多边形、image:图片、marker:标记、path:路径、text:文本、dom(svg):DOM(图渲染方式 renderer 为 'svg' 时可用)。
用x、y等属性控制位置((0, 0) 是该节点的中心),width、height等调整大小。
js
group.addShape('rect', {
attrs: {
x: 150,
y: 150,
width: 150,
height: 150,
stroke: 'black',
radius: [2, 4],
},
// must be assigned in G6 3.3 and later versions. it can be any value you want
name: 'rect-shape',
});
对于过长的内容,文本会超出外框:
可以通过使用 JS 来计算文本长度,也可以通过使用 \n 来进行换行或添加省略符号。G6 提供了 Util.getLetterWidth 与 Util.getTextSize 辅助计算文本长度,但仅适用于默认字体。
js
//计算节点宽度,改变外框大小
const getMaxWidth = (strList = [], fontSize = 14) => {
let maxWidth = 100;
const pattern = new RegExp("[\u4E00-\u9FA5]+");
if (strList.length) {
strList.map((str) => {
let currentWidth = 0;
str.split("").forEach((letter, i) => {
if (pattern.test(letter)) {
currentWidth += fontSize;
} else {
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
});
if (currentWidth > maxWidth) {
maxWidth = currentWidth;
}
});
}
return maxWidth;
};
//超出时添加省略符号
const getMaxWidth = (str , fontSize = 14) => {
const maxWidth=100
const ellipsis = '...';
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+');
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
currentWidth += fontSize;
} else {
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
modes
modes用于管理图的交互,比如拖拽画布、放大缩小、节点点击、hover等行为
可以设置多个模式,在特定的情况下使用graph.setMode('edit')来切换使用的模式,同样的,可以使用graph.addBehaviors('drag-canvas', 'default')和graph.removeBehaviors('drag-canvas', 'edit')来增加或移除Behavior
G6的内置交互可以处理较为常见的情况:内置Behavior
对于内置交互不能满足条件的情况,也可以使用自定义交互 registerBehavior来定制化开发:自定义交互 Behavior
js
G6.registerBehavior('activate-node', {
getDefaultCfg() {
return {
multiple: true
};
},//定义自定义 Behavior 时的默认参数,会与用户传入的参数进行合并。
getEvents() {
return {
'node:click': 'onNodeClick',
'canvas:click': 'onCanvasClick'
};
}
onNodeClick(e) {
const graph = this.graph;
const item = e.item;
//通过 hasState 方法判断元素的某种状态是否是激活态,从而判断是否应该激活另一个状态
if (item.hasState('active')) {
graph.setItemState(item, 'active', false);
return;
}
// this 上即可取到配置,如果不允许多个 'active',先取消其他节点的 'active' 状态
if (!this.multiple) {
this.removeNodesState();
}
// 置点击的节点状态 'active' 为 true
graph.setItemState(item, 'active', true);
},
onCanvasClick(e) {
// shouldUpdate 可以由用户复写,返回 true 时取消所有节点的 'active' 状态,即将 'active' 状态置为 false
if (this.shouldUpdate(e)) {
removeNodesState();
}
},
removeNodesState() {
graph.findAllByState('node', 'active').forEach(node => {
graph.setItemState(node, 'active', false);
});
}
});
Behavior 默认包含 shouldBegin,shouldUpdate,shouldEnd 三个回调,代表是否开始行为,是否更新元素,是否进行结束行为,当返回值为 false 时阻止默认行为。
getEvents返回该 Behavior 所需监听事件的对象其中标识了事件和回调函数的对应关系,可用事件:交互事件,在回调函数中自行处理逻辑;
如果需要改变样式,可以通过graph.setItemState(item, state, value)改变节点的状态,不同的状态对应不同的样式,而各状态所对应的样式可以在三个地方配置:
- 在实例化 Graph 时,通过 nodeStateStyles 和 edgeStateStyles 对象定义(对于自定义节点不生效,参考第三条);
- 在节点/边数据中,在 stateStyles 对象中定义状态(默认为styles),使用这种方式可以为不同的节点/边分别配置不同的 state 样式;
- 在自定义节点/边时,在registerNode的setState中定义不同状态的样式时的更改。
js
//内置节点
nodeStateStyles: {
// 二值状态 hover 为 true 时的样式
hover: {
// keyShape 的状态样式
fill: '#d3adf7',
// name 为 node-label 的子图形在该状态值下的样式
'node-label': {
fontSize: 15
},
},
}
//自定义节点
setState(name, value, node) {
const group = item.getContainer();
const shape = group.get('children')[0]; // 需要更改的图形,顺序根据 draw 时确定
if (name === 'active') {
if (value) {
shape.attr('fill', 'red');
} else {
shape.attr('fill', 'white');
}
}},
注意:后设置的状态(通过 graph.setItemState)优先级高于前者,以此来控制各状态样式冲突时的显示
layout
布局配置项,使用 type 字段指定使用的布局方式,树图的type 可取以下值:
CompactBox 紧凑树布局 Dendrogram 生态树布局 Indented 缩进树布局 Mindmap 脑图树布局
注:树图不支持自定义布局
绑定事件
graph.on(eventName, handler) 来进行交互事件的监听
graph.emit(eventName, params) 手动触发某个事件
graph.off(eventName, handler) 为图解除指定的事件监听
graph.off(eventName) 为图解除某事件的所有监听
graph.off() 为图解除所有监听
数据更新
树图的数据一般是嵌套结构,边的数据隐含在嵌套结构中,并不会特意指定 edge 。此布局要求数据中一个节点需要有 id 和 children 两个数据项
js
const data = {
id: 'root',
children: [
{
id: 'subTree1',
children: [...]
},
{
id: 'subTree2',
children: [...]
}
]
};
树图的子树更新可以使用addChild(childData, parentNode/parentId)、updateChild(childData, parentId)、updateChildren(childData, parentId)、removeChild(id)
js
const data = {
id: 'sub1',
children: [
{
id: 'subTree1',
children: [...]
},
{
id: 'subTree2',
children: [...]
}
]
};
treeGraph.addChild(data, 'root')
更新子树的数据并不会触发父节点的更新,因此如果需要同时更改父节点的设置,可以graph.refreshItem(item)刷新父节点或graph.setItemState更改父节点的对应状态
例:需要让图中只有叶子结点有操作菜单
注:将会直接使用 data 对象作为新增节点/边的数据模型,G6 内部可能会对其增加或修改一些必要的字段。若不希望原始参数被修改,建议在使用深拷贝后的 data。
插件Plugins
使用方法:
js
// 实例化 Grid 插件
const grid = new G6.Grid();
const minimap = new G6.Minimap();
const graph = new G6.Graph({
//... 其他配置项
plugins: [grid, minimap], // 配置 Grid 插件和 Minimap 插件
});
此外,类似menu、legend的功能可以通过自定义组件完成,需要注意的是使用不同的坐标系:clientX,clientY原点是浏览器的左上角,canvasX、canvasY、pointX、pointY的原点为画布左上角,同时图的缩放、平移其实是整个 pointX/pointY 坐标系的缩放和平移。
js
//悬浮 DOM 挂载在 body 上
document.body.appendChild(floatDOM);
graph.on('canvas:click', event => {
floatDOM.style.left = event.clientX;
floatDOM.style.top = event.clientY;
})
//悬浮 DOM 挂载在 Container DOM 上
container.appendChild(floatDOM);
graph.on('canvas:click', event => {
floatDOM.style.marginLeft = event.canvasX;
floatDOM.style.marginTop = event.canvasY;
});