753 lines
16 KiB
Vue
753 lines
16 KiB
Vue
<template>
|
||
<div>
|
||
<div ref="g6Dom" :style="{ height: height, width: width }"></div>
|
||
<button @click="actions['Enable/Disable Node States'].Breathing()">
|
||
Breathing()
|
||
</button>
|
||
<button @click="actions['Enable/Disable Node States'].Scaling()">
|
||
Scaling()
|
||
</button>
|
||
|
||
<button @click="actions['Enable/Disable Edge States'].Growing()">
|
||
Growing()
|
||
</button>
|
||
<button @click="actions['Enable/Disable Edge States'].Running()">
|
||
Running()
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { nextTick, watch, onMounted, onBeforeUnmount, ref } from 'vue';
|
||
import { Graph, extend, Extensions } from '@antv/g6';
|
||
|
||
const props = defineProps({
|
||
/**
|
||
* 图表主题
|
||
*
|
||
* 'dark' | 'light'
|
||
*/
|
||
theme: {
|
||
type: String,
|
||
default: 'light', // 'dark' | 'light'
|
||
},
|
||
/**宽度,默认100% */
|
||
width: {
|
||
type: String,
|
||
default: '100%',
|
||
},
|
||
/**高度 */
|
||
height: {
|
||
type: String,
|
||
default: '500px',
|
||
},
|
||
});
|
||
|
||
const g6Dom = ref<HTMLElement | undefined>(undefined);
|
||
|
||
const ExtGraph = extend(Graph, {
|
||
// layouts: {
|
||
// dagre: Extensions.DagreLayout,
|
||
// },
|
||
edges: {
|
||
'polyline-edge': Extensions.PolylineEdge,
|
||
'cubic-edge': Extensions.CubicEdge,
|
||
// 'custom-edge': Extensions.CubicEdge,
|
||
},
|
||
});
|
||
|
||
const data = {
|
||
nodes: [
|
||
// 0 基站
|
||
{
|
||
id: '0',
|
||
data: {
|
||
x: 80,
|
||
y: 150,
|
||
type: 'circle-node',
|
||
color: '#fffff',
|
||
keyShape: {
|
||
r: 24,
|
||
width: 48,
|
||
height: 48,
|
||
fill: '#9EC9FF',
|
||
stroke: '#5B8FF9',
|
||
lineWidth: 2,
|
||
},
|
||
labelShape: {
|
||
text: '基站',
|
||
position: 'bottom',
|
||
maxWidth: '200%',
|
||
offsetY: 10,
|
||
},
|
||
labelBackgroundShape: {},
|
||
iconShape: {
|
||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||
width: 24,
|
||
height: 24,
|
||
},
|
||
},
|
||
},
|
||
// 1 DM
|
||
{
|
||
id: '1',
|
||
data: {
|
||
label: 'DM',
|
||
x: 600,
|
||
y: 350,
|
||
},
|
||
},
|
||
// 2 O&M
|
||
{
|
||
id: '2',
|
||
data: {
|
||
label: 'O&M',
|
||
x: 900,
|
||
y: 100,
|
||
},
|
||
},
|
||
// 5GC控制面
|
||
{
|
||
id: '170',
|
||
data: {
|
||
label: 'NSSF',
|
||
parentId: 'combo-5gc',
|
||
x: 300,
|
||
y: 50,
|
||
},
|
||
},
|
||
{
|
||
id: '130',
|
||
data: {
|
||
label: 'AUSF',
|
||
parentId: 'combo-5gc',
|
||
x: 400,
|
||
y: 50,
|
||
},
|
||
},
|
||
{
|
||
id: '140',
|
||
data: {
|
||
label: 'UDM',
|
||
parentId: 'combo-5gc',
|
||
x: 500,
|
||
y: 50,
|
||
},
|
||
},
|
||
{
|
||
id: '120',
|
||
data: {
|
||
label: 'AMF',
|
||
parentId: 'combo-5gc',
|
||
x: 300,
|
||
y: 150,
|
||
},
|
||
},
|
||
{
|
||
id: '180',
|
||
data: {
|
||
label: 'NRF',
|
||
parentId: 'combo-5gc',
|
||
x: 400,
|
||
y: 150,
|
||
},
|
||
},
|
||
{
|
||
id: '150',
|
||
data: {
|
||
label: 'SMF',
|
||
parentId: 'combo-5gc',
|
||
x: 500,
|
||
y: 250,
|
||
},
|
||
},
|
||
{
|
||
id: '160',
|
||
data: {
|
||
label: 'PCF',
|
||
parentId: 'combo-5gc',
|
||
x: 600,
|
||
y: 250,
|
||
},
|
||
},
|
||
// 5GC用户面
|
||
{
|
||
id: '190',
|
||
data: {
|
||
label: 'UPF',
|
||
parentId: 'combo-upf',
|
||
x: 300,
|
||
y: 350,
|
||
},
|
||
},
|
||
// EP-IMS
|
||
{
|
||
id: '110',
|
||
data: {
|
||
label: 'I/S-CSCF',
|
||
parentId: 'combo-ims',
|
||
x: 800,
|
||
y: 350,
|
||
},
|
||
},
|
||
{
|
||
id: '111',
|
||
data: {
|
||
label: 'P-CSCF',
|
||
parentId: 'combo-ims',
|
||
x: 800,
|
||
y: 400,
|
||
},
|
||
},
|
||
// O&M
|
||
{
|
||
id: '100',
|
||
data: {
|
||
label: 'EMS',
|
||
parentId: 'combo-ems',
|
||
x: 800,
|
||
y: 100,
|
||
},
|
||
},
|
||
],
|
||
edges: [
|
||
{
|
||
id: '0-5gc',
|
||
source: '0',
|
||
target: 'combo-5gc',
|
||
data: {
|
||
type: 'cubic-edge',
|
||
animates: {
|
||
update: [
|
||
{
|
||
// 在 selected 和 active 状态变化导致的 haloShape opacity 变化时,使 opacity 带动画地更新
|
||
fields: ['opacity'],
|
||
shapeId: 'haloShape',
|
||
states: ['selected', 'active'],
|
||
duration: 500,
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
id: '5gc-ems',
|
||
source: 'combo-5gc',
|
||
target: 'combo-ems',
|
||
data: {},
|
||
},
|
||
{
|
||
id: 'upf-ems',
|
||
source: 'combo-upf',
|
||
target: 'combo-ems',
|
||
data: {},
|
||
},
|
||
{
|
||
id: 'ims-ems',
|
||
source: 'combo-ims',
|
||
target: 'combo-ems',
|
||
data: {},
|
||
},
|
||
{
|
||
id: 'ems-2',
|
||
source: 'combo-ems',
|
||
target: '2',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '170-120',
|
||
source: '170',
|
||
target: '120',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '130-120',
|
||
source: '130',
|
||
target: '120',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '140-120',
|
||
source: '140',
|
||
target: '120',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '140-180',
|
||
source: '140',
|
||
target: '180',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '120-180',
|
||
source: '120',
|
||
target: '180',
|
||
data: {
|
||
type: 'polyline-edge',
|
||
keyShape: {
|
||
endArrow: true,
|
||
routeCfg: {
|
||
/**
|
||
* 目前支持正交路由 'orth' 和地铁路由 'er'
|
||
*/
|
||
// name: 'er',
|
||
/**
|
||
* 是否开启自动避障,默认为 false
|
||
* Whether to enable automatic obstacle avoidance, default is false
|
||
*/
|
||
enableObstacleAvoidance: true,
|
||
},
|
||
/**
|
||
* 拐弯处的圆角弧度,默认为直角,值为 0
|
||
* The radius of the corner rounding, defaults to a right angle
|
||
*/
|
||
// radius: 20,
|
||
/**
|
||
* 拐弯处距离节点最小距离, 默认为 2
|
||
* Minimum distance from the node at the corner, default is 5.
|
||
*/
|
||
// offset: 0,
|
||
/**
|
||
* 控制点数组,不指定时根据 A* 算法自动生成折线。若指定了,则按照 controlPoints 指定的位置进行弯折
|
||
* An array of control points that, when not specified, automatically generates the bends according to the A* algorithm. If specified, bends are made at the position specified by controlPoints.
|
||
*/
|
||
// controlPoints: [],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
id: '130-180',
|
||
source: '130',
|
||
target: '180',
|
||
data: {
|
||
type: 'polyline-edge',
|
||
},
|
||
},
|
||
{
|
||
id: '140-150',
|
||
source: '140',
|
||
target: '150',
|
||
data: {
|
||
type: 'polyline-edge',
|
||
},
|
||
},
|
||
{
|
||
id: '140-110',
|
||
source: '140',
|
||
target: '110',
|
||
data: {
|
||
type: 'polyline-edge',
|
||
},
|
||
},
|
||
{
|
||
id: '120-150',
|
||
source: '120',
|
||
target: '150',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '150-180',
|
||
source: '150',
|
||
target: '180',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '150-160',
|
||
source: '150',
|
||
target: '160',
|
||
data: { type: 'polyline-edge' },
|
||
},
|
||
{
|
||
id: '160-120',
|
||
source: '160',
|
||
target: '120',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '160-180',
|
||
source: '160',
|
||
target: '180',
|
||
data: {
|
||
keyShape: {
|
||
endArrow: true,
|
||
},
|
||
labelShape: {
|
||
text: 'asdf-arrow',
|
||
},
|
||
},
|
||
},
|
||
{
|
||
id: '160-111',
|
||
source: '160',
|
||
target: '111',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '0-upf',
|
||
source: '0',
|
||
target: 'combo-upf',
|
||
data: {},
|
||
},
|
||
{
|
||
id: '150-190',
|
||
source: '150',
|
||
target: '190',
|
||
data: {},
|
||
},
|
||
{
|
||
id: 'upf-1',
|
||
source: 'combo-upf',
|
||
target: '1',
|
||
data: {},
|
||
},
|
||
{
|
||
id: 'upf-ims',
|
||
source: 'combo-upf',
|
||
target: 'combo-ims',
|
||
data: {},
|
||
},
|
||
],
|
||
combos: [
|
||
{
|
||
id: 'combo-5gc',
|
||
data: {
|
||
text: '5GC控制面',
|
||
},
|
||
},
|
||
{
|
||
id: 'combo-upf',
|
||
data: {
|
||
label: '5GC用户面',
|
||
},
|
||
},
|
||
{
|
||
id: 'combo-ims',
|
||
data: {
|
||
label: 'EP-IMS用户面',
|
||
},
|
||
},
|
||
{
|
||
id: 'combo-ems',
|
||
data: {
|
||
label: 'EMS',
|
||
},
|
||
},
|
||
],
|
||
};
|
||
let graph: any = null;
|
||
/**初始化渲染图表 */
|
||
function initChart() {
|
||
if (!g6Dom.value) return;
|
||
graph = new ExtGraph({
|
||
container: g6Dom.value,
|
||
height: 500,
|
||
plugins: [
|
||
{
|
||
// lod-controller will be automatically assigned to graph with `disableLod: false` to graph if it is not configured as following
|
||
type: 'lod-controller',
|
||
disableLod: true,
|
||
},
|
||
] as any,
|
||
modes: {
|
||
default: [
|
||
{
|
||
type: 'drag-node',
|
||
enableTransient: false,
|
||
updateComboStructure: false,
|
||
},
|
||
{
|
||
type: 'click-select',
|
||
itemTypes: ['node', 'edge', 'combo'],
|
||
},
|
||
{
|
||
type: 'drag-combo',
|
||
enableTransient: true,
|
||
updateComboStructure: true,
|
||
},
|
||
// 'drag-combo',
|
||
'drag-canvas',
|
||
// 'click-select',
|
||
'zoom-canvas',
|
||
] as any,
|
||
},
|
||
|
||
theme: {
|
||
type: 'spec',
|
||
base: 'light',
|
||
specification: {
|
||
node: {
|
||
dataTypeField: 'parentId',
|
||
},
|
||
},
|
||
} as any,
|
||
// 全局节点 矩形
|
||
node: model => {
|
||
const { id, data } = model;
|
||
return {
|
||
id,
|
||
data: {
|
||
type: 'rect-node',
|
||
keyShape: {
|
||
width: 80,
|
||
height: 40,
|
||
radius: 8,
|
||
},
|
||
labelShape: {
|
||
position: 'center',
|
||
text: data.label,
|
||
},
|
||
animates: {
|
||
update: [
|
||
{
|
||
fields: ['opacity'],
|
||
shapeId: 'haloShape',
|
||
states: ['breathing'],
|
||
iterations: Infinity,
|
||
direction: 'alternate',
|
||
duration: 500,
|
||
},
|
||
{
|
||
fields: ['lineWidth'],
|
||
shapeId: 'keyShape',
|
||
states: ['breathing'],
|
||
iterations: Infinity,
|
||
direction: 'alternate',
|
||
duration: 500,
|
||
},
|
||
{
|
||
fields: ['height', 'width'],
|
||
shapeId: 'keyShape',
|
||
states: ['scaling'],
|
||
iterations: Infinity,
|
||
direction: 'alternate',
|
||
duration: 500,
|
||
},
|
||
],
|
||
},
|
||
...data,
|
||
},
|
||
};
|
||
},
|
||
// 全局边 三次贝塞尔曲线
|
||
edge: model => {
|
||
const { id, source, target, data } = model;
|
||
return {
|
||
id,
|
||
source,
|
||
target,
|
||
data: {
|
||
type: 'cubic-edge',
|
||
animates: {
|
||
update: [
|
||
{
|
||
fields: ['lineDash'],
|
||
shapeId: 'keyShape',
|
||
states: ['growing', 'running'],
|
||
iterations: Infinity,
|
||
duration: 2000,
|
||
},
|
||
{
|
||
fields: ['offsetDistance'],
|
||
shapeId: 'buShape',
|
||
states: ['circleRunning'],
|
||
iterations: Infinity,
|
||
duration: 2000,
|
||
},
|
||
],
|
||
},
|
||
...data,
|
||
},
|
||
};
|
||
},
|
||
combo: model => {
|
||
const { id, data } = model;
|
||
return {
|
||
id,
|
||
data: {
|
||
...data,
|
||
type: 'rect-combo',
|
||
keyShape: {
|
||
opacity: 0.8,
|
||
padding: [20, 20, 20, 20],
|
||
radius: 8,
|
||
},
|
||
labelShape: {
|
||
text: data.label,
|
||
offsetY: 8,
|
||
},
|
||
labelBackgroundShape: {},
|
||
animates: {
|
||
update: [
|
||
{
|
||
fields: ['width', 'height', 'x', 'y'],
|
||
shapeId: 'keyShape',
|
||
},
|
||
],
|
||
},
|
||
},
|
||
};
|
||
},
|
||
// 节点状态
|
||
nodeState: {
|
||
breathing: {
|
||
haloShape: {
|
||
opacity: 0.25,
|
||
lineWidth: 20,
|
||
visible: true,
|
||
},
|
||
keyShape: {
|
||
radius: 4,
|
||
},
|
||
},
|
||
scaling: {
|
||
keyShape: {
|
||
width: 100,
|
||
height: 60,
|
||
},
|
||
},
|
||
} as any,
|
||
edgeState: {
|
||
growing: {
|
||
keyShape: {
|
||
lineWidth: 2,
|
||
lineDash: ['100%', 0],
|
||
},
|
||
},
|
||
running: {
|
||
keyShape: {
|
||
lineWidth: 2,
|
||
lineDash: [2, 2],
|
||
// TODO: lineDashOffset
|
||
},
|
||
},
|
||
},
|
||
data,
|
||
});
|
||
|
||
graph.on('node:click', (e: any) => {
|
||
const s = graph.getItemState(e.itemId);
|
||
console.log(s);
|
||
// graph.updateData('node', {
|
||
// id: e.itemId,
|
||
// data: {
|
||
// cluster: Math.random(),
|
||
// keyShape: {
|
||
// r: 32 + Math.random() * 10 - 5,
|
||
// lineWidth: 6 + Math.random() * 6 - 3,
|
||
// stroke: '#000',
|
||
// },
|
||
// labelShape: {
|
||
// fontWeight: 700,
|
||
// },
|
||
// },
|
||
// });
|
||
});
|
||
|
||
graph.on('node:pointerenter', (e: any) => {
|
||
const { itemId } = e;
|
||
if (graph.getItemState(itemId, 'breathing')) {
|
||
graph.setItemState(itemId, 'breathing', false);
|
||
} else {
|
||
graph.setItemState(itemId, 'scaling', false);
|
||
graph.setItemState(itemId, 'breathing', true);
|
||
}
|
||
// graph.updateData('node', {
|
||
// id: itemId,
|
||
// data: {
|
||
// label: `after been hovered ${itemId}`,
|
||
// labelShape: {
|
||
// fill: '#0f0',
|
||
// },
|
||
// },
|
||
// });
|
||
});
|
||
|
||
graph.on('node:pointerleave', (e: any) => {
|
||
const { itemId } = e;
|
||
if (graph.getItemState(itemId, 'breathing')) {
|
||
graph.setItemState(itemId, 'breathing', false);
|
||
} else {
|
||
graph.setItemState(itemId, 'scaling', false);
|
||
graph.setItemState(itemId, 'breathing', true);
|
||
}
|
||
// graph.updateData('node', {
|
||
// id: itemId,
|
||
// data: {
|
||
// label: 'label before been hovered',
|
||
// labelShape: {
|
||
// fill: '#000',
|
||
// },
|
||
// },
|
||
// });
|
||
});
|
||
}
|
||
|
||
const actions = {
|
||
'Enable/Disable Node States': {
|
||
Breathing: () => {
|
||
graph.getAllNodesData().forEach((node:any) => {
|
||
if (graph.getItemState(node.id, 'breathing')) {
|
||
graph.setItemState(node.id, 'breathing', false);
|
||
} else {
|
||
graph.setItemState(node.id, 'scaling', false);
|
||
graph.setItemState(node.id, 'breathing', true);
|
||
}
|
||
});
|
||
},
|
||
Scaling: () => {
|
||
graph.getAllNodesData().forEach((node:any) => {
|
||
if (graph.getItemState(node.id, 'scaling')) {
|
||
graph.setItemState(node.id, 'scaling', false);
|
||
} else {
|
||
graph.setItemState(node.id, 'breathing', false);
|
||
graph.setItemState(node.id, 'scaling', true);
|
||
}
|
||
});
|
||
},
|
||
},
|
||
'Enable/Disable Edge States': {
|
||
Growing: () => {
|
||
graph.getAllEdgesData().forEach((edge:any) => {
|
||
if (graph.getItemState(edge.id, 'growing')) {
|
||
graph.setItemState(edge.id, 'growing', false);
|
||
} else {
|
||
graph.setItemState(edge.id, 'running', false);
|
||
graph.setItemState(edge.id, 'growing', true);
|
||
}
|
||
});
|
||
},
|
||
Running: () => {
|
||
graph.getAllEdgesData().forEach((edge:any) => {
|
||
if (graph.getItemState(edge.id, 'running')) {
|
||
graph.setItemState(edge.id, 'running', false);
|
||
} else {
|
||
graph.setItemState(edge.id, 'growing', false);
|
||
graph.setItemState(edge.id, 'running', true);
|
||
}
|
||
});
|
||
},
|
||
},
|
||
};
|
||
|
||
// watch(
|
||
// () => props.option,
|
||
// val => {
|
||
// if (val) {
|
||
// nextTick(() => {
|
||
// initChart();
|
||
// });
|
||
// }
|
||
// }
|
||
// );
|
||
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
initChart();
|
||
});
|
||
});
|
||
|
||
onBeforeUnmount(() => {});
|
||
</script>
|
||
|
||
<style lang="less" scoped></style>
|