在项目中使用 NPM 包引入
npm install --save @antv/g6
AntV G6 示例图
初始化数据
点击节点动态加入数据
上代码(vue 文件)
<template>
<!-- g6 容器 -->
<!-- 以防数据量过大,加载速度慢,引入 element ui 的 loading -->
<div
id="container"
v-loading="loading"
element-loading-text="加载中..."
element-loading-background="rgba(0, 0, 0, 0.8)"
></div>
</template>
<script>
// 导入 G6
import G6 from '@antv/g6';
// 导入 api 后台数据
import { PostGraph } from "../../api/index";
export default {
data() {
return {
data: {
id:'1',
nood: 0,
direction: 0,
children: [ // 子集
{
id: "11",
nood: 2, // 0:主节点, 1:统计节点, 2:普通节点
direction: 1, // 0:主节点, 1:向左展开, 2:向右展开
isSpread: false, // 有无子集
text1: 'text11111',
text2: "text2222222222",
text3: "text33333333333333333333333333333",
url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
},
{
id: "12",
nood: 2,
direction: 1,
isSpread: false,
text1: 'text11111',
text2: "text2222222222",
text3: "text33333333333333333333333333333",
url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
},
{
id: "13",
nood: 2,
direction: 1,
isSpread: false,
text1: 'text11111',
text2: "text2222222222",
text3: "text33333333333333333333333333333",
url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
},
{
id: "14",
nood: 2,
direction: 1,
isSpread: false,
text1: 'text11111',
text2: "text2222222222",
text3: "text33333333333333333333333333333",
url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
},
{
id: "16",
nood: 2,
direction: 2,
isSpread: true,
text1: 'text11111',
text2: "text2222222222",
text3: "text33333333333333333333333333333",
url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",//标签url
},
]
},
data2: {
id: '2',
nood: 1,
direction: 2,
value: 1111111111,
value1: 2222,
value2: 3333,
children: [
{
id: "21",
nood: 2,
direction: 2,
isSpread: true,
text1: 'text11111',
text2: "text2222222222",
text3: "text33333333333333333333333333333",
url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
},
{
id: "22",
nood: 2,
direction: 2,
isSpread: true,
text1: 'text11111',
text2: "text2222222222",
text3: "text33333333333333333333333333333",
url: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
},
]
},
loading: true,
dataList: {},
dataList2: {},
id: "",
direction: 0,
};
},
created (){
// this.tradeMap();
},
mounted () {
this.tradeMap();
// this.render();
},
methods: {
// 从api获取图谱初始数据
async tradeMap(){
let res = await PostGraph({
id: this.id,
direction: this.direction,
})
if (res.status == "1"){
this.dataList = res.data[0];
this.render();
}
},
// 点击加载的数据
async tradeMap2(){
let res = await PostGraph({
id: this.id,
direction: this.direction,
})
if (res.status == "1"){
this.dataList2 = res.data[0];
}
},
// g6 配置
async render () {
// g6 的实例方法里 this 会变
let _this = this;
// 文本超出隐藏
// fittingString(字段, 最大长度, 字体大小)
const fittingString = (str, maxWidth, fontSize) => {
const ellipsis = '...';
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
// 自定义节点
G6.registerNode(
'tree-node',
{
drawShape: function drawShape(cfg, group) {
const rect = group.addShape('rect', {
attrs: {
x: 0, // x 轴移动距离
y: 0, // y 轴移动距离
width: cfg.nood == 1 ? 170 : 190,// 宽
height: 65,// 高
// fill: '#666', // 背景色
// stroke: '#666',// 边框色
},
name: 'big-rect-shape',
});
// 主节点
if (cfg.nood == 0){
group.addShape('rect', {
attrs: {
x: 0,
y: 0,
},
name: 'source-shape',
});
group.addShape('rect', {
attrs: {
fill: '#eff4ff',
x: 60,
y: 0,
width: 64,
height: 64,
radius: 32,
},
name: 'source-rect-shape',
});
group.addShape('rect', {
attrs: {
fill: '#2d60e0',
x: 78,
y: 18,
width: 28,
height: 28,
radius: 14,
},
name: 'source-rect-shape',
});
group.addShape('text', {
attrs: {
fill: '#fff',
text: '主节点',
x: 63,
y: 90,
},
name: 'source-text-shape',
});
}
// 统计节点
if (cfg.nood == 1){
let operate = '';
if(cfg.value3 > cfg.value2){
operate= '转出'
}else{
operate= '转入'
}
/* 盒子 */
group.addShape('rect', {
attrs: {
fill: '#eff4ff',
x: 5,
y: 18,
width: 170,
height: 30,
radius: 15,
},
name: 'stat-rect-shape',
});
/* 文字 */
group.addShape('text', {
attrs: {
fill: '#000',
text: operate + cfg.value,
x: 30,
y: 40,
},
name: 'stat-text-shape',
});
}
// 普通节点
if (cfg.nood == 2){
/* 盒子 */
group.addShape('rect', {
attrs: {
fill: '#fff',
shadowColor: '#c1c1c0',//阴影颜色
shadowBlur: 10,//阴影范围
x: cfg.direction == '2' ? 5 : 15,
y: 0,
width: 170,
height: 65,
radius: 5,
},
name: 'rect-shape',
});
/* 第一个图标 */
group.addShape('image', {
attrs: {
x: cfg.direction == '2' ? 13 : 23,
y: 8,
height: 14,
width: 14,
img: 'https://gw.alipayobjects.com/zos/basement_prod/300a2523-67e0-4cbf-9d4a-67c077b40395.svg',
},
name: 'state-icon',
});
/* 第二个图标 */
group.addShape('image', {
attrs: {
x: cfg.direction == '2' ? 13 : 23,
y: 30,
height: 14,
width: 14,
img: cfg.url,
},
name: 'copy-icon',
});
/* 第一行字 */
group.addShape('text', {
attrs: {
text: cfg.text1,
x: cfg.direction == '2' ? 35 : 45,
y: 15,
fontSize: 10,
textAlign: 'left',
textBaseline: 'middle',
fill: cfg.direction == '2' ? '#e35e5e' : '#2ead65',
},
name: 'text-shape',
});
/* 第二行字 */
group.addShape('text', {
attrs: {
// fittingString(字段, 最大长度, 字体大小)
text: fittingString(cfg.text2, 130, 12),
x: cfg.direction == '2' ? 35 : 45,
y: 35,
fontSize: 12,
textAlign: 'left',
textBaseline: 'middle',
fill: '#666',
},
name: 'center-text-shape',
});
/* 第三行字 */
group.addShape('text', {
attrs: {
text: fittingString(cfg.text3, 150, 12),
x: cfg.direction == '2' ? 35 : 45,
y: 52,
fontSize: 10,
textAlign: 'left',
textBaseline: 'middle',
fill: '#9aa1b1',
},
name: 'bottom-text-shape',
});
/* 折叠展开 */
if (cfg.isSpread) {
group.addShape('marker', {
attrs: {
x: cfg.direction == '2' ? 182 : 8,
y: 32,
r: 6,
symbol: cfg.collapsed || cfg.isSpread ? G6.Marker.expand : G6.Marker.collapse,
stroke: '#666',
fill: '#fff',
lineWidth: 1,
cursor: 'pointer', // 鼠标变手
},
name: 'collapse-icon',
});
}
}
return rect;
},
// 更新
update: (cfg, item) => {
if(cfg.txhash){
// 简单实现节流
// 防止用户瞎点出现bug
let previous = 0;
let now = Date.now();
if(now - previous > 1000){
previous = now;
const group = item.getContainer();
const icon = group.find((e) => e.get('name') === 'collapse-icon');
icon.attr('symbol', cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse);
}
}
},
},
'single-node',
);
const container = document.getElementById('container');// 获取容器id
const width = container.scrollWidth || 1520; // 宽
const height = container.scrollHeight || 856; // 高
const graph = new G6.TreeGraph({
// 图的 DOM 容器,可以传入该 DOM 的 id 或者直接传入容器的 HTML 节点对象。
container: 'container',
// 指定画布宽度,单位为 'px'
width,
// 指定画布高度,单位为 'px'
height,
// 设置画布的模式
modes: {
default: [
{
// 只适用于树图,展开或收起子树
type: 'collapse-expand',
// onChange:收起或展开的回调函数。警告:V3.1.2 版本中将移除
onChange: function onChange(item, collapsed) {
const data = item.get('model');
graph.updateItem(item, {
collapsed,
});
data.collapsed = collapsed;
return true;
},
},
// 拖拽画布;
'drag-canvas',
// 缩放画布;
'zoom-canvas',
],
},
// 默认状态下节点的配置,比如 type, size, color,会被写入的 data 覆盖
defaultNode: {
// 节点类型,这里是自定义节点,自己取名
type: 'tree-node',
// 指定边连入节点的连接点的位置(相对于该节点而言),可以为空
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
// 默认状态下边的配置
defaultEdge: {
// 指定边的类型,可以是内置边的类型名称,也可以是自定义边的名称。默认为 'line'
type: 'cubic-horizontal',
// 边的样式属性
style: {
// 边的颜色
stroke: '#A3B1BF',
// 开始箭头
startArrow: {
path: G6.Arrow.circle(2, 2), // 内置样式
fill: '#A3B1BF', // 颜色
d: 4, // 偏移距离
},
// 结束箭头
endArrow: {
path: 'M 0,0 L 5,4 L 5,-4 Z', // 自定义样式
fill: '#A3B1BF',
d: 3,
},
},
},
// 布局配置项,使用 type 字段指定使用的布局方式
layout: {
// 布局名称
type: 'compactBox',
// layout 的方向 H / V / LR / RL / TB / BT
// TB —— 根节点在上,往下布局
// BT —— 根节点在下,往上布局
// LR —— 根节点在左,往右布局
// RL —— 根节点在右,往左布局
// H —— 根节点在中间,水平对称布局
// V —— 根节点在中间,垂直对称布局
direction: 'H',
// 判断节点向左还是向右
getSide: (d) => {
if (d.data.direction == '2') {
return 'right';
}
return 'left';
},
// 节点 id 的回调函数
getId: function getId(d) {
return d.id;
},
// 节点高度的回调函数
getHeight: function getHeight() {
return 40;
},
// 节点宽度的回调函数
getWidth: function getWidth() {
return 20;
},
// 节点纵向间距的回调函数
getVGap: function getVGap() {
return 18;
},
// 节点横向间距的回调函数
getHGap: function getHGap() {
return 100;
},
},
// 动画
animate: true,
});
// 初始化的图数据
graph.data(this.data);
// graph.data(this.dataList);
// 根据提供的数据渲染视图。
graph.render();
// 缩放视窗窗口到一个固定比例
graph.zoomTo(1);
// 平移图到中心将对齐到画布中心,但不缩放
graph.fitCenter();
// 点击动态加入节点
// 因为数据量过大,在用户点击展开时才请求获取数据
// 数据源里要有唯一字段 id ,不加 id g6会自动生成id,会有一些bug
graph.on('node:click', async (e) => {
const item = e.item;
const nodeId = item.get('id');
const model = item.getModel();
const children = model.children;
// 没有子集加入数据
if (!children && model.isSpread){
_this.txhash= model.txhash;
_this.direction= model.direction;
await _this.tradeMap2();
// 不加加定时器获取不到数据
setTimeout(() =>{
graph.addChild(_this.data2, nodeId);
// graph.addChild(_this.dataList2, nodeId);
},100)
}
});
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};
// 数据加载完成关闭 element ui 的 loading
this.loading = false;
},
},
};
</script>
<style scoped>
#container{
width: 1520px;
height: 856px;
}
</style>
AntV 官网
https://antv.gitee.io/zh
点击查看官网(G6)