@agilehub/vue-knowledge-graph 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +315 -3
- package/dist/components/GraphToolbar.vue.d.ts +21 -0
- package/dist/components/GraphVisualization.vue.d.ts +26 -0
- package/dist/index.d.ts +14 -0
- package/dist/module/g6Graph.d.ts +60 -0
- package/dist/types/index.d.ts +54 -0
- package/dist/vue-knowledge-graph.cjs +4 -0
- package/dist/vue-knowledge-graph.cjs.map +1 -0
- package/dist/vue-knowledge-graph.mjs +517 -0
- package/dist/vue-knowledge-graph.mjs.map +1 -0
- package/package.json +80 -6
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
|
|
2
|
+
(function(){if(typeof document<"u"&&!document.getElementById("vue-knowledge-graph-styles")){var e=document.createElement("style");e.id="vue-knowledge-graph-styles",e.textContent=".graph-container[data-v-232ee924]{width:100%;height:100%}.kg-toolbar[data-v-f8202e9e]{z-index:100;background:#fff;border:1px solid #e2e8f0;border-radius:10px;align-items:center;gap:6px;padding:8px;font-family:Segoe UI,PingFang SC,system-ui,sans-serif;display:flex;position:absolute;top:16px;right:16px;box-shadow:0 2px 8px #0000000f}.kg-toolbar[data-v-f8202e9e]:hover{box-shadow:0 4px 12px #0000001a}.kg-toolbar.kg-collapsed[data-v-f8202e9e]{padding:6px;right:12px}.kg-btn-group[data-v-f8202e9e]{align-items:center;gap:8px;display:flex}.kg-btn[data-v-f8202e9e]{color:#334155;cursor:pointer;background:0 0;border:none;border-radius:6px;outline:none;align-items:center;gap:5px;padding:6px 12px;font-size:12px;font-weight:500;transition:all .2s;display:flex}.kg-btn[data-v-f8202e9e]:hover{color:#0f172a;background:#e2e8f0}.kg-btn[data-v-f8202e9e]:focus-visible,.kg-toggle[data-v-f8202e9e]:focus-visible{outline-offset:2px;outline:2px solid #60a5fa}.kg-icon[data-v-f8202e9e]{font-size:13px;line-height:1}.kg-layout-control[data-v-f8202e9e]{border-left:1px solid #e2e8f0;align-items:center;gap:7px;padding-left:10px;display:flex}.kg-layout-label[data-v-f8202e9e]{color:#64748b;white-space:nowrap;font-size:11px;font-weight:650}.kg-layout-select[data-v-f8202e9e]{color:#1e293b;background:#fff;border:1px solid #cbd5e1;border-radius:6px;outline:none;width:128px;min-height:31px;padding:5px 26px 5px 9px;font-size:12px}.kg-layout-select[data-v-f8202e9e]:hover,.kg-layout-select[data-v-f8202e9e]:focus{border-color:#60a5fa;box-shadow:0 0 0 2px #dbeafe}.kg-toggle[data-v-f8202e9e]{color:#64748b;background:0 0;border:none;border-radius:4px;padding:4px 8px;font-size:11px}.kg-toggle[data-v-f8202e9e]:hover{color:#0f172a;background:#e2e8f0}@media (width<=640px){.kg-toolbar[data-v-f8202e9e]{max-width:calc(100% - 24px)}.kg-btn-group[data-v-f8202e9e]{gap:4px}.kg-btn[data-v-f8202e9e]{padding:6px 8px}.kg-btn-label[data-v-f8202e9e]{display:none}.kg-layout-control[data-v-f8202e9e]{gap:4px;padding-left:6px}.kg-layout-label[data-v-f8202e9e]{display:none}.kg-layout-select[data-v-f8202e9e]{width:120px}}\n/*$vite$:1*/",document.head.appendChild(e)}})();
|
|
3
|
+
import { Fragment as e, computed as t, createElementBlock as n, createElementVNode as r, defineComponent as i, nextTick as a, normalizeClass as o, onMounted as s, onUnmounted as c, openBlock as l, ref as u, renderList as d, toDisplayString as f, vShow as p, watch as m, withDirectives as h } from "vue";
|
|
4
|
+
import { CanvasEvent as g, Graph as _, NodeEvent as v } from "@antv/g6";
|
|
5
|
+
//#region src/module/g6Graph.ts
|
|
6
|
+
var y = [
|
|
7
|
+
{
|
|
8
|
+
value: "force",
|
|
9
|
+
label: "力导向",
|
|
10
|
+
description: "通用网络布局,适合结构未知或关系密集的图谱",
|
|
11
|
+
category: "network"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
value: "circular",
|
|
15
|
+
label: "环形",
|
|
16
|
+
description: "节点沿圆环排列,适合观察整体结构",
|
|
17
|
+
category: "network"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: "concentric",
|
|
21
|
+
label: "同心圆",
|
|
22
|
+
description: "按连接度分层,突出核心节点和外围节点",
|
|
23
|
+
category: "network"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: "antv-dagre",
|
|
27
|
+
label: "有向层级",
|
|
28
|
+
description: "按边的方向排列层级,适合 DAG 或流程数据",
|
|
29
|
+
category: "hierarchy"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
value: "indented",
|
|
33
|
+
label: "缩进树",
|
|
34
|
+
description: "目录式层级布局,适合近树结构",
|
|
35
|
+
category: "hierarchy"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: "mindmap",
|
|
39
|
+
label: "思维导图",
|
|
40
|
+
description: "从中心向两侧分支,适合近树结构",
|
|
41
|
+
category: "hierarchy"
|
|
42
|
+
}
|
|
43
|
+
], b = class {
|
|
44
|
+
graph = null;
|
|
45
|
+
container = null;
|
|
46
|
+
resizeHandler = null;
|
|
47
|
+
createGraph(e, t) {
|
|
48
|
+
let { container: n, width: r, height: i } = t;
|
|
49
|
+
this.graph && this.destroy(), this.container = n, this.graph = new _({
|
|
50
|
+
container: n,
|
|
51
|
+
width: r || n.offsetWidth,
|
|
52
|
+
height: i || n.offsetHeight,
|
|
53
|
+
...e
|
|
54
|
+
}), this.setupResizeHandler();
|
|
55
|
+
}
|
|
56
|
+
setupResizeHandler() {
|
|
57
|
+
this.cleanupResizeHandler(), this.resizeHandler = () => {
|
|
58
|
+
this.container && this.resize(this.container.offsetWidth, this.container.offsetHeight);
|
|
59
|
+
}, window.addEventListener("resize", this.resizeHandler);
|
|
60
|
+
}
|
|
61
|
+
cleanupResizeHandler() {
|
|
62
|
+
this.resizeHandler &&= (window.removeEventListener("resize", this.resizeHandler), null);
|
|
63
|
+
}
|
|
64
|
+
run(e) {
|
|
65
|
+
e.catch(() => void 0);
|
|
66
|
+
}
|
|
67
|
+
hasGraph() {
|
|
68
|
+
return this.graph !== null;
|
|
69
|
+
}
|
|
70
|
+
resize(e, t) {
|
|
71
|
+
if (!this.graph || e <= 0 || t <= 0) return;
|
|
72
|
+
let [n, r] = this.graph.getSize();
|
|
73
|
+
(n !== e || r !== t) && this.graph.resize(e, t);
|
|
74
|
+
}
|
|
75
|
+
fitView(e = 20) {
|
|
76
|
+
this.graph && this.run(this.graph.fitView({ when: "always" }, !1));
|
|
77
|
+
}
|
|
78
|
+
destroy() {
|
|
79
|
+
this.cleanupResizeHandler(), this.graph?.destroy(), this.graph = null, this.container = null;
|
|
80
|
+
}
|
|
81
|
+
}, x = class extends b {
|
|
82
|
+
currentLayoutType = "force";
|
|
83
|
+
nodeClickHandler = null;
|
|
84
|
+
canvasElement = null;
|
|
85
|
+
canvasPointerDownHandler = null;
|
|
86
|
+
canvasPointerMoveHandler = null;
|
|
87
|
+
canvasPointerUpHandler = null;
|
|
88
|
+
canvasPointerId = null;
|
|
89
|
+
lastCanvasPoint = {
|
|
90
|
+
x: 0,
|
|
91
|
+
y: 0
|
|
92
|
+
};
|
|
93
|
+
isCanvasDragging = !1;
|
|
94
|
+
canvasDefaultCursor = "default";
|
|
95
|
+
getLayoutConfig(e, t, n) {
|
|
96
|
+
let r = [t / 2, n / 2], i = (e) => Math.min(Math.max((e.label?.length ?? 4) * 14 + 28, 72), 220), a = () => 42;
|
|
97
|
+
switch (e) {
|
|
98
|
+
case "circular": return {
|
|
99
|
+
type: e,
|
|
100
|
+
center: r,
|
|
101
|
+
radius: Math.min(t, n) * .35,
|
|
102
|
+
startAngle: Math.PI / 2,
|
|
103
|
+
endAngle: Math.PI * 2.5,
|
|
104
|
+
ordering: "degree"
|
|
105
|
+
};
|
|
106
|
+
case "concentric": return {
|
|
107
|
+
type: e,
|
|
108
|
+
center: r,
|
|
109
|
+
preventOverlap: !0,
|
|
110
|
+
nodeSize: 42,
|
|
111
|
+
sortBy: "degree",
|
|
112
|
+
equidistant: !0
|
|
113
|
+
};
|
|
114
|
+
case "antv-dagre": return {
|
|
115
|
+
type: e,
|
|
116
|
+
rankdir: "TB",
|
|
117
|
+
align: "UL",
|
|
118
|
+
nodesep: 42,
|
|
119
|
+
ranksep: 110
|
|
120
|
+
};
|
|
121
|
+
case "indented": return {
|
|
122
|
+
type: e,
|
|
123
|
+
direction: "LR",
|
|
124
|
+
indent: 36,
|
|
125
|
+
getWidth: i,
|
|
126
|
+
getHeight: a,
|
|
127
|
+
getHGap: () => 18,
|
|
128
|
+
getVGap: () => 12
|
|
129
|
+
};
|
|
130
|
+
case "mindmap": return {
|
|
131
|
+
type: e,
|
|
132
|
+
direction: "H",
|
|
133
|
+
preLayout: !1,
|
|
134
|
+
getWidth: i,
|
|
135
|
+
getHeight: a,
|
|
136
|
+
getHGap: () => 30,
|
|
137
|
+
getVGap: () => 18
|
|
138
|
+
};
|
|
139
|
+
default: return {
|
|
140
|
+
type: "force",
|
|
141
|
+
center: r,
|
|
142
|
+
linkDistance: 280,
|
|
143
|
+
nodeStrength: 1e3,
|
|
144
|
+
edgeStrength: 200,
|
|
145
|
+
gravity: 10
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
getColorByType(e) {
|
|
150
|
+
return {
|
|
151
|
+
人物: "#ff4d4f",
|
|
152
|
+
地点: "#52c41a",
|
|
153
|
+
组织: "#1890ff",
|
|
154
|
+
概念: "#faad14",
|
|
155
|
+
事件: "#722ed1",
|
|
156
|
+
entity: "#13c2c2"
|
|
157
|
+
}[e || "entity"] || "#13c2c2";
|
|
158
|
+
}
|
|
159
|
+
getColorByRelation(e) {
|
|
160
|
+
return {
|
|
161
|
+
因果: "#e74c3c",
|
|
162
|
+
所属: "#2ecc71",
|
|
163
|
+
引用: "#3498db",
|
|
164
|
+
相关: "#9b59b6"
|
|
165
|
+
}[e || "相关"] || "#9b59b6";
|
|
166
|
+
}
|
|
167
|
+
init(e, t, n) {
|
|
168
|
+
let r = e.width || e.container.offsetWidth, i = e.height || e.container.offsetHeight, a = this.processData(t, n);
|
|
169
|
+
this.currentLayoutType = e.layoutType ?? "force", this.createGraph({
|
|
170
|
+
data: a,
|
|
171
|
+
animation: !1,
|
|
172
|
+
zoomRange: [.1, 5],
|
|
173
|
+
layout: this.getLayoutConfig(this.currentLayoutType, r, i),
|
|
174
|
+
behaviors: [
|
|
175
|
+
{
|
|
176
|
+
type: "zoom-canvas",
|
|
177
|
+
sensitivity: 1
|
|
178
|
+
},
|
|
179
|
+
{ type: "drag-element" },
|
|
180
|
+
{
|
|
181
|
+
type: "hover-activate",
|
|
182
|
+
degree: 1
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
type: "click-select",
|
|
186
|
+
degree: 1
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
node: {
|
|
190
|
+
type: "circle",
|
|
191
|
+
style: {
|
|
192
|
+
cursor: "pointer",
|
|
193
|
+
labelPlacement: "bottom",
|
|
194
|
+
labelFill: "#000000",
|
|
195
|
+
labelFontSize: 16,
|
|
196
|
+
labelFontWeight: "bold"
|
|
197
|
+
},
|
|
198
|
+
state: {
|
|
199
|
+
active: {
|
|
200
|
+
lineWidth: 3,
|
|
201
|
+
shadowBlur: 12,
|
|
202
|
+
shadowColor: "#40a9ff"
|
|
203
|
+
},
|
|
204
|
+
selected: {
|
|
205
|
+
lineWidth: 4,
|
|
206
|
+
stroke: "#ff4d4f"
|
|
207
|
+
},
|
|
208
|
+
inactive: {
|
|
209
|
+
opacity: .25,
|
|
210
|
+
labelOpacity: .35
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
edge: {
|
|
215
|
+
type: "line",
|
|
216
|
+
style: {
|
|
217
|
+
cursor: "pointer",
|
|
218
|
+
endArrow: !0,
|
|
219
|
+
labelAutoRotate: !0,
|
|
220
|
+
labelFontSize: 12
|
|
221
|
+
},
|
|
222
|
+
state: {
|
|
223
|
+
active: { lineWidth: 3 },
|
|
224
|
+
selected: {
|
|
225
|
+
lineWidth: 3,
|
|
226
|
+
stroke: "#ff4d4f"
|
|
227
|
+
},
|
|
228
|
+
inactive: {
|
|
229
|
+
opacity: .2,
|
|
230
|
+
labelOpacity: .2
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}, e), this.graph && (this.nodeClickHandler = e.onNodeClick ?? null, this.setupEvents(), this.run(this.graph.render().then(() => {
|
|
235
|
+
this.setupCanvasDrag(), this.scheduleFitView();
|
|
236
|
+
})));
|
|
237
|
+
}
|
|
238
|
+
update(e, t) {
|
|
239
|
+
this.graph && (this.graph.setData(this.processData(e, t)), this.run(this.graph.render().then(() => this.scheduleFitView())));
|
|
240
|
+
}
|
|
241
|
+
resetLayout() {
|
|
242
|
+
if (!this.graph) return;
|
|
243
|
+
let e = this.graph.getNodeData().map((e) => {
|
|
244
|
+
let t = { ...e.style };
|
|
245
|
+
return delete t.x, delete t.y, {
|
|
246
|
+
...e,
|
|
247
|
+
style: t
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
this.graph.setData({
|
|
251
|
+
nodes: e,
|
|
252
|
+
edges: this.graph.getEdgeData()
|
|
253
|
+
}), this.graph.setLayout(this.getCurrentLayout()), this.run(this.graph.layout().then(() => this.scheduleFitView()));
|
|
254
|
+
}
|
|
255
|
+
switchLayout(e) {
|
|
256
|
+
!this.graph || !this.container || (this.currentLayoutType = e, this.graph.setLayout(this.getCurrentLayout()), this.run(this.graph.layout().then(() => this.scheduleFitView())));
|
|
257
|
+
}
|
|
258
|
+
getLayoutType() {
|
|
259
|
+
return this.currentLayoutType;
|
|
260
|
+
}
|
|
261
|
+
focusNode(e) {
|
|
262
|
+
!this.graph || !this.graph.getNodeData().some(({ id: t }) => t === e) || this.run(this.graph.focusElement(e, {
|
|
263
|
+
duration: 500,
|
|
264
|
+
easing: "ease-in-out"
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
getCurrentLayout() {
|
|
268
|
+
let e = this.container?.offsetWidth || this.graph?.getSize()[0] || 0, t = this.container?.offsetHeight || this.graph?.getSize()[1] || 0;
|
|
269
|
+
return this.getLayoutConfig(this.currentLayoutType, e, t);
|
|
270
|
+
}
|
|
271
|
+
processData(e, t) {
|
|
272
|
+
let n = {};
|
|
273
|
+
for (let e of t) n[e.source] = (n[e.source] || 0) + 1, n[e.target] = (n[e.target] || 0) + 1;
|
|
274
|
+
return {
|
|
275
|
+
nodes: e.map((e) => ({
|
|
276
|
+
id: e.id,
|
|
277
|
+
data: {
|
|
278
|
+
...e,
|
|
279
|
+
label: e.label,
|
|
280
|
+
entityType: e.type
|
|
281
|
+
},
|
|
282
|
+
style: {
|
|
283
|
+
x: e.x,
|
|
284
|
+
y: e.y,
|
|
285
|
+
size: e.size || Math.min(15 + (n[e.id] || 0) * 3, 40),
|
|
286
|
+
fill: e.style?.fill || this.getColorByType(e.type),
|
|
287
|
+
stroke: e.style?.stroke || "#ffffff",
|
|
288
|
+
lineWidth: e.style?.lineWidth || 2,
|
|
289
|
+
labelText: e.label
|
|
290
|
+
}
|
|
291
|
+
})),
|
|
292
|
+
edges: t.map((e, t) => {
|
|
293
|
+
let n = e.style?.stroke || this.getColorByRelation(e.relation_type);
|
|
294
|
+
return {
|
|
295
|
+
id: e.id || `edge-${e.source}-${e.target}-${t}`,
|
|
296
|
+
source: e.source,
|
|
297
|
+
target: e.target,
|
|
298
|
+
data: {
|
|
299
|
+
label: e.label,
|
|
300
|
+
relationType: e.relation_type
|
|
301
|
+
},
|
|
302
|
+
style: {
|
|
303
|
+
stroke: n,
|
|
304
|
+
lineWidth: e.style?.lineWidth || 2,
|
|
305
|
+
endArrowFill: n,
|
|
306
|
+
labelText: e.label,
|
|
307
|
+
labelFill: n
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
})
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
scheduleFitView() {
|
|
314
|
+
let e = () => {
|
|
315
|
+
a(() => {
|
|
316
|
+
requestAnimationFrame(() => this.fitView(20));
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
e(), window.setTimeout(e, 300), window.setTimeout(e, 1200);
|
|
320
|
+
}
|
|
321
|
+
setupEvents() {
|
|
322
|
+
if (!this.graph || !this.container) return;
|
|
323
|
+
let e = this.graph;
|
|
324
|
+
e.on(v.CLICK, (e) => {
|
|
325
|
+
this.nodeClickHandler?.(e.target.id);
|
|
326
|
+
}), e.on(v.DRAG, (t) => {
|
|
327
|
+
let n = t.target.id, r = .3, i = e.getNeighborNodesData(n).map((e) => ({
|
|
328
|
+
id: e.id,
|
|
329
|
+
style: {
|
|
330
|
+
x: (e.style?.x || 0) + t.dx * r,
|
|
331
|
+
y: (e.style?.y || 0) + t.dy * r
|
|
332
|
+
}
|
|
333
|
+
}));
|
|
334
|
+
i.length !== 0 && (e.updateNodeData(i), this.run(e.draw()));
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
setupCanvasDrag() {
|
|
338
|
+
if (!this.graph) return;
|
|
339
|
+
let e = this.graph, t = e.getCanvas(), n = t.getContextService().getDomElement();
|
|
340
|
+
n && (this.canvasElement = n, this.canvasDefaultCursor = t.getConfig().cursor || "default", this.canvasPointerDownHandler = (e) => {
|
|
341
|
+
e.button === 0 && this.canvasPointerId === null && (this.canvasPointerId = e.pointerId, this.isCanvasDragging = !0, this.lastCanvasPoint = {
|
|
342
|
+
x: e.clientX,
|
|
343
|
+
y: e.clientY
|
|
344
|
+
}, n.setPointerCapture(e.pointerId), t.setCursor("grabbing"));
|
|
345
|
+
}, this.canvasPointerMoveHandler = (t) => {
|
|
346
|
+
if (!this.isCanvasDragging || this.canvasPointerId !== null && t.pointerId !== this.canvasPointerId) return;
|
|
347
|
+
let n = [t.clientX - this.lastCanvasPoint.x, t.clientY - this.lastCanvasPoint.y];
|
|
348
|
+
this.lastCanvasPoint = {
|
|
349
|
+
x: t.clientX,
|
|
350
|
+
y: t.clientY
|
|
351
|
+
}, this.run(e.translateBy(n, !1)), t.preventDefault?.();
|
|
352
|
+
}, this.canvasPointerUpHandler = (e) => {
|
|
353
|
+
if (this.canvasPointerId !== null && e.pointerId !== this.canvasPointerId) return;
|
|
354
|
+
let r = this.isCanvasDragging;
|
|
355
|
+
this.isCanvasDragging = !1, this.canvasPointerId = null, r && n.hasPointerCapture?.(e.pointerId) && n.releasePointerCapture(e.pointerId), r && t.setCursor(this.canvasDefaultCursor);
|
|
356
|
+
}, e.on(g.POINTER_DOWN, this.canvasPointerDownHandler), window.addEventListener("pointermove", this.canvasPointerMoveHandler, !0), window.addEventListener("pointerup", this.canvasPointerUpHandler, !0), window.addEventListener("pointercancel", this.canvasPointerUpHandler, !0));
|
|
357
|
+
}
|
|
358
|
+
destroy() {
|
|
359
|
+
this.graph && this.canvasPointerDownHandler && this.graph.off(g.POINTER_DOWN, this.canvasPointerDownHandler), this.canvasPointerMoveHandler && window.removeEventListener("pointermove", this.canvasPointerMoveHandler, !0), this.canvasPointerUpHandler && (window.removeEventListener("pointerup", this.canvasPointerUpHandler, !0), window.removeEventListener("pointercancel", this.canvasPointerUpHandler, !0)), this.canvasElement = null, this.canvasPointerDownHandler = null, this.canvasPointerMoveHandler = null, this.canvasPointerUpHandler = null, this.canvasPointerId = null, this.isCanvasDragging = !1, this.nodeClickHandler = null, super.destroy();
|
|
360
|
+
}
|
|
361
|
+
}, S = /*@__PURE__*/ i({
|
|
362
|
+
__name: "GraphVisualization",
|
|
363
|
+
props: {
|
|
364
|
+
nodes: {},
|
|
365
|
+
edges: {},
|
|
366
|
+
width: { default: 0 },
|
|
367
|
+
height: { default: 0 },
|
|
368
|
+
layoutType: { default: "force" }
|
|
369
|
+
},
|
|
370
|
+
emits: ["nodeClick"],
|
|
371
|
+
setup(e, { expose: t, emit: r }) {
|
|
372
|
+
let i = e, o = r, d = u(null), f = null, p = null, h = null, g = () => d.value ? {
|
|
373
|
+
width: d.value.clientWidth,
|
|
374
|
+
height: d.value.clientHeight
|
|
375
|
+
} : {
|
|
376
|
+
width: 0,
|
|
377
|
+
height: 0
|
|
378
|
+
}, _ = () => {
|
|
379
|
+
d.value && (d.value.clientHeight === 0 && (d.value.style.height = "100%"), a(() => {
|
|
380
|
+
let { width: e, height: t } = g();
|
|
381
|
+
e !== 0 && t !== 0 && (f ||= new x(), f.init({
|
|
382
|
+
container: d.value,
|
|
383
|
+
width: i.width || e,
|
|
384
|
+
height: i.height || t,
|
|
385
|
+
layoutType: i.layoutType,
|
|
386
|
+
onNodeClick: (e) => {
|
|
387
|
+
let t = i.nodes.find((t) => t.id === e);
|
|
388
|
+
t && o("nodeClick", t);
|
|
389
|
+
}
|
|
390
|
+
}, i.nodes, i.edges));
|
|
391
|
+
}));
|
|
392
|
+
}, v = () => {
|
|
393
|
+
if (!f) {
|
|
394
|
+
_();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
f.update(i.nodes, i.edges);
|
|
398
|
+
};
|
|
399
|
+
return t({
|
|
400
|
+
fitView: () => {
|
|
401
|
+
f?.fitView(20);
|
|
402
|
+
},
|
|
403
|
+
resetLayout: () => {
|
|
404
|
+
f?.resetLayout();
|
|
405
|
+
},
|
|
406
|
+
switchLayout: (e) => {
|
|
407
|
+
f?.switchLayout(e);
|
|
408
|
+
},
|
|
409
|
+
getLayoutType: () => f?.getLayoutType() ?? "force",
|
|
410
|
+
focusNode: (e) => {
|
|
411
|
+
f?.focusNode(e);
|
|
412
|
+
}
|
|
413
|
+
}), m(() => [i.nodes, i.edges], () => {
|
|
414
|
+
f ? v() : _();
|
|
415
|
+
}, { deep: !0 }), m(() => i.layoutType, (e) => {
|
|
416
|
+
f?.switchLayout(e);
|
|
417
|
+
}), s(() => {
|
|
418
|
+
(i.nodes.length > 0 || i.edges.length > 0) && _(), p = new ResizeObserver((e) => {
|
|
419
|
+
for (let t of e) {
|
|
420
|
+
let { width: e, height: n } = t.contentRect;
|
|
421
|
+
h && clearTimeout(h), h = setTimeout(() => {
|
|
422
|
+
e > 0 && n > 0 && f ? f.resize(e, n) : e > 0 && n > 0 && _();
|
|
423
|
+
}, 100);
|
|
424
|
+
}
|
|
425
|
+
}), d.value && (p.observe(d.value), (i.nodes.length > 0 || i.edges.length > 0) && setTimeout(() => {
|
|
426
|
+
f || _();
|
|
427
|
+
}, 100));
|
|
428
|
+
}), c(() => {
|
|
429
|
+
h && clearTimeout(h), p?.disconnect(), p = null, f?.destroy(), f = null;
|
|
430
|
+
}), (e, t) => (l(), n("div", {
|
|
431
|
+
ref_key: "containerRef",
|
|
432
|
+
ref: d,
|
|
433
|
+
class: "graph-container"
|
|
434
|
+
}, null, 512));
|
|
435
|
+
}
|
|
436
|
+
}), C = (e, t) => {
|
|
437
|
+
let n = e.__vccOpts || e;
|
|
438
|
+
for (let [e, r] of t) n[e] = r;
|
|
439
|
+
return n;
|
|
440
|
+
}, w = /*#__PURE__*/ C(S, [["__scopeId", "data-v-232ee924"]]), T = [
|
|
441
|
+
"title",
|
|
442
|
+
"aria-label",
|
|
443
|
+
"aria-expanded"
|
|
444
|
+
], E = { class: "kg-btn-group" }, D = ["title"], O = ["value"], k = ["label"], A = ["value"], j = /*#__PURE__*/ C(/* @__PURE__ */ i({
|
|
445
|
+
__name: "GraphToolbar",
|
|
446
|
+
props: { layoutType: { default: "force" } },
|
|
447
|
+
emits: [
|
|
448
|
+
"fitView",
|
|
449
|
+
"resetLayout",
|
|
450
|
+
"switchLayout",
|
|
451
|
+
"switchToForce",
|
|
452
|
+
"switchToCircular"
|
|
453
|
+
],
|
|
454
|
+
setup(i, { emit: a }) {
|
|
455
|
+
let s = i, c = a, g = u(!1), _ = u(s.layoutType), v = [{
|
|
456
|
+
category: "network",
|
|
457
|
+
label: "关系网络"
|
|
458
|
+
}, {
|
|
459
|
+
category: "hierarchy",
|
|
460
|
+
label: "层级与树形"
|
|
461
|
+
}], b = t(() => v.map((e) => ({
|
|
462
|
+
...e,
|
|
463
|
+
options: y.filter((t) => t.category === e.category)
|
|
464
|
+
})).filter((e) => e.options.length > 0)), x = t(() => y.find((e) => e.value === _.value)), S = (e) => {
|
|
465
|
+
let t = e.target.value;
|
|
466
|
+
y.some((e) => e.value === t) && (_.value = t, c("switchLayout", t), t === "force" && c("switchToForce"), t === "circular" && c("switchToCircular"));
|
|
467
|
+
};
|
|
468
|
+
m(() => s.layoutType, (e) => {
|
|
469
|
+
_.value = e;
|
|
470
|
+
});
|
|
471
|
+
let C = () => {
|
|
472
|
+
g.value = !g.value;
|
|
473
|
+
};
|
|
474
|
+
return (t, i) => (l(), n("div", { class: o(["kg-toolbar", { "kg-collapsed": g.value }]) }, [r("button", {
|
|
475
|
+
onClick: C,
|
|
476
|
+
class: "kg-btn kg-toggle",
|
|
477
|
+
title: g.value ? "展开工具栏" : "收起工具栏",
|
|
478
|
+
"aria-label": g.value ? "展开工具栏" : "收起工具栏",
|
|
479
|
+
"aria-expanded": !g.value
|
|
480
|
+
}, f(g.value ? "▶" : "◀"), 9, T), h(r("div", E, [
|
|
481
|
+
r("button", {
|
|
482
|
+
onClick: i[0] ||= (e) => c("fitView"),
|
|
483
|
+
class: "kg-btn",
|
|
484
|
+
title: "适应画布"
|
|
485
|
+
}, [...i[2] ||= [r("span", { class: "kg-icon" }, "⊡", -1), r("span", { class: "kg-btn-label" }, "适应画布", -1)]]),
|
|
486
|
+
r("button", {
|
|
487
|
+
onClick: i[1] ||= (e) => c("resetLayout"),
|
|
488
|
+
class: "kg-btn",
|
|
489
|
+
title: "重置布局"
|
|
490
|
+
}, [...i[3] ||= [r("span", { class: "kg-icon" }, "↻", -1), r("span", { class: "kg-btn-label" }, "重置布局", -1)]]),
|
|
491
|
+
r("label", {
|
|
492
|
+
class: "kg-layout-control",
|
|
493
|
+
title: x.value?.description
|
|
494
|
+
}, [i[4] ||= r("span", { class: "kg-layout-label" }, "图类型", -1), r("select", {
|
|
495
|
+
class: "kg-layout-select",
|
|
496
|
+
value: _.value,
|
|
497
|
+
"aria-label": "选择图布局",
|
|
498
|
+
onChange: S
|
|
499
|
+
}, [(l(!0), n(e, null, d(b.value, (t) => (l(), n("optgroup", {
|
|
500
|
+
key: t.category,
|
|
501
|
+
label: t.label
|
|
502
|
+
}, [(l(!0), n(e, null, d(t.options, (e) => (l(), n("option", {
|
|
503
|
+
key: e.value,
|
|
504
|
+
value: e.value
|
|
505
|
+
}, f(e.label), 9, A))), 128))], 8, k))), 128))], 40, O)], 8, D)
|
|
506
|
+
], 512), [[p, !g.value]])], 2));
|
|
507
|
+
}
|
|
508
|
+
}), [["__scopeId", "data-v-f8202e9e"]]), M = (e) => {
|
|
509
|
+
e.component("GraphVisualization", w), e.component("GraphToolbar", j);
|
|
510
|
+
}, N = {
|
|
511
|
+
install: M,
|
|
512
|
+
version: "0.1.0"
|
|
513
|
+
};
|
|
514
|
+
//#endregion
|
|
515
|
+
export { y as GRAPH_LAYOUT_OPTIONS, j as GraphToolbar, w as GraphVisualization, x as KnowledgeGraph, N as default, M as install };
|
|
516
|
+
|
|
517
|
+
//# sourceMappingURL=vue-knowledge-graph.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue-knowledge-graph.mjs","names":[],"sources":["../src/module/g6Graph.ts","../src/components/GraphVisualization.vue","../src/components/GraphVisualization.vue","../src/components/GraphToolbar.vue","../src/components/GraphToolbar.vue","../src/index.ts"],"sourcesContent":["import {\n CanvasEvent,\n Graph,\n NodeEvent,\n type Canvas,\n type EdgeData,\n type GraphOptions,\n type IElementEvent,\n type IElementDragEvent,\n type IPointerEvent,\n type NodeData,\n type SingleLayoutOptions,\n} from '@antv/g6'\nimport { nextTick } from 'vue'\nimport type { GraphEdge, GraphNode } from '../types'\n\nexport type LayoutType =\n | 'force'\n | 'circular'\n | 'concentric'\n | 'antv-dagre'\n | 'indented'\n | 'mindmap'\n\nexport type GraphLayoutCategory = 'network' | 'hierarchy'\n\nexport interface GraphLayoutOption {\n value: LayoutType\n label: string\n description: string\n category: GraphLayoutCategory\n}\n\nexport const GRAPH_LAYOUT_OPTIONS: readonly GraphLayoutOption[] = [\n {\n value: 'force',\n label: '力导向',\n description: '通用网络布局,适合结构未知或关系密集的图谱',\n category: 'network',\n },\n {\n value: 'circular',\n label: '环形',\n description: '节点沿圆环排列,适合观察整体结构',\n category: 'network',\n },\n {\n value: 'concentric',\n label: '同心圆',\n description: '按连接度分层,突出核心节点和外围节点',\n category: 'network',\n },\n {\n value: 'antv-dagre',\n label: '有向层级',\n description: '按边的方向排列层级,适合 DAG 或流程数据',\n category: 'hierarchy',\n },\n {\n value: 'indented',\n label: '缩进树',\n description: '目录式层级布局,适合近树结构',\n category: 'hierarchy',\n },\n {\n value: 'mindmap',\n label: '思维导图',\n description: '从中心向两侧分支,适合近树结构',\n category: 'hierarchy',\n },\n]\n\nexport interface GraphInitOptions {\n container: HTMLElement\n width?: number\n height?: number\n layoutType?: LayoutType\n onNodeClick?: (nodeId: string) => void\n}\n\n/** 负责 G6 实例的生命周期和通用视口操作。 */\nexport class BaseG6Graph {\n protected graph: Graph | null = null\n protected container: HTMLElement | null = null\n\n private resizeHandler: (() => void) | null = null\n\n protected createGraph(\n config: Omit<GraphOptions, 'container' | 'width' | 'height'>,\n options: GraphInitOptions,\n ) {\n const { container, width, height } = options\n\n if (this.graph) this.destroy()\n\n this.container = container\n this.graph = new Graph({\n container,\n width: width || container.offsetWidth,\n height: height || container.offsetHeight,\n ...config,\n })\n this.setupResizeHandler()\n }\n\n private setupResizeHandler() {\n this.cleanupResizeHandler()\n this.resizeHandler = () => {\n if (!this.container) return\n this.resize(this.container.offsetWidth, this.container.offsetHeight)\n }\n window.addEventListener('resize', this.resizeHandler)\n }\n\n private cleanupResizeHandler() {\n if (!this.resizeHandler) return\n window.removeEventListener('resize', this.resizeHandler)\n this.resizeHandler = null\n }\n\n protected run(action: Promise<unknown>) {\n void action.catch(() => undefined)\n }\n\n public hasGraph(): boolean {\n return this.graph !== null\n }\n\n public resize(width: number, height: number) {\n if (!this.graph || width <= 0 || height <= 0) return\n const [currentWidth, currentHeight] = this.graph.getSize()\n if (currentWidth === width && currentHeight === height) return\n this.graph.resize(width, height)\n }\n\n public fitView(_padding = 20) {\n if (this.graph) this.run(this.graph.fitView({ when: 'always' }, false))\n }\n\n public destroy() {\n this.cleanupResizeHandler()\n this.graph?.destroy()\n this.graph = null\n this.container = null\n }\n}\n\n/** 知识图谱实现:实体/关系配色、布局切换和图谱交互。 */\nexport class KnowledgeGraph extends BaseG6Graph {\n private currentLayoutType: LayoutType = 'force'\n private nodeClickHandler: ((nodeId: string) => void) | null = null\n private canvasElement: HTMLCanvasElement | null = null\n private canvasPointerDownHandler: ((event: IPointerEvent) => void) | null = null\n private canvasPointerMoveHandler: ((event: PointerEvent) => void) | null = null\n private canvasPointerUpHandler: ((event: PointerEvent) => void) | null = null\n private canvasPointerId: number | null = null\n private lastCanvasPoint = { x: 0, y: 0 }\n private isCanvasDragging = false\n private canvasDefaultCursor: NonNullable<ReturnType<Canvas['getConfig']>['cursor']> = 'default'\n\n private getLayoutConfig(\n type: LayoutType,\n width: number,\n height: number,\n ): SingleLayoutOptions {\n const center: [number, number] = [width / 2, height / 2]\n const getNodeWidth = (node: { label?: string }) =>\n Math.min(Math.max((node.label?.length ?? 4) * 14 + 28, 72), 220)\n const getNodeHeight = () => 42\n\n switch (type) {\n case 'circular':\n return {\n type,\n center,\n radius: Math.min(width, height) * 0.35,\n startAngle: Math.PI / 2,\n endAngle: Math.PI * 2.5,\n ordering: 'degree',\n }\n case 'concentric':\n return {\n type,\n center,\n preventOverlap: true,\n nodeSize: 42,\n sortBy: 'degree',\n equidistant: true,\n }\n case 'antv-dagre':\n return {\n type,\n rankdir: 'TB',\n align: 'UL',\n nodesep: 42,\n ranksep: 110,\n }\n case 'indented':\n return {\n type,\n direction: 'LR',\n indent: 36,\n getWidth: getNodeWidth,\n getHeight: getNodeHeight,\n getHGap: () => 18,\n getVGap: () => 12,\n }\n case 'mindmap':\n return {\n type,\n direction: 'H',\n preLayout: false,\n getWidth: getNodeWidth,\n getHeight: getNodeHeight,\n getHGap: () => 30,\n getVGap: () => 18,\n }\n case 'force':\n default:\n return {\n type: 'force',\n center,\n linkDistance: 280,\n nodeStrength: 1000,\n edgeStrength: 200,\n gravity: 10,\n }\n }\n }\n\n private getColorByType(entityType?: string): string {\n const colors: Record<string, string> = {\n 人物: '#ff4d4f',\n 地点: '#52c41a',\n 组织: '#1890ff',\n 概念: '#faad14',\n 事件: '#722ed1',\n entity: '#13c2c2',\n }\n return colors[entityType || 'entity'] || '#13c2c2'\n }\n\n private getColorByRelation(relationType?: string): string {\n const colors: Record<string, string> = {\n 因果: '#e74c3c',\n 所属: '#2ecc71',\n 引用: '#3498db',\n 相关: '#9b59b6',\n }\n return colors[relationType || '相关'] || '#9b59b6'\n }\n\n public init(options: GraphInitOptions, nodes: GraphNode[], edges: GraphEdge[]) {\n const width = options.width || options.container.offsetWidth\n const height = options.height || options.container.offsetHeight\n const data = this.processData(nodes, edges)\n\n this.currentLayoutType = options.layoutType ?? 'force'\n this.createGraph(\n {\n data,\n animation: false,\n zoomRange: [0.1, 5],\n layout: this.getLayoutConfig(this.currentLayoutType, width, height),\n behaviors: [\n { type: 'zoom-canvas', sensitivity: 1 },\n { type: 'drag-element' },\n { type: 'hover-activate', degree: 1 },\n { type: 'click-select', degree: 1 },\n ],\n node: {\n type: 'circle',\n style: {\n cursor: 'pointer',\n labelPlacement: 'bottom',\n labelFill: '#000000',\n labelFontSize: 16,\n labelFontWeight: 'bold',\n },\n state: {\n active: { lineWidth: 3, shadowBlur: 12, shadowColor: '#40a9ff' },\n selected: { lineWidth: 4, stroke: '#ff4d4f' },\n inactive: { opacity: 0.25, labelOpacity: 0.35 },\n },\n },\n edge: {\n type: 'line',\n style: {\n cursor: 'pointer',\n endArrow: true,\n labelAutoRotate: true,\n labelFontSize: 12,\n },\n state: {\n active: { lineWidth: 3 },\n selected: { lineWidth: 3, stroke: '#ff4d4f' },\n inactive: { opacity: 0.2, labelOpacity: 0.2 },\n },\n },\n },\n options,\n )\n\n if (!this.graph) return\n this.nodeClickHandler = options.onNodeClick ?? null\n this.setupEvents()\n this.run(this.graph.render().then(() => {\n this.setupCanvasDrag()\n this.scheduleFitView()\n }))\n }\n\n public update(nodes: GraphNode[], edges: GraphEdge[]) {\n if (!this.graph) return\n this.graph.setData(this.processData(nodes, edges))\n this.run(this.graph.render().then(() => this.scheduleFitView()))\n }\n\n public resetLayout() {\n if (!this.graph) return\n\n const nodes = this.graph.getNodeData().map((node) => {\n const style = { ...node.style }\n delete style.x\n delete style.y\n return { ...node, style }\n })\n this.graph.setData({ nodes, edges: this.graph.getEdgeData() })\n this.graph.setLayout(this.getCurrentLayout())\n this.run(this.graph.layout().then(() => this.scheduleFitView()))\n }\n\n public switchLayout(type: LayoutType) {\n if (!this.graph || !this.container) return\n this.currentLayoutType = type\n this.graph.setLayout(this.getCurrentLayout())\n this.run(this.graph.layout().then(() => this.scheduleFitView()))\n }\n\n public getLayoutType(): LayoutType {\n return this.currentLayoutType\n }\n\n public focusNode(nodeId: string) {\n if (!this.graph || !this.graph.getNodeData().some(({ id }) => id === nodeId)) return\n this.run(this.graph.focusElement(nodeId, { duration: 500, easing: 'ease-in-out' }))\n }\n\n private getCurrentLayout() {\n const width = this.container?.offsetWidth || this.graph?.getSize()[0] || 0\n const height = this.container?.offsetHeight || this.graph?.getSize()[1] || 0\n return this.getLayoutConfig(this.currentLayoutType, width, height)\n }\n\n private processData(nodes: GraphNode[], edges: GraphEdge[]): {\n nodes: NodeData[]\n edges: EdgeData[]\n } {\n const degree: Record<string, number> = {}\n for (const edge of edges) {\n degree[edge.source] = (degree[edge.source] || 0) + 1\n degree[edge.target] = (degree[edge.target] || 0) + 1\n }\n\n return {\n nodes: nodes.map((node) => ({\n id: node.id,\n data: {\n ...node,\n label: node.label,\n entityType: node.type,\n },\n style: {\n x: node.x,\n y: node.y,\n size: node.size || Math.min(15 + (degree[node.id] || 0) * 3, 40),\n fill: node.style?.fill || this.getColorByType(node.type),\n stroke: node.style?.stroke || '#ffffff',\n lineWidth: node.style?.lineWidth || 2,\n labelText: node.label,\n },\n })),\n edges: edges.map((edge, index) => {\n const color = edge.style?.stroke || this.getColorByRelation(edge.relation_type)\n return {\n id: edge.id || `edge-${edge.source}-${edge.target}-${index}`,\n source: edge.source,\n target: edge.target,\n data: { label: edge.label, relationType: edge.relation_type },\n style: {\n stroke: color,\n lineWidth: edge.style?.lineWidth || 2,\n endArrowFill: color,\n labelText: edge.label,\n labelFill: color,\n },\n }\n }),\n }\n }\n\n private scheduleFitView() {\n const fit = () => {\n nextTick(() => {\n requestAnimationFrame(() => this.fitView(20))\n })\n }\n fit()\n window.setTimeout(fit, 300)\n window.setTimeout(fit, 1200)\n }\n\n private setupEvents() {\n if (!this.graph || !this.container) return\n const graph = this.graph\n\n graph.on(NodeEvent.CLICK, (event: IElementEvent) => {\n this.nodeClickHandler?.(event.target.id)\n })\n\n graph.on(NodeEvent.DRAG, (event: IElementDragEvent) => {\n const nodeId = event.target.id\n const factor = 0.3\n const updates = graph.getNeighborNodesData(nodeId).map((node) => ({\n id: node.id,\n style: {\n x: (node.style?.x || 0) + event.dx * factor,\n y: (node.style?.y || 0) + event.dy * factor,\n },\n }))\n if (updates.length === 0) return\n graph.updateNodeData(updates)\n this.run(graph.draw())\n })\n }\n\n private setupCanvasDrag() {\n if (!this.graph) return\n const graph = this.graph\n const canvas = graph.getCanvas()\n const canvasElement = canvas.getContextService().getDomElement() as HTMLCanvasElement | null\n if (!canvasElement) return\n\n this.canvasElement = canvasElement\n this.canvasDefaultCursor = canvas.getConfig().cursor || 'default'\n this.canvasPointerDownHandler = (event) => {\n if (event.button !== 0 || this.canvasPointerId !== null) return\n\n this.canvasPointerId = event.pointerId\n this.isCanvasDragging = true\n this.lastCanvasPoint = { x: event.clientX, y: event.clientY }\n canvasElement.setPointerCapture(event.pointerId)\n canvas.setCursor('grabbing')\n }\n this.canvasPointerMoveHandler = (event) => {\n if (\n !this.isCanvasDragging ||\n (this.canvasPointerId !== null && event.pointerId !== this.canvasPointerId)\n ) {\n return\n }\n\n const offset: [number, number] = [\n event.clientX - this.lastCanvasPoint.x,\n event.clientY - this.lastCanvasPoint.y,\n ]\n this.lastCanvasPoint = { x: event.clientX, y: event.clientY }\n this.run(graph.translateBy(offset, false))\n event.preventDefault?.()\n }\n this.canvasPointerUpHandler = (event) => {\n if (this.canvasPointerId !== null && event.pointerId !== this.canvasPointerId) {\n return\n }\n\n const wasDragging = this.isCanvasDragging\n this.isCanvasDragging = false\n this.canvasPointerId = null\n if (wasDragging && canvasElement.hasPointerCapture?.(event.pointerId)) {\n canvasElement.releasePointerCapture(event.pointerId)\n }\n if (wasDragging) canvas.setCursor(this.canvasDefaultCursor)\n }\n\n graph.on(CanvasEvent.POINTER_DOWN, this.canvasPointerDownHandler)\n window.addEventListener('pointermove', this.canvasPointerMoveHandler, true)\n window.addEventListener('pointerup', this.canvasPointerUpHandler, true)\n window.addEventListener('pointercancel', this.canvasPointerUpHandler, true)\n }\n\n public override destroy() {\n if (this.graph && this.canvasPointerDownHandler) {\n this.graph.off(CanvasEvent.POINTER_DOWN, this.canvasPointerDownHandler)\n }\n if (this.canvasPointerMoveHandler) {\n window.removeEventListener('pointermove', this.canvasPointerMoveHandler, true)\n }\n if (this.canvasPointerUpHandler) {\n window.removeEventListener('pointerup', this.canvasPointerUpHandler, true)\n window.removeEventListener('pointercancel', this.canvasPointerUpHandler, true)\n }\n\n this.canvasElement = null\n this.canvasPointerDownHandler = null\n this.canvasPointerMoveHandler = null\n this.canvasPointerUpHandler = null\n this.canvasPointerId = null\n this.isCanvasDragging = false\n this.nodeClickHandler = null\n super.destroy()\n }\n}\n","<template>\n <div\n ref=\"containerRef\"\n class=\"graph-container\"\n ></div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { GraphNode, GraphEdge } from '../types'\nimport { KnowledgeGraph, type LayoutType } from '../module/g6Graph'\n\ninterface Props {\n nodes: GraphNode[]\n edges: GraphEdge[]\n width?: number\n height?: number\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 0,\n height: 0,\n layoutType: 'force',\n})\n\nconst emit = defineEmits<{\n nodeClick: [node: GraphNode]\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nlet graph: KnowledgeGraph | null = null\nlet resizeObserver: ResizeObserver | null = null\nlet resizeTimeout: ReturnType<typeof setTimeout> | null = null\n\nconst getContainerSize = () => {\n if (!containerRef.value) return { width: 0, height: 0 }\n return {\n width: containerRef.value.clientWidth,\n height: containerRef.value.clientHeight\n }\n}\n\nconst initGraph = () => {\n if (!containerRef.value) return\n\n // 确保容器有高度\n if (containerRef.value.clientHeight === 0) {\n containerRef.value.style.height = '100%'\n }\n\n nextTick(() => {\n const { width, height } = getContainerSize()\n if (width === 0 || height === 0) {\n return\n }\n\n if (!graph) {\n graph = new KnowledgeGraph()\n }\n\n graph.init(\n {\n container: containerRef.value!,\n width: props.width || width,\n height: props.height || height,\n layoutType: props.layoutType,\n onNodeClick: (nodeId) => {\n const node = props.nodes.find((item) => item.id === nodeId)\n if (node) emit('nodeClick', node)\n },\n },\n props.nodes,\n props.edges\n )\n })\n}\n\nconst updateGraph = () => {\n if (!graph) {\n initGraph()\n return\n }\n\n graph.update(props.nodes, props.edges)\n}\n\nconst fitView = () => {\n graph?.fitView(20)\n}\n\nconst resetLayout = () => {\n graph?.resetLayout()\n}\n\nconst switchLayout = (type: LayoutType) => {\n graph?.switchLayout(type)\n}\n\nconst getLayoutType = () => graph?.getLayoutType() ?? 'force'\n\nconst focusNode = (nodeId: string) => {\n graph?.focusNode(nodeId)\n}\n\ndefineExpose({\n fitView,\n resetLayout,\n switchLayout,\n getLayoutType,\n focusNode\n})\n\nwatch(\n () => [props.nodes, props.edges],\n () => {\n if (graph) {\n updateGraph()\n } else {\n initGraph()\n }\n },\n { deep: true }\n)\n\nwatch(\n () => props.layoutType,\n (type) => {\n graph?.switchLayout(type)\n }\n)\n\nonMounted(() => {\n if (props.nodes.length > 0 || props.edges.length > 0) {\n initGraph()\n }\n\n // 使用 ResizeObserver 监听容器尺寸变化\n resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n // 防止 resize 循环:使用防抖\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeTimeout = setTimeout(() => {\n if (width > 0 && height > 0 && graph) {\n graph.resize(width, height)\n } else if (width > 0 && height > 0) {\n // 首次初始化\n initGraph()\n }\n }, 100)\n }\n })\n\n if (containerRef.value) {\n resizeObserver.observe(containerRef.value)\n\n // 如果数据已存在但尺寸为 0,延迟尝试初始化\n if ((props.nodes.length > 0 || props.edges.length > 0)) {\n setTimeout(() => {\n if (!graph) initGraph()\n }, 100)\n }\n }\n})\n\nonUnmounted(() => {\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeObserver?.disconnect()\n resizeObserver = null\n graph?.destroy()\n graph = null\n})\n</script>\n\n<style scoped>\n.graph-container {\n width: 100%;\n height: 100%;\n}\n</style>\n","<template>\n <div\n ref=\"containerRef\"\n class=\"graph-container\"\n ></div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { GraphNode, GraphEdge } from '../types'\nimport { KnowledgeGraph, type LayoutType } from '../module/g6Graph'\n\ninterface Props {\n nodes: GraphNode[]\n edges: GraphEdge[]\n width?: number\n height?: number\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 0,\n height: 0,\n layoutType: 'force',\n})\n\nconst emit = defineEmits<{\n nodeClick: [node: GraphNode]\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nlet graph: KnowledgeGraph | null = null\nlet resizeObserver: ResizeObserver | null = null\nlet resizeTimeout: ReturnType<typeof setTimeout> | null = null\n\nconst getContainerSize = () => {\n if (!containerRef.value) return { width: 0, height: 0 }\n return {\n width: containerRef.value.clientWidth,\n height: containerRef.value.clientHeight\n }\n}\n\nconst initGraph = () => {\n if (!containerRef.value) return\n\n // 确保容器有高度\n if (containerRef.value.clientHeight === 0) {\n containerRef.value.style.height = '100%'\n }\n\n nextTick(() => {\n const { width, height } = getContainerSize()\n if (width === 0 || height === 0) {\n return\n }\n\n if (!graph) {\n graph = new KnowledgeGraph()\n }\n\n graph.init(\n {\n container: containerRef.value!,\n width: props.width || width,\n height: props.height || height,\n layoutType: props.layoutType,\n onNodeClick: (nodeId) => {\n const node = props.nodes.find((item) => item.id === nodeId)\n if (node) emit('nodeClick', node)\n },\n },\n props.nodes,\n props.edges\n )\n })\n}\n\nconst updateGraph = () => {\n if (!graph) {\n initGraph()\n return\n }\n\n graph.update(props.nodes, props.edges)\n}\n\nconst fitView = () => {\n graph?.fitView(20)\n}\n\nconst resetLayout = () => {\n graph?.resetLayout()\n}\n\nconst switchLayout = (type: LayoutType) => {\n graph?.switchLayout(type)\n}\n\nconst getLayoutType = () => graph?.getLayoutType() ?? 'force'\n\nconst focusNode = (nodeId: string) => {\n graph?.focusNode(nodeId)\n}\n\ndefineExpose({\n fitView,\n resetLayout,\n switchLayout,\n getLayoutType,\n focusNode\n})\n\nwatch(\n () => [props.nodes, props.edges],\n () => {\n if (graph) {\n updateGraph()\n } else {\n initGraph()\n }\n },\n { deep: true }\n)\n\nwatch(\n () => props.layoutType,\n (type) => {\n graph?.switchLayout(type)\n }\n)\n\nonMounted(() => {\n if (props.nodes.length > 0 || props.edges.length > 0) {\n initGraph()\n }\n\n // 使用 ResizeObserver 监听容器尺寸变化\n resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n // 防止 resize 循环:使用防抖\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeTimeout = setTimeout(() => {\n if (width > 0 && height > 0 && graph) {\n graph.resize(width, height)\n } else if (width > 0 && height > 0) {\n // 首次初始化\n initGraph()\n }\n }, 100)\n }\n })\n\n if (containerRef.value) {\n resizeObserver.observe(containerRef.value)\n\n // 如果数据已存在但尺寸为 0,延迟尝试初始化\n if ((props.nodes.length > 0 || props.edges.length > 0)) {\n setTimeout(() => {\n if (!graph) initGraph()\n }, 100)\n }\n }\n})\n\nonUnmounted(() => {\n if (resizeTimeout) clearTimeout(resizeTimeout)\n resizeObserver?.disconnect()\n resizeObserver = null\n graph?.destroy()\n graph = null\n})\n</script>\n\n<style scoped>\n.graph-container {\n width: 100%;\n height: 100%;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n GRAPH_LAYOUT_OPTIONS,\n type GraphLayoutCategory,\n type LayoutType,\n} from '../module/g6Graph'\n\ninterface Props {\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n layoutType: 'force'\n})\n\nconst emit = defineEmits<{\n fitView: []\n resetLayout: []\n switchLayout: [type: LayoutType]\n switchToForce: []\n switchToCircular: []\n}>()\n\nconst collapsed = ref(false)\nconst selectedLayout = ref<LayoutType>(props.layoutType)\n\nconst layoutGroups: Array<{ category: GraphLayoutCategory; label: string }> = [\n { category: 'network', label: '关系网络' },\n { category: 'hierarchy', label: '层级与树形' },\n]\n\nconst groupedLayouts = computed(() =>\n layoutGroups\n .map((group) => ({\n ...group,\n options: GRAPH_LAYOUT_OPTIONS.filter((layout) => layout.category === group.category),\n }))\n .filter((group) => group.options.length > 0)\n)\n\nconst currentLayout = computed(\n () => GRAPH_LAYOUT_OPTIONS.find((layout) => layout.value === selectedLayout.value)\n)\n\nconst handleLayoutChange = (event: Event) => {\n const type = (event.target as HTMLSelectElement).value as LayoutType\n if (!GRAPH_LAYOUT_OPTIONS.some((layout) => layout.value === type)) return\n\n selectedLayout.value = type\n emit('switchLayout', type)\n if (type === 'force') emit('switchToForce')\n if (type === 'circular') emit('switchToCircular')\n}\n\nwatch(\n () => props.layoutType,\n (type) => {\n selectedLayout.value = type\n }\n)\n\nconst toggleCollapse = () => {\n collapsed.value = !collapsed.value\n}\n</script>\n\n<template>\n <div\n class=\"kg-toolbar\"\n :class=\"{ 'kg-collapsed': collapsed }\"\n >\n <button\n @click=\"toggleCollapse\"\n class=\"kg-btn kg-toggle\"\n :title=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-label=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-expanded=\"!collapsed\"\n >\n {{ collapsed ? '▶' : '◀' }}\n </button>\n <div\n class=\"kg-btn-group\"\n v-show=\"!collapsed\"\n >\n <button\n @click=\"emit('fitView')\"\n class=\"kg-btn\"\n title=\"适应画布\"\n >\n <span class=\"kg-icon\">⊡</span>\n <span class=\"kg-btn-label\">适应画布</span>\n </button>\n <button\n @click=\"emit('resetLayout')\"\n class=\"kg-btn\"\n title=\"重置布局\"\n >\n <span class=\"kg-icon\">↻</span>\n <span class=\"kg-btn-label\">重置布局</span>\n </button>\n <label\n class=\"kg-layout-control\"\n :title=\"currentLayout?.description\"\n >\n <span class=\"kg-layout-label\">图类型</span>\n <select\n class=\"kg-layout-select\"\n :value=\"selectedLayout\"\n aria-label=\"选择图布局\"\n @change=\"handleLayoutChange\"\n >\n <optgroup\n v-for=\"group in groupedLayouts\"\n :key=\"group.category\"\n :label=\"group.label\"\n >\n <option\n v-for=\"layout in group.options\"\n :key=\"layout.value\"\n :value=\"layout.value\"\n >\n {{ layout.label }}\n </option>\n </optgroup>\n </select>\n </label>\n </div>\n </div>\n</template>\n\n<style scoped>\n.kg-toolbar {\n position: absolute;\n top: 16px;\n right: 16px;\n background: #fff;\n border: 1px solid #e2e8f0;\n border-radius: 10px;\n padding: 8px;\n z-index: 100;\n display: flex;\n gap: 6px;\n align-items: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n font-family: 'Segoe UI', 'PingFang SC', system-ui, sans-serif;\n}\n\n.kg-toolbar:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n}\n\n.kg-toolbar.kg-collapsed {\n padding: 6px;\n right: 12px;\n}\n\n.kg-btn-group {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.kg-btn {\n padding: 6px 12px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: #334155;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 5px;\n outline: none;\n}\n\n.kg-btn:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n.kg-btn:focus-visible,\n.kg-toggle:focus-visible {\n outline: 2px solid #60a5fa;\n outline-offset: 2px;\n}\n\n.kg-icon {\n font-size: 13px;\n line-height: 1;\n}\n\n.kg-layout-control {\n display: flex;\n align-items: center;\n gap: 7px;\n padding-left: 10px;\n border-left: 1px solid #e2e8f0;\n}\n\n.kg-layout-label {\n color: #64748b;\n font-size: 11px;\n font-weight: 650;\n white-space: nowrap;\n}\n\n.kg-layout-select {\n width: 128px;\n min-height: 31px;\n padding: 5px 26px 5px 9px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n background: #fff;\n color: #1e293b;\n font-size: 12px;\n outline: none;\n}\n\n.kg-layout-select:hover,\n.kg-layout-select:focus {\n border-color: #60a5fa;\n box-shadow: 0 0 0 2px #dbeafe;\n}\n\n.kg-toggle {\n padding: 4px 8px;\n background: transparent;\n color: #64748b;\n border: none;\n border-radius: 4px;\n font-size: 11px;\n}\n\n.kg-toggle:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n@media (max-width: 640px) {\n .kg-toolbar {\n max-width: calc(100% - 24px);\n }\n\n .kg-btn-group {\n gap: 4px;\n }\n\n .kg-btn {\n padding: 6px 8px;\n }\n\n .kg-btn-label {\n display: none;\n }\n\n .kg-layout-control {\n padding-left: 6px;\n gap: 4px;\n }\n\n .kg-layout-label {\n display: none;\n }\n\n .kg-layout-select {\n width: 120px;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n GRAPH_LAYOUT_OPTIONS,\n type GraphLayoutCategory,\n type LayoutType,\n} from '../module/g6Graph'\n\ninterface Props {\n layoutType?: LayoutType\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n layoutType: 'force'\n})\n\nconst emit = defineEmits<{\n fitView: []\n resetLayout: []\n switchLayout: [type: LayoutType]\n switchToForce: []\n switchToCircular: []\n}>()\n\nconst collapsed = ref(false)\nconst selectedLayout = ref<LayoutType>(props.layoutType)\n\nconst layoutGroups: Array<{ category: GraphLayoutCategory; label: string }> = [\n { category: 'network', label: '关系网络' },\n { category: 'hierarchy', label: '层级与树形' },\n]\n\nconst groupedLayouts = computed(() =>\n layoutGroups\n .map((group) => ({\n ...group,\n options: GRAPH_LAYOUT_OPTIONS.filter((layout) => layout.category === group.category),\n }))\n .filter((group) => group.options.length > 0)\n)\n\nconst currentLayout = computed(\n () => GRAPH_LAYOUT_OPTIONS.find((layout) => layout.value === selectedLayout.value)\n)\n\nconst handleLayoutChange = (event: Event) => {\n const type = (event.target as HTMLSelectElement).value as LayoutType\n if (!GRAPH_LAYOUT_OPTIONS.some((layout) => layout.value === type)) return\n\n selectedLayout.value = type\n emit('switchLayout', type)\n if (type === 'force') emit('switchToForce')\n if (type === 'circular') emit('switchToCircular')\n}\n\nwatch(\n () => props.layoutType,\n (type) => {\n selectedLayout.value = type\n }\n)\n\nconst toggleCollapse = () => {\n collapsed.value = !collapsed.value\n}\n</script>\n\n<template>\n <div\n class=\"kg-toolbar\"\n :class=\"{ 'kg-collapsed': collapsed }\"\n >\n <button\n @click=\"toggleCollapse\"\n class=\"kg-btn kg-toggle\"\n :title=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-label=\"collapsed ? '展开工具栏' : '收起工具栏'\"\n :aria-expanded=\"!collapsed\"\n >\n {{ collapsed ? '▶' : '◀' }}\n </button>\n <div\n class=\"kg-btn-group\"\n v-show=\"!collapsed\"\n >\n <button\n @click=\"emit('fitView')\"\n class=\"kg-btn\"\n title=\"适应画布\"\n >\n <span class=\"kg-icon\">⊡</span>\n <span class=\"kg-btn-label\">适应画布</span>\n </button>\n <button\n @click=\"emit('resetLayout')\"\n class=\"kg-btn\"\n title=\"重置布局\"\n >\n <span class=\"kg-icon\">↻</span>\n <span class=\"kg-btn-label\">重置布局</span>\n </button>\n <label\n class=\"kg-layout-control\"\n :title=\"currentLayout?.description\"\n >\n <span class=\"kg-layout-label\">图类型</span>\n <select\n class=\"kg-layout-select\"\n :value=\"selectedLayout\"\n aria-label=\"选择图布局\"\n @change=\"handleLayoutChange\"\n >\n <optgroup\n v-for=\"group in groupedLayouts\"\n :key=\"group.category\"\n :label=\"group.label\"\n >\n <option\n v-for=\"layout in group.options\"\n :key=\"layout.value\"\n :value=\"layout.value\"\n >\n {{ layout.label }}\n </option>\n </optgroup>\n </select>\n </label>\n </div>\n </div>\n</template>\n\n<style scoped>\n.kg-toolbar {\n position: absolute;\n top: 16px;\n right: 16px;\n background: #fff;\n border: 1px solid #e2e8f0;\n border-radius: 10px;\n padding: 8px;\n z-index: 100;\n display: flex;\n gap: 6px;\n align-items: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n font-family: 'Segoe UI', 'PingFang SC', system-ui, sans-serif;\n}\n\n.kg-toolbar:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n}\n\n.kg-toolbar.kg-collapsed {\n padding: 6px;\n right: 12px;\n}\n\n.kg-btn-group {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.kg-btn {\n padding: 6px 12px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: #334155;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 5px;\n outline: none;\n}\n\n.kg-btn:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n.kg-btn:focus-visible,\n.kg-toggle:focus-visible {\n outline: 2px solid #60a5fa;\n outline-offset: 2px;\n}\n\n.kg-icon {\n font-size: 13px;\n line-height: 1;\n}\n\n.kg-layout-control {\n display: flex;\n align-items: center;\n gap: 7px;\n padding-left: 10px;\n border-left: 1px solid #e2e8f0;\n}\n\n.kg-layout-label {\n color: #64748b;\n font-size: 11px;\n font-weight: 650;\n white-space: nowrap;\n}\n\n.kg-layout-select {\n width: 128px;\n min-height: 31px;\n padding: 5px 26px 5px 9px;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n background: #fff;\n color: #1e293b;\n font-size: 12px;\n outline: none;\n}\n\n.kg-layout-select:hover,\n.kg-layout-select:focus {\n border-color: #60a5fa;\n box-shadow: 0 0 0 2px #dbeafe;\n}\n\n.kg-toggle {\n padding: 4px 8px;\n background: transparent;\n color: #64748b;\n border: none;\n border-radius: 4px;\n font-size: 11px;\n}\n\n.kg-toggle:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n@media (max-width: 640px) {\n .kg-toolbar {\n max-width: calc(100% - 24px);\n }\n\n .kg-btn-group {\n gap: 4px;\n }\n\n .kg-btn {\n padding: 6px 8px;\n }\n\n .kg-btn-label {\n display: none;\n }\n\n .kg-layout-control {\n padding-left: 6px;\n gap: 4px;\n }\n\n .kg-layout-label {\n display: none;\n }\n\n .kg-layout-select {\n width: 120px;\n }\n}\n</style>\n","import type { App } from 'vue'\r\nimport GraphVisualization from './components/GraphVisualization.vue'\r\nimport GraphToolbar from './components/GraphToolbar.vue'\r\nimport { GRAPH_LAYOUT_OPTIONS, KnowledgeGraph } from './module/g6Graph'\r\n\r\n// 导出类型\r\nexport type { GraphNode, GraphEdge } from './types'\r\nexport type { GraphInitOptions } from './module/g6Graph'\r\nexport type {\r\n GraphLayoutCategory,\r\n GraphLayoutOption,\r\n LayoutType,\r\n} from './module/g6Graph'\r\n\r\n// Vue 插件 install 方法\r\nconst install = (app: App) => {\r\n app.component('GraphVisualization', GraphVisualization)\r\n app.component('GraphToolbar', GraphToolbar)\r\n}\r\n\r\n// 具名导出(ES Modules)\r\nexport {\r\n install,\r\n GRAPH_LAYOUT_OPTIONS,\r\n GraphVisualization,\r\n GraphToolbar,\r\n KnowledgeGraph,\r\n}\r\n\r\n// 自动导出(UMD)- 默认导出\r\nexport default {\r\n install,\r\n version: __PACKAGE_VERSION__,\r\n}\r\n"],"mappings":"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"}
|