|
|
@@ -4,7 +4,7 @@
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
|
|
- import { Graph, treeToGraphData, Rect, register, ExtensionCategory, NodeEvent, Polyline } from '@antv/g6';
|
|
|
+ import { Graph, treeToGraphData, register, ExtensionCategory, NodeEvent, Polyline, CanvasEvent } from '@antv/g6';
|
|
|
|
|
|
/**
|
|
|
* @description: 为了确保图的正确渲染和交互,建议按照 G6 标准数据结构组织数据。
|
|
|
@@ -22,47 +22,25 @@
|
|
|
treeData: TreeData;
|
|
|
}>();
|
|
|
|
|
|
- // 通过 treeToGraphData 方法,将树形结构数据转换为 G6 的标准数据结构
|
|
|
- const data = treeToGraphData(props.treeData);
|
|
|
- // 图表容器引用
|
|
|
- const container = ref<HTMLDivElement | null>(null);
|
|
|
- let graph: any = null;
|
|
|
-
|
|
|
- // 设置图表节点样式
|
|
|
- class ChartNode extends Rect {
|
|
|
- get data() {
|
|
|
- return this.context.model.getElementDataById(this.id).data;
|
|
|
- }
|
|
|
-
|
|
|
- getLabelStyle() {
|
|
|
- const text = this.data?.name;
|
|
|
- const labelStyle = {
|
|
|
- fill: '#000',
|
|
|
- fontSize: 20,
|
|
|
- fontWeight: 600,
|
|
|
- textAlign: 'center',
|
|
|
- transform: [['translate', 0, 0]],
|
|
|
- };
|
|
|
- return { text, ...labelStyle };
|
|
|
- }
|
|
|
+ const emits = defineEmits<{
|
|
|
+ (event: 'node-click', nodeData: any): void;
|
|
|
+ (event: 'canvas-click'): void;
|
|
|
+ }>();
|
|
|
|
|
|
- render(attributes = this.parsedAttributes, container = this) {
|
|
|
- super.render(attributes, container);
|
|
|
- }
|
|
|
- }
|
|
|
+ const data = treeToGraphData(props.treeData); // 通过 treeToGraphData 方法,将树形结构数据转换为 G6 的标准数据结构
|
|
|
+ const container = ref<HTMLDivElement | null>(null); // 图表容器引用
|
|
|
+ let graph: any = null;
|
|
|
|
|
|
// 自定义edge样式
|
|
|
class AntLine extends Polyline {
|
|
|
onCreate() {
|
|
|
const shape = this.shapeMap.key;
|
|
|
- shape.animate([{ lineDashOffset: -20 }, { lineDashOffset: 0 }], {
|
|
|
+ shape.animate([{ lineDashOffset: 20 }, { lineDashOffset: 0 }], {
|
|
|
duration: 500,
|
|
|
iterations: Infinity,
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- register(ExtensionCategory.NODE, 'chart-node', ChartNode);
|
|
|
register(ExtensionCategory.EDGE, 'ant-line', AntLine);
|
|
|
|
|
|
// 初始化图表
|
|
|
@@ -78,30 +56,31 @@
|
|
|
// 创建新的 G6 实例
|
|
|
graph = new Graph({
|
|
|
container: container.value,
|
|
|
+ padding: [20, 20, 20, 20], // 图表内边距
|
|
|
data,
|
|
|
node: {
|
|
|
- type: 'chart-node', // 节点类型
|
|
|
+ type: 'rect', // 使用内置的矩形节点类型
|
|
|
style: {
|
|
|
+ labelText: (d: any) => d.data.name, // 节点文本
|
|
|
+ labelFill: '#333', // 文本颜色
|
|
|
+ labelFontSize: 14, // 文本大小
|
|
|
+ size: [250, 50],
|
|
|
+ lineWidth: 1, // 边框宽度
|
|
|
+ // lineDash: [5, 5], // 虚线边框
|
|
|
+ stroke: '#1777FF', // 边框色
|
|
|
fill: '#E7F1FF', // 填充色
|
|
|
+ radius: 8,
|
|
|
labelPlacement: 'center',
|
|
|
- lineWidth: 1, // 边框宽度
|
|
|
ports: [{ placement: 'top' }, { placement: 'bottom' }],
|
|
|
- radius: 2,
|
|
|
- // shadowBlur: 10,
|
|
|
- // shadowColor: '#e0e0e0',
|
|
|
- // shadowOffsetX: 3,
|
|
|
- size: [150, 60],
|
|
|
- stroke: '#1777FF', // 边框色
|
|
|
},
|
|
|
// 节点状态样式
|
|
|
state: {
|
|
|
selected: {
|
|
|
- fill: '#bae7ff',
|
|
|
- stroke: '#1890ff',
|
|
|
- lineWidth: 2,
|
|
|
- },
|
|
|
- active: {
|
|
|
- fill: '#0b0',
|
|
|
+ fill: '#1777FF',
|
|
|
+ stroke: '#1777FF',
|
|
|
+ lineWidth: 1,
|
|
|
+ labelFill: '#fff', // 选中状态下文本颜色
|
|
|
+ labelFontSize: 16, // 选中状态下文本大小
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
@@ -116,13 +95,6 @@
|
|
|
type: 'orth',
|
|
|
},
|
|
|
},
|
|
|
- // 边的状态样式
|
|
|
- state: {
|
|
|
- selected: {
|
|
|
- stroke: '#1890ff',
|
|
|
- lineWidth: 3,
|
|
|
- },
|
|
|
- },
|
|
|
},
|
|
|
layout: {
|
|
|
type: 'dagre',
|
|
|
@@ -147,18 +119,18 @@
|
|
|
easing: 'ease-in-out', // 动画缓动函数
|
|
|
},
|
|
|
},
|
|
|
+ autoResize: true, // 自动调整大小
|
|
|
behaviors: [
|
|
|
'drag-canvas',
|
|
|
{
|
|
|
type: 'zoom-canvas',
|
|
|
- sensitivity: 1.5, // 配置灵敏度
|
|
|
+ sensitivity: 0.5, // 配置灵敏度
|
|
|
key: 'zoom-behavior', // 为交互指定key,便于后续更新
|
|
|
},
|
|
|
'focus-element',
|
|
|
{
|
|
|
type: 'click-select',
|
|
|
- degree: 1,
|
|
|
- state: 'active',
|
|
|
+ state: 'selected',
|
|
|
unselectedState: 'inactive',
|
|
|
multiple: true,
|
|
|
trigger: ['shift'],
|
|
|
@@ -169,33 +141,26 @@
|
|
|
// 渲染
|
|
|
graph.render();
|
|
|
|
|
|
- // 添加交互效果
|
|
|
- // graph.on('node:mouseenter', (evt) => {
|
|
|
- // const node = evt.item;
|
|
|
- // graph.setItemState(node, 'hover', true);
|
|
|
- // });
|
|
|
-
|
|
|
- // graph.on('node:mouseleave', (evt) => {
|
|
|
- // const node = evt.item;
|
|
|
- // graph.setItemState(node, 'hover', false);
|
|
|
- // });
|
|
|
- graph.on(NodeEvent.POINTER_ENTER, (event) => {
|
|
|
- const { target } = event;
|
|
|
- graph.updateNodeData([
|
|
|
- { id: target.id, style: { labelText: 'Hovered', fill: 'lightgreen', labelFill: 'lightgreen' } },
|
|
|
- ]);
|
|
|
- graph.draw();
|
|
|
+ // 监听节点点击事件
|
|
|
+ graph.on(NodeEvent.CLICK, (evt) => {
|
|
|
+ const { target } = evt;
|
|
|
+ const nodeData = graph.getNodeData(target.id); // 获取节点数据
|
|
|
+ emits('node-click', nodeData);
|
|
|
+ });
|
|
|
+
|
|
|
+ graph.on(CanvasEvent.CLICK, () => {
|
|
|
+ graph.fitCenter();
|
|
|
+ emits('canvas-click');
|
|
|
});
|
|
|
|
|
|
- // 响应窗口大小变化
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
};
|
|
|
|
|
|
// 处理窗口大小变化
|
|
|
const handleResize = () => {
|
|
|
- if (graph.get('destroyed')) return;
|
|
|
if (container.value) {
|
|
|
- graph.changeSize(container.value.clientWidth, container.value.clientHeight);
|
|
|
+ graph.resize(container.value.offsetWidth, container.value.offsetHeight);
|
|
|
+ graph.fitCenter();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -216,6 +181,7 @@
|
|
|
onBeforeUnmount(() => {
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
if (graph) {
|
|
|
+ graph.off(); // 移除所有事件监听
|
|
|
graph.destroy();
|
|
|
}
|
|
|
});
|