ant G6 + element-ui 根据el-tree点击的子返回所有的父级数据进行展示
效果图
Video_2024-02-20_140319
业务分析
1.需要根据左边tree数据更新右边 ant G6
2.每次点击节点,需要获取所有父节点包括自己
3.难点就在于怎么去把数据处理成我们需要的
处理数据代码
findNewTree(treeData, targetNode, newTree = {}) {
let t = treeData
if (!targetNode?.parentid) {
return targetNode
}
// 里面定义一个函数用来递归
function getParentsTree(treeData, targetNode,) {
for (const item of treeData) {
// 判断点击的id是否相同
if (item.id === targetNode.parentid) {
// 直接让父级只有自己一个儿子
item.children = [targetNode]
// 把数据赋值方便返回
newTree = item
// 判断父亲有无父亲
if (item.parentid) {
// 有就找父亲的父亲
return getParentsTree(t, newTree)
} else {
// 没有说明到第一级了 直接返回
return newTree
}
}
//递归
if (item.children && item.children.length > 0) {
return getParentsTree(item.children, targetNode)
}
}
}
getParentsTree(treeData, targetNode, {})
// 返回父节点数组
return newTree;
},
全部代码
// 左侧树代码
<template>
<div id="elTree">
<el-tree :data="data" ref="tree" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
data() {
return {
data: [
{
id: 'root',
label: '利息收入',
subLabel: '3,283.456',
ratio: 3,
children: [
{
id: 'child-a',
parentid: 'root',
label: '平均利息',
subLabel: '9%',
ratio: 1,
increase: true,
},
{ //情况1
id: 'child-b',
parentid: 'root',
label: '贷款余额',
subLabel: '1,789,567',
ratio: 23,
increase: true,
children: [
{
id: 'child-b-a',
parentid: 'child-b',
label: '投放金额111111111111',
subLabel: '2,385,124',
ratio: 17,
increase: true,
operator: '-',
children: [
{
id: 'child-b-a-2',
parentid: 'child-b-a',
label: '投放金额222',
subLabel: '2,385,124',
ratio: 17,
increase: true,
operator: '-',
}, {
id: 'child-b-b-1',
parentid: 'child-b-a',
label: '投放金额222',
subLabel: '595,557',
ratio: 12,
increase: true,
}
]
}, {
id: 'child-b-b2314',
parentid: 'child-b',
label: '还款金额',
subLabel: '595,557',
ratio: 12,
increase: true,
}
]
},
]
},
{
id: 'root',
label: '利息收入',
subLabel: '3,283.456',
ratio: 3,
children: [
{
id: 'child-a',
parentid: 'root',
label: '平均利息',
subLabel: '9%',
ratio: 1,
increase: true,
},
{ //情况1
id: 'child-b',
parentid: 'root',
label: '贷款余额',
subLabel: '1,789,567',
ratio: 23,
increase: true,
children: [
{
id: 'child-b-a',
parentid: 'child-b',
label: '投放金额111111111111',
subLabel: '2,385,124',
ratio: 17,
increase: true,
operator: '-',
children: [
{
id: 'child-b-a-2',
parentid: 'child-b-a',
label: '投放金额222',
subLabel: '2,385,124',
ratio: 17,
increase: true,
operator: '-',
}, {
id: 'child-b-b-1',
parentid: 'child-b-a',
label: '投放金额222',
subLabel: '595,557',
ratio: 12,
increase: true,
}
]
}, {
id: 'child-b-b2314',
parentid: 'child-b',
label: '还款金额',
subLabel: '595,557',
ratio: 12,
increase: true,
}
]
},
]
},
],
defaultProps: {
children: 'children',
label: 'label'
},
};
},
mounted() {
},
methods: {
...mapMutations("decision", ["setData", "getData"]),
handleNodeClick(node) {
const data = JSON.parse(JSON.stringify(this.data))
const newData = this.findNewTree(data, node)
this.setData(newData)
},
findNewTree(treeData, targetNode, newTree = {}) {
let t = treeData
if (!targetNode?.parentid) {
return targetNode
}
// 里面定义一个函数用来递归
function getParentsTree(treeData, targetNode,) {
for (const item of treeData) {
// 判断点击的id是否相同
if (item.id === targetNode.parentid) {
// 直接让父级只有自己一个儿子
item.children = [targetNode]
// 把数据赋值方便返回
newTree = item
// 判断父亲有无父亲
if (item.parentid) {
// 有就找父亲的父亲
return getParentsTree(t, newTree)
} else {
// 没有说明到第一级了 直接返回
return newTree
}
}
//递归
if (item.children && item.children.length > 0) {
return getParentsTree(item.children, targetNode)
}
}
}
getParentsTree(treeData, targetNode, {})
// 返回父节点数组
return newTree;
},
},
};
</script>
<style scoped>
#elTree {
width: 400px;
border: 1px solid green;
height: 100%;
}
</style>
// 右侧ant g6代码
<template>
<div id="antTree">
</div>
</template>
<script>
import G6 from '@antv/g6';
export default {
data() {
return {
graph: null,
g6Data: {
id: 'root',
label: '利息收入',
subLabel: '3,283.456',
ratio: 3,
children: [{
id: 'child-a',
label: '平均利息',
subLabel: '9%',
ratio: 1,
increase: true,
}, {
id: 'child-b',
label: '贷款余额',
subLabel: '1,789,567',
ratio: 23,
increase: true,
children: [{
id: 'child-b-a',
label: '投放金额',
subLabel: '2,385,124',
ratio: 17,
increase: true,
operator: '-',
}, {
id: 'child-b-b',
label: '还款金额',
subLabel: '595,557',
ratio: 12,
increase: true,
}
]
}, {
id: 'child-c',
label: '还款期限',
subLabel: '7',
ratio: 23,
increase: false,
}
]
}
}
},
mounted() {
this.init()
},
methods: {
init() {
if (this.graph) {
this.graph.destroy();
}
// root node
G6.registerNode('root', {
draw: (cfg, group) => {
const size = [80, 30];
const keyShape = group.addShape('rect', {
attrs: {
width: size[0],
height: size[1],
x: -size[0] / 2,
y: -size[1] / 2,
fill: 'rgb(19, 33, 92)',
radius: 5
},
draggable: true,
name: 'root-keyshape'
});
group.addShape('text', {
attrs: {
text: `${cfg.ratio}%`,
fill: 'rgba(255, 255, 255, 0.85)',
fontSize: 6,
x: 10 - size[0] / 2,
y: 3,
},
draggable: true,
name: 'ratio-shape'
});
group.addShape('text', {
attrs: {
text: `${cfg.label}`,
fill: 'rgba(255, 255, 255, 0.85)',
fontSize: 9,
x: -6,
y: 0,
},
draggable: true,
name: 'label-shape'
});
group.addShape('line', {
attrs: {
x1: -6,
x2: 35,
y1: 2,
y2: 2,
stroke: 'rgba(255, 255, 255, 0.85)',
lineWidth: 0.5
},
draggable: true,
name: 'divider-shape'
});
group.addShape('text', {
attrs: {
text: `${cfg.subLabel}`,
fill: 'rgba(255, 255, 255, 0.65)',
fontSize: 6,
x: -6,
y: 10,
},
draggable: true,
name: 'sublabel-shape'
});
return keyShape;
}
});
// level1 node
G6.registerNode('level1node', {
draw: (cfg, group) => {
const size = [60, 40]
const keyShape = group.addShape('rect', {
attrs: {
width: size[0],
height: size[1],
x: -size[0] / 2,
y: -size[1] / 2,
fill: 'rgb(213, 225, 247)',
radius: 5
},
draggable: true,
name: 'level1node-keyshape'
});
group.addShape('text', {
attrs: {
text: `${cfg.label}`,
fill: 'rgba(19, 33, 92, 0.65)',
fontSize: 6,
x: 0,
y: -6,
textAlign: 'center'
},
draggable: true,
name: 'label-shape'
});
group.addShape('text', {
attrs: {
text: `${cfg.subLabel}`,
fill: 'rgba(19, 33, 92, 0.65)',
fontSize: 8,
x: 0,
y: 6,
fontWeight: 800,
textAlign: 'center'
},
draggable: true,
name: 'sublabel-shape'
});
group.addShape('rect', {
attrs: {
x: -12,
y: 8,
width: 25,
height: 8,
radius: 4,
fill: cfg.increase ? 'rgb(127, 193, 193)' : 'rgb(220, 124, 125)'
},
draggable: true,
name: 'ratio-box',
})
group.addShape('text', {
attrs: {
text: `${cfg.ratio}%`,
fill: 'rgba(255, 255, 255, 0.85)',
fontSize: 6,
x: 0,
y: 9,
textAlign: 'center',
textBaseline: 'top'
},
draggable: true,
name: 'ratio-shape'
});
// edge end
group.addShape('line', {
attrs: {
x1: -size[0] / 2,
x2: -size[0] / 2 + 6,
y1: 0,
y2: 0,
lineWidth: 1,
stroke: 'rgb(19, 33, 92)',
}
});
group.addShape('circle', {
attrs: {
r: 2,
x: -size[0] / 2 + 6,
y: 0,
fill: 'rgb(19, 33, 92)',
}
})
return keyShape;
},
update: undefined,
}, 'rect')
// other node
G6.registerNode('othernode', {
draw: (cfg, group) => {
const size = [100, 30];
const keyShape = group.addShape('rect', {
attrs: {
width: size[0],
height: size[1],
x: -size[0] / 2,
y: -size[1] / 2,
fill: 'rgb(213, 225, 247)',
radius: 5
},
draggable: true,
name: 'level1node-keyshape'
});
group.addShape('text', {
attrs: {
text: `${cfg.label}`,
fill: 'rgba(19, 33, 92, 0.65)',
fontSize: 6,
x: 10 - size[0] / 2,
y: -2,
textAlign: 'left'
},
draggable: true,
name: 'label-shape'
});
group.addShape('text', {
attrs: {
text: `${cfg.subLabel}`,
fill: 'rgba(19, 33, 92, 0.65)',
fontSize: 8,
fontWeight: 800,
x: 10 - size[0] / 2,
y: 8,
textAlign: 'left'
},
draggable: true,
name: 'sublabel-shape'
});
group.addShape('rect', {
attrs: {
x: 12,
y: -4,
width: 25,
height: 8,
radius: 4,
fill: cfg.increase ? 'rgb(127, 193, 193)' : 'rgb(220, 124, 125)'
},
draggable: true,
name: 'ratio-box'
})
group.addShape('text', {
attrs: {
text: `${cfg.ratio}%`,
fill: 'rgba(255, 255, 255, 0.85)',
fontSize: 6,
x: 18,
y: -3,
textAlign: 'left',
textBaseline: 'top'
},
draggable: true,
name: 'ratio-shape'
});
if (cfg.operator) {
group.addShape('rect', {
attrs: {
x: -8,
y: 27,
width: 16,
height: 16,
lineWidth: 1,
stroke: '#aaa',
lineDash: [2, 1],
opacity: 0
},
name: 'operator-box'
});
group.addShape('circle', {
attrs: {
r: 6,
x: 0,
y: 35,
fill: 'rgba(240, 90, 109, 0.15)'
},
name: 'operator-circle'
});
group.addShape('text', {
attrs: {
text: cfg.operator,
x: 0,
y: 34,
fontSize: 12,
fill: 'rgba(240, 90, 109, 0.85)',
textAlign: 'center',
textBaseline: 'middle'
},
name: 'operator-symbol'
});
}
// edge end
group.addShape('line', {
attrs: {
x1: -size[0] / 2,
x2: -size[0] / 2 + 6,
y1: 0,
y2: 0,
lineWidth: 1,
stroke: 'rgb(19, 33, 92)',
}
});
group.addShape('circle', {
attrs: {
r: 2,
x: -size[0] / 2 + 6,
y: 0,
fill: 'rgb(19, 33, 92)',
}
})
return keyShape;
},
update: undefined
}, 'rect')
// edge
G6.registerEdge('round-poly', {
getControlPoints: (cfg) => {
const { startPoint, endPoint } = cfg;
return [
startPoint,
{
x: startPoint.x,
y: endPoint.y
},
endPoint
];
}
}, 'polyline')
G6.Util.traverseTree(this.g6Data, subtree => {
if (subtree.level === undefined) subtree.level = 0;
subtree.children?.forEach(child => child.level = subtree.level + 1);
switch (subtree.level) {
case 0:
subtree.type = 'root';
break;
case 1:
subtree.type = 'level1node';
break;
default:
subtree.type = 'othernode';
}
});
const antTree = document.getElementById('antTree')
const graph = new G6.TreeGraph({
container: antTree,
width: document.getElementById("antTree").clientWidth,
height: document.getElementById("antTree").clientHeight,
fitView: true,
layout: {
type: 'compactBox',
direction: 'LR',
getHGap: function getVGap() {
return 5;
},
},
defaultEdge: {
type: 'round-poly',
sourceAnchor: 0,
targetAnchor: 1,
style: {
radius: 8,
stroke: 'rgb(19, 33, 92)'
}
},
defaultNode: {
anchorPoints: [
[0.9, 0.5],
[0, 0.5]
]
},
nodeStateStyles: {
hover: {
fill: '#fff',
shadowBlur: 30,
shadowColor: '#ddd',
},
operatorhover: {
'operator-box': {
opacity: 1
}
}
},
modes: {
default: ['zoom-canvas', 'drag-canvas', 'collapse-expand']
}
});
this.graph = graph;
graph.on('node:mouseenter', e => {
if (e.target.get('name')?.includes('operator')) {
graph.setItemState(e.item, 'operatorhover', true);
} else {
graph.setItemState(e.item, 'hover', true);
}
})
graph.on('node:mouseleave', e => {
graph.setItemState(e.item, 'operatorhover', false);
graph.setItemState(e.item, 'hover', false);
});
graph.data(this.g6Data);
graph.render();
window.addEventListener("resize", () => {
this.graph.changeSize(
document.body.clientWidth - 48,
document.body.clientHeight - 110
);
});
},
},
watch: {
dataTree(newData) {
this.g6Data = newData
this.init()
}
},
computed: {
dataTree() {
return this.$store.state.decision.dataTree
}
},
};
</script>
<style scoped>
#antTree {
flex: 1;
border: 1px solid lemonchiffon;
height: 100%;
width: 100%;
}
</style>
// vuex代码
import Vue from 'vue'
import Vuex from 'vuex'
import decision from './decision'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {
decision
}
})
export default store
// decision模块
export default ({
namespaced: true,
state: {
dataTree: {}
},
getters: {},
mutations: {
setData(state, data) {
state.dataTree = data || {}
},
},
actions: {},
})