实现效果如下图:
API文档:图配置 G6.Graph(cfg) | G6
参考样例:自定义树图 | G6
坐标转换:坐标转换
一、安装 & 引用
1. 通过npm包引入
npm install --save @antv/g6
在需要使用的文件中直接引入G6即可
import G6 from "@antv/g6"
2. 通过CDN形式引入
// version <= 3.2
<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-{$version}/build/g6.js"></script>
// version >= 3.3
<script src="https://gw.alipayobjects.com/os/lib/antv/g6/{$version}/dist/g6.min.js"></script>
二、数据结构如下:
const mockData = {
id: 'g1',
name: '我是科目我是科目我是科目我是科目我是科目我是科目目目目目',
dataType: 'root',
amount: 123123213123.123123,
children: [
{
id: 'g12',
name: 'Deal with LONG label LONG label LONG label LONG label',
amount: 123123213123.123123,
children: [
{
id: 'g121',
name: 'Name3',
collapsed: true,
amount: 123123213123.123123,
children: [
{
id: 'g1211',
name: 'Name4',
amount: 123123213123.123123,
children: [],
},
],
},
{
id: 'g122',
name: 'Name5',
collapsed: true,
amount: 2.34,
children: [
{
id: 'g1221',
name: 'Name6',
amount: 0.123123,
children: [
{
id: 'g12211',
name: 'Name6-1',
amount: 123123,
children: [],
},
],
},
{
id: 'g1222',
name: 'Name7',
amount: 123123213123.123123,
children: [],
},
],
},
{
id: 'g123',
name: 'Name8',
collapsed: true,
amount: 12,
children: [
{
id: 'g1231',
name: 'Name8-1',
amount: 123123213123.123123,
children: [],
},
],
},
],
},
{
id: 'g13',
name: 'Name9',
amount: 123123213123.123123,
children: [
{
id: 'g131',
name: 'Name10',
amount: 12313123.123123,
children: [],
},
{
id: 'g132',
name: 'Name11',
amount: 123123213123.123123,
children: [],
},
],
},
{
id: 'g14',
name: 'Name12',
amount: 123123213123.123123,
children: [],
},
],
};
完整代码如下:
<template>
<div id="container"></div>
</template>
<script>
import G6 from '@antv/g6';
export default {
name: 'HelloWorld',
props: {
msg: String
},
created() { },
mounted() {
this.initAntv();
},
methods: {
// 初始化
initAntv() {
// 组件props
const props = {
data: mockData,
config: {
padding: [20, 50],
// defaultLevel: 3,
defaultZoom: 0.8,
modes: { default: ['zoom-canvas', 'drag-canvas'] },
},
};
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
// 默认配置
const defaultConfig = {
width,
height,
modes: {
default: ['zoom-canvas', 'drag-canvas'],
},
fitView: true,
animate: true,
minZoom: 0.5,
maxZoom: 1.5,
linkCenter: false,
defaultNode: { // 默认节点
type: 'flow-rect',
},
defaultEdge: { // 默认边
type: 'cubic-horizontal',
style: {
stroke: 'rgb(93, 162, 216)',
},
},
layout: { // layout 布局配置
type: 'mindmap',
direction: 'V',
dropCap: false,
rankSep: 350,
nodeSep: 100,
getHeight: () => {
return 60;
},
// 节点横向间距的回调函数
getHGap: function getHGap() {
return 150;
}
},
};
// 自定义节点
const registerFn = () => {
/**
* 自定义节点
*/
G6.registerNode(
'flow-rect',
{
shapeType: 'flow-rect',
draw(cfg, group) {
const {
name = '',
collapsed,
dataType,
amount,
} = cfg;
const config = {
basicColor: 'rgb(66, 151, 215)', // 左边粗线的颜色
fontColor: 'rgb(74, 177, 241)', // 字体颜色
borderColor: 'rgb(169, 217, 252)', // 边框颜色
bgColor: 'rgb(230, 240, 250)', // 矩形背景色
};
const rectConfig = {
width: 243,
height: 64,
lineWidth: 1,
fontSize: 12,
opacity: 1,
fill: config.bgColor,
stroke: config.borderColor,
radius: 2,
cursor: 'pointer',
};
const nodeOrigin = {
x: -rectConfig.width / 2,
y: -rectConfig.height / 2,
};
const textConfig = {
textAlign: 'left',
textBaseline: 'bottom',
};
if (dataType != 'root') {
/* 左边的小圆点 */
group.addShape('circle', {
attrs: {
x: nodeOrigin.x - 0,
y: 0,
r: 6,
fill: config.basicColor,
},
name: 'left-dot-shape',
});
}
// 外层矩形
const rect = group.addShape('rect', {
attrs: {
x: nodeOrigin.x,
y: nodeOrigin.y,
...rectConfig,
},
});
/* 左边的粗线 */
group.addShape('rect', {
attrs: {
x: nodeOrigin.x,
y: nodeOrigin.y,
width: 3,
height: rectConfig.height,
fill: config.basicColor,
radius: 1.5,
},
name: 'left-border-shape',
});
const rectBBox = rect.getBBox();
// 科目
group.addShape('text', {
attrs: {
...textConfig,
x: 12 + nodeOrigin.x,
y: 20 + nodeOrigin.y,
text: name.length > 16 ? name.substr(0, 16) + '...' : name,
fontSize: 12,
opacity: 0.85,
fill: config.fontColor,
cursor: 'pointer',
},
name: 'name-shape',
});
if (amount) {
// 金额-矩形
const amountRect = group.addShape('rect', {
attrs: {
fill: '#fff',
radius: 2,
stroke: config.borderColor,
cursor: 'pointer', // 需要调整
},
name: 'amount-container-shape',
})
// 金额
const amountText = group.addShape('text', {
attrs: {
...textConfig,
x: rectBBox.maxX - 10,
y: rectBBox.maxY - 10,
text: `${amount}`,
fontSize: 12,
textAlign: 'right',
fill: '#000',
},
name: 'amount-text-shape',
});
const amountBBox = amountText.getBBox(); // 获取元素的包围盒
/* amountBBox */
amountRect.attr({
x: rectBBox.maxX - amountBBox.width - 15,
y: rectBBox.maxY - 27,
width: amountBBox.width + 10,
height: amountBBox.height + 10,
});
}
// collapse rect
if (cfg.children && cfg.children.length) {
group.addShape('circle', {
attrs: {
x: rectConfig.width / 2 + 8.5,
y: 0,
r: 8,
width: 16,
height: 16,
stroke: 'rgb(74, 177, 241)',
cursor: 'pointer',
fill: '#fff',
},
name: 'collapse-back',
modelId: cfg.id,
});
// collpase text
group.addShape('text', {
attrs: {
x: rectConfig.width / 2 + 8.5,
y: 1,
textAlign: 'center',
textBaseline: 'middle',
text: collapsed ? '+' : '-',
fontSize: 16,
cursor: 'pointer',
fill: 'rgb(74, 177, 241)',
},
name: 'collapse-text',
modelId: cfg.id,
});
}
return rect;
},
update(item) {
// const { level, name } = cfg;
// console.log("update生命周期函数中的cfg========", cfg)
const group = item.getContainer();
let mask = group.find(ele => ele.get('name') === 'mask-shape');
let maskLabel = group.find(ele => ele.get('name') === 'mask-label-shape');
group.get('children').forEach(child => {
if (child.get('name')?.includes('collapse')) return;
child.show();
})
mask?.animate({ opacity: 0 }, {
duration: 200,
callback: () => mask.hide()
});
maskLabel?.animate({ opacity: 0 }, {
duration: 200,
callback: () => maskLabel.hide()
});
},
setState(name, value, item) {
if (name === 'collapse') {
const group = item.getContainer();
const collapseText = group.find((e) => e.get('name') === 'collapse-text');
if (collapseText) {
if (!value) {
collapseText.attr({
text: '-',
});
} else {
collapseText.attr({
text: '+',
});
}
}
}
},
getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
];
},
},
'rect',
);
};
registerFn();
const { data } = props;
let graph = null;
const initGraph = (data) => {
if (!data) {
return;
}
const { onInit, config } = props;
const tooltip = new G6.Tooltip({
// offsetX and offsetY include the padding of the parent container
offsetX: 20,
offsetY: 30,
// 允许出现 tooltip 的 item 类型
itemTypes: ['node'],
// custom the tooltip's content
// 自定义 tooltip 内容
getContent: (e) => {
const outDiv = document.createElement('div');
outDiv.style.textAlign = 'left';
const nodeName = e.item.getModel().name;
let formatedNodeName = '';
for (let i = 0; i < nodeName.length; i++) {
formatedNodeName = `${formatedNodeName}${nodeName[i]}`;
if (i !== 0 && i % 20 === 0) formatedNodeName = `${formatedNodeName}<br/>`;
}
outDiv.innerHTML = `${formatedNodeName}`;
return outDiv;
},
shouldBegin: (e) => {
// if (e.target.get('name') === 'name-shape' || e.target.get('name') === 'mask-label-shape') return true;
const nodeName = e.item.getModel().name;
if (nodeName.length > 24) {
return true
} else {
return false;
}
},
});
graph = new G6.TreeGraph({
container: 'container',
...defaultConfig,
...config,
plugins: [tooltip],
});
if (typeof onInit === 'function') {
onInit(graph);
}
graph.data(data);
graph.render();
const handleCollapse = (e) => {
const target = e.target;
const id = target.get('modelId');
const item = graph.findById(id);
const nodeModel = item.getModel();
nodeModel.collapsed = !nodeModel.collapsed;
graph.layout();
graph.setItemState(item, 'collapse', nodeModel.collapsed);
};
graph.on('collapse-text:click', (e) => {
handleCollapse(e);
});
graph.on('collapse-back:click', (e) => {
handleCollapse(e);
});
};
initGraph(data);
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);
};
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
问题:
当我们按照以上操作绘制完图之后,发现画布中的内容并非居中展示,设置 fitView: true 并没有起作用,有可能是因为布局原因,有定位的设置可能会影响画布的中心坐标,有了解决问题的思路 ,我们就开始着手试试把。增加以下代码:
// 设置画布居中
setCenetr(graph) {
const con_width = document.getElementById("container").clientWidth;
const con_Height = document.getElementById("container").clientHeight;
// 重新计算画布中心的坐标
const centerX = con_width / 2 - 60;
const centerY = con_Height / 2 - 60;
// 以 x, y 为坐标中心,进行缩放
graph.zoomTo(0.7, {
x: centerX,
y: centerY
})
}
以上方法需在 graph.render() 之前执行。
友情提示:新手小白入门的话最好是先找个和自己需求相似案例看看,然后开始照猫画虎,画着画着慢慢就能就看明白了,当然 大佬略过哈 ~ 多看看官方的文档,写的很详细。