先附上效果图
// 节点内属性的点击事件:node:port:click
graph.on(‘node:port:click’, ({ node, port }) => {
resetAllHighlights();
highlightPort(node, port, true);
highlightEdgesForPort(port, new Set());
});
// 以下为源码
<template>
<div id="container"></div>
</template>
<script setup lang="ts">
import {onMounted} from "vue";
import { Graph, Cell, Shape } from '@antv/x6'
const LINE_HEIGHT = 24
const NODE_WIDTH = 150
Graph.registerPortLayout(
'erPortPosition',
(portsPositionArgs) => {
return portsPositionArgs.map((_, index) => {
return {
position: {
x: 0,
y: (index + 1) * LINE_HEIGHT,
},
angle: 0,
}
})
},
true,
)
Graph.registerNode(
'er-rect',
{
inherit: 'rect',
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
rect: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#5F95FF',
},
label: {
fontWeight: 'bold',
fill: '#ffffff',
fontSize: 12,
},
},
ports: {
groups: {
list: {
markup: [
{
tagName: 'rect',
selector: 'portBody',
},
{
tagName: 'text',
selector: 'portNameLabel',
},
{
tagName: 'text',
selector: 'portTypeLabel',
},
],
attrs: {
portBody: {
width: NODE_WIDTH,
height: LINE_HEIGHT,
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF',
cursor: 'pointer',
},
portNameLabel: {
ref: 'portBody',
refX: 6,
refY: 6,
fontSize: 10,
},
portTypeLabel: {
ref: 'portBody',
refX: 95,
refY: 6,
fontSize: 10,
},
},
position: 'erPortPosition',
},
},
},
},
true,
)
onMounted(() => {
const graph = new Graph({
container: document.getElementById('container'),
interacting: false, // 节点不可拖动
connecting: {
router: {
name: 'er',
args: {
offset: 25,
direction: 'H',
},
},
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: '#A2B1C3',
strokeWidth: 2,
},
},
})
},
},
})
const resetAllHighlights = () => {
const nodes = graph.getNodes(); // 获取所有nodes
nodes.forEach((node) => {
const ports = node.getPorts(); // 获取所有ports
ports.forEach((port) => {
node.portProp(port.id, 'attrs/portBody/fill', '#EFF4FF');
node.portProp(port.id, 'attrs/portNameLabel/fill', '#000000');
node.portProp(port.id, 'attrs/portTypeLabel/fill', '#000000');
});
});
const edges = graph.getEdges();
edges.forEach((edge) => {
edge.attr('line/stroke', '#A2B1C3');
edge.attr('line/strokeWidth', 2);
});
};
const highlightPort = (node: Cell, portId: string, highlight: boolean) => {
if (highlight) {
node.portProp(portId, 'attrs/portBody/fill', 'yellow');
node.portProp(portId, 'attrs/portNameLabel/fill', 'red');
node.portProp(portId, 'attrs/portTypeLabel/fill', 'red');
} else {
node.portProp(portId, 'attrs/portBody/fill', '#EFF4FF');
node.portProp(portId, 'attrs/portNameLabel/fill', '#000000');
node.portProp(portId, 'attrs/portTypeLabel/fill', '#000000');
}
};
const highlightEdgesForPort = (portId: string, visitedPorts: Set<string>) => {
if (visitedPorts.has(portId)) return;
visitedPorts.add(portId);
const edges = graph.getEdges();
edges.forEach((edge) => {
const sourcePortId = edge.getSourcePortId();
const targetPortId = edge.getTargetPortId();
if (sourcePortId === portId || targetPortId === portId) {
edge.attr('line/stroke', 'red');
edge.attr('line/strokeWidth', 2);
const sourceNode = edge.getSourceNode();
const targetNode = edge.getTargetNode();
// 递归
if (sourcePortId === portId && targetNode) {
highlightPort(targetNode, targetPortId, true);
highlightEdgesForPort(targetPortId, visitedPorts); // Recursively highlight connected ports
} else if (targetPortId === portId && sourceNode) {
highlightPort(sourceNode, sourcePortId, true);
highlightEdgesForPort(sourcePortId, visitedPorts); // Recursively highlight connected ports
}
}
});
};
graph.on('node:port:click', ({ node, port }) => {
resetAllHighlights();
highlightPort(node, port, true);
highlightEdgesForPort(port, new Set());
});
const data = [
{
"id": "1",
"shape": "er-rect",
"label": "学生",
"width": 150,
"height": 24,
"position": {
"x": 24,
"y": 150
},
"ports": [
{
"id": "1-1",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "ID"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "1-2",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "Name"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "1-3",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "Class"
},
"portTypeLabel": {
"text": "NUMBER"
}
}
},
{
"id": "1-4",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "Gender"
},
"portTypeLabel": {
"text": "BOOLEAN"
}
}
}
]
},
{
"id": "2",
"shape": "er-rect",
"label": "课程",
"width": 150,
"height": 24,
"position": {
"x": 250,
"y": 210
},
"ports": [
{
"id": "2-1",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "ID"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "2-2",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "Name"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "2-3",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "StudentID"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "2-4",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "TeacherID"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "2-5",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "Description"
},
"portTypeLabel": {
"text": "STRING"
}
}
}
]
},
{
"id": "3",
"shape": "er-rect",
"label": "老师",
"width": 150,
"height": 24,
"position": {
"x": 480,
"y": 350
},
"ports": [
{
"id": "3-1",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "ID"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "3-2",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "Name"
},
"portTypeLabel": {
"text": "STRING"
}
}
},
{
"id": "3-3",
"group": "list",
"attrs": {
"portNameLabel": {
"text": "Age"
},
"portTypeLabel": {
"text": "NUMBER"
}
}
}
]
},
{
"id": "4",
"shape": "edge",
"source": {
"cell": "1",
"port": "1-1"
},
"target": {
"cell": "2",
"port": "2-4"
},
"attrs": {
"line": {
"stroke": "#A2B1C3",
"strokeWidth": 2
}
},
"zIndex": 0
},
{
"id": "5",
"shape": "edge",
"source": {
"cell": "2",
"port": "2-4"
},
"target": {
"cell": "3",
"port": "3-1"
},
"attrs": {
"line": {
"stroke": "#A2B1C3",
"strokeWidth": 2
}
},
"zIndex": 0
},
{
"id": "6",
"shape": "edge",
"source": {
"cell": "1",
"port": "1-2"
},
"target": {
"cell": "2",
"port": "2-2"
},
"attrs": {
"line": {
"stroke": "#A2B1C3",
"strokeWidth": 2
}
},
"zIndex": 0
},
]
const cells: Cell[] = []
data.forEach((item: any) => {
if (item.shape === 'edge') {
cells.push(graph.createEdge(item))
} else {
cells.push(graph.createNode(item))
}
})
graph.resetCells(cells)
graph.zoomToFit({ padding: 10, maxScale: 1 })
})
</script>