@logixode/force-graph-lib 0.1.4 → 0.1.6
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/dist/force-graph-lib.js +105 -245
- package/dist/force-graph-lib.umd.cjs +1 -3
- package/dist/index.d.ts +24 -14
- package/package.json +13 -10
package/dist/force-graph-lib.js
CHANGED
|
@@ -1,133 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
3
|
-
|
|
4
|
-
(t == null || t > o.length) && (t = o.length);
|
|
5
|
-
for (var i = 0, r = Array(t); i < t; i++) r[i] = o[i];
|
|
6
|
-
return r;
|
|
7
|
-
}
|
|
8
|
-
function G(o) {
|
|
9
|
-
if (Array.isArray(o)) return o;
|
|
10
|
-
}
|
|
11
|
-
function A(o) {
|
|
12
|
-
if (Array.isArray(o)) return w(o);
|
|
13
|
-
}
|
|
14
|
-
function I(o) {
|
|
15
|
-
if (typeof Symbol < "u" && o[Symbol.iterator] != null || o["@@iterator"] != null) return Array.from(o);
|
|
16
|
-
}
|
|
17
|
-
function P(o, t) {
|
|
18
|
-
var i = o == null ? null : typeof Symbol < "u" && o[Symbol.iterator] || o["@@iterator"];
|
|
19
|
-
if (i != null) {
|
|
20
|
-
var r, n, a, s, h = [], p = !0, e = !1;
|
|
21
|
-
try {
|
|
22
|
-
if (a = (i = i.call(o)).next, t !== 0) for (; !(p = (r = a.call(i)).done) && (h.push(r.value), h.length !== t); p = !0) ;
|
|
23
|
-
} catch (l) {
|
|
24
|
-
e = !0, n = l;
|
|
25
|
-
} finally {
|
|
26
|
-
try {
|
|
27
|
-
if (!p && i.return != null && (s = i.return(), Object(s) !== s)) return;
|
|
28
|
-
} finally {
|
|
29
|
-
if (e) throw n;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return h;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function z() {
|
|
36
|
-
throw new TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
37
|
-
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`);
|
|
38
|
-
}
|
|
39
|
-
function N() {
|
|
40
|
-
throw new TypeError(`Invalid attempt to spread non-iterable instance.
|
|
41
|
-
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`);
|
|
42
|
-
}
|
|
43
|
-
function M(o, t) {
|
|
44
|
-
return G(o) || P(o, t) || L(o, t) || z();
|
|
45
|
-
}
|
|
46
|
-
function C(o) {
|
|
47
|
-
return A(o) || I(o) || L(o) || N();
|
|
48
|
-
}
|
|
49
|
-
function L(o, t) {
|
|
50
|
-
if (o) {
|
|
51
|
-
if (typeof o == "string") return w(o, t);
|
|
52
|
-
var i = {}.toString.call(o).slice(8, -1);
|
|
53
|
-
return i === "Object" && o.constructor && (i = o.constructor.name), i === "Map" || i === "Set" ? Array.from(o) : i === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i) ? w(o, t) : void 0;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function O() {
|
|
57
|
-
var o = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : function(e) {
|
|
58
|
-
return e.cluster;
|
|
59
|
-
}, t, i = [], r = v(1), n = v(0.2), a = 0, s = /* @__PURE__ */ new Map();
|
|
60
|
-
function h(e) {
|
|
61
|
-
var l = new Map(C(s.entries()).map(function(u) {
|
|
62
|
-
var d = M(u, 2), g = d[0], y = d[1];
|
|
63
|
-
return [g, W(y, r, t)];
|
|
64
|
-
})), f = new Map(C(s.entries()).map(function(u) {
|
|
65
|
-
var d = M(u, 2), g = d[0], y = d[1];
|
|
66
|
-
return [g, n(g, y)];
|
|
67
|
-
})), c = ["x", t > 1 && "y", t > 2 && "z"].filter(function(u) {
|
|
68
|
-
return u;
|
|
69
|
-
}), m = c.map(function(u) {
|
|
70
|
-
return "v".concat(u);
|
|
71
|
-
});
|
|
72
|
-
i.forEach(function(u) {
|
|
73
|
-
var d = o(u);
|
|
74
|
-
if (l.has(d)) {
|
|
75
|
-
var g = l.get(d), y = c.map(function(b) {
|
|
76
|
-
return g[b] - u[b];
|
|
77
|
-
});
|
|
78
|
-
if (!(D.apply(void 0, C(y)) <= a)) {
|
|
79
|
-
var B = f.get(d) * e;
|
|
80
|
-
m.forEach(function(b, T) {
|
|
81
|
-
return u[b] += y[T] * B;
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function p() {
|
|
88
|
-
s.clear(), i.forEach(function(e) {
|
|
89
|
-
var l = o(e);
|
|
90
|
-
s.has(l) || s.set(l, []), s.get(l).push(e);
|
|
91
|
-
}), s.forEach(function(e, l) {
|
|
92
|
-
return e.length <= 1 && s.delete(l);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return h.initialize = function(e) {
|
|
96
|
-
i = e;
|
|
97
|
-
for (var l = arguments.length, f = new Array(l > 1 ? l - 1 : 0), c = 1; c < l; c++)
|
|
98
|
-
f[c - 1] = arguments[c];
|
|
99
|
-
t = f.find(function(m) {
|
|
100
|
-
return [1, 2, 3].includes(m);
|
|
101
|
-
}) || 2, p();
|
|
102
|
-
}, h.clusterId = function(e) {
|
|
103
|
-
return arguments.length ? (o = e, p(), h) : o;
|
|
104
|
-
}, h.weight = function(e) {
|
|
105
|
-
return arguments.length ? (r = typeof e == "function" ? e : v(+e), h) : r;
|
|
106
|
-
}, h.strength = function(e) {
|
|
107
|
-
return arguments.length ? (n = typeof e == "function" ? e : v(+e), h) : n;
|
|
108
|
-
}, h.distanceMin = function(e) {
|
|
109
|
-
return arguments.length ? (a = e, h) : a;
|
|
110
|
-
}, h;
|
|
111
|
-
}
|
|
112
|
-
function D(o) {
|
|
113
|
-
var t = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0, i = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : 0;
|
|
114
|
-
return Math.sqrt(o * o + t * t + i * i);
|
|
115
|
-
}
|
|
116
|
-
function v(o) {
|
|
117
|
-
return function() {
|
|
118
|
-
return o;
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function W(o, t, i) {
|
|
122
|
-
var r = 0, n = 0, a = 0, s = 0;
|
|
123
|
-
o.forEach(function(p) {
|
|
124
|
-
var e = t(p);
|
|
125
|
-
n += p.x * e, i > 1 && (a += p.y * e), i > 2 && (s += p.z * e), r += e;
|
|
126
|
-
});
|
|
127
|
-
var h = {};
|
|
128
|
-
return h.x = n / r, i > 1 && (h.y = a / r), i > 2 && (h.z = s / r), h;
|
|
129
|
-
}
|
|
130
|
-
class x {
|
|
1
|
+
import f from "force-graph";
|
|
2
|
+
import * as c from "d3";
|
|
3
|
+
class C {
|
|
131
4
|
container;
|
|
132
5
|
graph;
|
|
133
6
|
data = { nodes: [], links: [] };
|
|
@@ -136,15 +9,16 @@ class x {
|
|
|
136
9
|
options;
|
|
137
10
|
worker = null;
|
|
138
11
|
groupBounds = /* @__PURE__ */ new Map();
|
|
139
|
-
constructor(t, i = { nodes: [], links: [] },
|
|
140
|
-
this.container = t, this.data = i, this.graph = new
|
|
141
|
-
this.nodesMap.set(
|
|
12
|
+
constructor(t, i = { nodes: [], links: [] }, o = {}) {
|
|
13
|
+
this.container = t, this.data = i, this.graph = new f(this.container), i.nodes.forEach((r) => {
|
|
14
|
+
this.nodesMap.set(r.id.toString(), r);
|
|
142
15
|
});
|
|
143
|
-
const
|
|
16
|
+
const e = {
|
|
144
17
|
labelThreshold: 1.5,
|
|
18
|
+
// Show labels when they're not bigger than the node
|
|
145
19
|
showGroups: !1,
|
|
146
|
-
...
|
|
147
|
-
},
|
|
20
|
+
...o
|
|
21
|
+
}, s = e.showGroups ? {
|
|
148
22
|
groupBy: "topic",
|
|
149
23
|
groupBorderColor: "#666",
|
|
150
24
|
groupBorderWidth: 2,
|
|
@@ -155,22 +29,12 @@ class x {
|
|
|
155
29
|
groupPadding: 20
|
|
156
30
|
} : {};
|
|
157
31
|
this.options = {
|
|
158
|
-
...
|
|
159
|
-
...
|
|
32
|
+
...e,
|
|
33
|
+
...s
|
|
160
34
|
}, this.initGraph();
|
|
161
35
|
}
|
|
162
36
|
initGraph() {
|
|
163
|
-
|
|
164
|
-
console.log("Node clicked:", t), this.options.nodeClickHandler && this.options.nodeClickHandler(t), this.focusPosition({ x: t.x, y: t.y });
|
|
165
|
-
}).cooldownTime(this.calculateCooldownTime()).d3Force(
|
|
166
|
-
"cluster",
|
|
167
|
-
this.options.cluster ? O().clusterId(this.options.cluster) : null
|
|
168
|
-
).d3Force(
|
|
169
|
-
"collide",
|
|
170
|
-
this.options.collide ? k.forceCollide().radius((t) => this.options.collide ? this.options.collide(t) : t.marker.radius) : null
|
|
171
|
-
), this.options.keepDragPosition && this.graph.onNodeDragEnd((t) => {
|
|
172
|
-
t.fx = t.x, t.fy = t.y;
|
|
173
|
-
}), this.graphData(this.data), this.applyOptions(), this.render(), this.graph.onEngineStop(() => this.graph.zoomToFit(400));
|
|
37
|
+
this.applyOptions(), this.render(), this.graphData(this.data), this.graph.onEngineStop(() => this.graph.zoomToFit(400));
|
|
174
38
|
}
|
|
175
39
|
renderer() {
|
|
176
40
|
return this.graph;
|
|
@@ -178,27 +42,31 @@ class x {
|
|
|
178
42
|
focusPosition(t = {}) {
|
|
179
43
|
if (Object.values(t).length) {
|
|
180
44
|
if (t.id) {
|
|
181
|
-
const { x: i, y:
|
|
182
|
-
i &&
|
|
45
|
+
const { x: i, y: o } = this.nodesMap.get(t.id) ?? { x: 0, y: 0 };
|
|
46
|
+
i && o && (t = { x: i, y: o });
|
|
183
47
|
}
|
|
184
48
|
t && (this.graph.centerAt(t.x, t.y, 1e3), this.graph.zoom(8, 2e3));
|
|
185
49
|
}
|
|
186
50
|
}
|
|
187
51
|
render() {
|
|
188
|
-
this.graph.d3Force("charge",
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
52
|
+
this.graph.width(this.options.width ?? 800).height(this.options.height ?? 400).cooldownTime(this.getCooldownTime()).d3Force("charge", c.forceManyBody().strength(this.options.nodeGap ?? -50)), typeof this.options.nodeClickHandler == "function" && this.graph.onNodeClick((t) => {
|
|
53
|
+
this.options.nodeClickHandler(t);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
force(t, i) {
|
|
57
|
+
return this.graph.d3Force(t, i);
|
|
192
58
|
}
|
|
193
59
|
applyOptions() {
|
|
194
|
-
this.graph.
|
|
195
|
-
t
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const s = this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
60
|
+
this.options.keepDragPosition && this.graph.onNodeDragEnd((t) => {
|
|
61
|
+
t.fx = t.x, t.fy = t.y;
|
|
62
|
+
}), this.options.pointerInteraction || this.graph.enablePointerInteraction(!1), this.graph.nodeCanvasObject((t, i, o) => {
|
|
63
|
+
t === this.data.nodes[0] && this.renderGroups(i, o);
|
|
64
|
+
const e = this.getNodeSize(t) * 2, s = typeof this.options.nodeBorderWidth == "function" ? this.options.nodeBorderWidth(t) : this.options.nodeBorderWidth;
|
|
65
|
+
i.beginPath(), i.arc(t.x || 0, t.y || 0, e, 0, 2 * Math.PI), i.fillStyle = this.getNodeColor(t), i.fill(), s && s > 0 && (i.beginPath(), i.arc(t.x || 0, t.y || 0, e, 0, 2 * Math.PI), i.strokeStyle = this.getNodeBorderColor(t), i.lineWidth = s, i.stroke());
|
|
66
|
+
const r = this.getNodeLabel(t);
|
|
67
|
+
if (r && this.shouldShowLabel(t, o)) {
|
|
68
|
+
const a = typeof this.options.nodeLabelColor == "function" ? this.options.nodeLabelColor(t) : this.options.nodeLabelColor ?? "#555", n = (this.options.labelFontSize || 14) / o;
|
|
69
|
+
i.font = `${n}px Arial`, i.fillStyle = a, i.textAlign = "center", i.textBaseline = "middle", i.fillText(r, t.x || 0, (t.y || 0) + e + n / 2 + 2);
|
|
202
70
|
}
|
|
203
71
|
}), this.applyLinkOptions();
|
|
204
72
|
}
|
|
@@ -206,21 +74,29 @@ class x {
|
|
|
206
74
|
return typeof this.options.nodeSize == "function" ? this.options.nodeSize(t) || t.marker?.radius : this.options.nodeSize || t.marker?.radius || 1;
|
|
207
75
|
}
|
|
208
76
|
getNodeLabel(t) {
|
|
209
|
-
return typeof this.options.nodeLabel == "function" ? this.options.nodeLabel(t) :
|
|
77
|
+
return typeof this.options.nodeLabel == "function" ? this.options.nodeLabel(t) : t.label || t.id;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if label should be shown based on threshold logic
|
|
81
|
+
* The threshold determines when labels become too big relative to nodes
|
|
82
|
+
*/
|
|
83
|
+
shouldShowLabel(t, i) {
|
|
84
|
+
const o = this.getNodeSize(t) * 2, s = (this.options.labelFontSize || 14) / i, r = this.options.labelThreshold || 1.5;
|
|
85
|
+
return s <= o * r;
|
|
210
86
|
}
|
|
211
87
|
updateData(t) {
|
|
212
|
-
const i = new Set(this.data.nodes.map((
|
|
213
|
-
|
|
214
|
-
this.nodesMap.set(
|
|
88
|
+
const i = new Set(this.data.nodes.map((r) => r.id.toString())), o = t.nodes.filter((r) => !i.has(r.id.toString()));
|
|
89
|
+
o.forEach((r) => {
|
|
90
|
+
this.nodesMap.set(r.id.toString(), r);
|
|
215
91
|
});
|
|
216
|
-
const
|
|
217
|
-
this.data.links.map((
|
|
218
|
-
),
|
|
219
|
-
(
|
|
92
|
+
const e = new Set(
|
|
93
|
+
this.data.links.map((r) => this.createLinkKey(r.source, r.target))
|
|
94
|
+
), s = t.links.filter(
|
|
95
|
+
(r) => !e.has(this.createLinkKey(r.source, r.target))
|
|
220
96
|
);
|
|
221
97
|
this.data = {
|
|
222
|
-
nodes: [...this.data.nodes, ...
|
|
223
|
-
links: [...this.data.links, ...
|
|
98
|
+
nodes: [...this.data.nodes, ...o],
|
|
99
|
+
links: [...this.data.links, ...s]
|
|
224
100
|
}, this.refreshGraph();
|
|
225
101
|
}
|
|
226
102
|
getNodeColor(t) {
|
|
@@ -257,23 +133,23 @@ class x {
|
|
|
257
133
|
if (!this.options.showGroups) return;
|
|
258
134
|
this.groupBounds.clear();
|
|
259
135
|
const t = this.options.groupPadding || 20, i = /* @__PURE__ */ new Map();
|
|
260
|
-
this.data.nodes.forEach((
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
}), i.forEach((
|
|
264
|
-
if (
|
|
265
|
-
let
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
const
|
|
269
|
-
|
|
136
|
+
this.data.nodes.forEach((o) => {
|
|
137
|
+
const e = this.getNodeGroupId(o);
|
|
138
|
+
e && (i.has(e) || i.set(e, []), i.get(e).push(o));
|
|
139
|
+
}), i.forEach((o, e) => {
|
|
140
|
+
if (o.length === 0) return;
|
|
141
|
+
let s = 1 / 0, r = 1 / 0, a = -1 / 0, p = -1 / 0;
|
|
142
|
+
o.forEach((n) => {
|
|
143
|
+
if (n.x !== void 0 && n.y !== void 0) {
|
|
144
|
+
const h = this.getNodeSize(n);
|
|
145
|
+
s = Math.min(s, n.x - h), r = Math.min(r, n.y - h), a = Math.max(a, n.x + h), p = Math.max(p, n.y + h);
|
|
270
146
|
}
|
|
271
|
-
}),
|
|
272
|
-
minX:
|
|
273
|
-
minY:
|
|
274
|
-
maxX:
|
|
147
|
+
}), s !== 1 / 0 && this.groupBounds.set(e, {
|
|
148
|
+
minX: s - t,
|
|
149
|
+
minY: r - t,
|
|
150
|
+
maxX: a + t,
|
|
275
151
|
maxY: p + t,
|
|
276
|
-
nodes:
|
|
152
|
+
nodes: o
|
|
277
153
|
});
|
|
278
154
|
});
|
|
279
155
|
}
|
|
@@ -292,20 +168,20 @@ class x {
|
|
|
292
168
|
* Render group borders and labels
|
|
293
169
|
*/
|
|
294
170
|
renderGroups(t, i) {
|
|
295
|
-
this.options.showGroups && (this.calculateGroupBounds(), this.groupBounds.forEach((
|
|
296
|
-
const
|
|
297
|
-
t.save(), t.globalAlpha =
|
|
171
|
+
this.options.showGroups && (this.calculateGroupBounds(), this.groupBounds.forEach((o, e) => {
|
|
172
|
+
const s = this.getGroupBorderColor(e), r = this.options.groupBorderWidth || 2, a = this.options.groupBorderOpacity || 0.3;
|
|
173
|
+
t.save(), t.globalAlpha = a, t.strokeStyle = s, t.lineWidth = r / i, t.setLineDash([10 / i, 5 / i]), t.strokeRect(o.minX, o.minY, o.maxX - o.minX, o.maxY - o.minY), t.restore();
|
|
298
174
|
const p = this.options.groupLabelThreshold || 0.8;
|
|
299
175
|
if (i <= p) {
|
|
300
|
-
const
|
|
301
|
-
t.save(), t.font = `bold ${
|
|
302
|
-
const
|
|
176
|
+
const n = this.getGroupLabelColor(e), h = (this.options.groupLabelSize || 16) / i;
|
|
177
|
+
t.save(), t.font = `bold ${h}px Arial`, t.fillStyle = n, t.textAlign = "center", t.textBaseline = "middle";
|
|
178
|
+
const l = (o.minX + o.maxX) / 2, d = o.minY - h / 2, u = t.measureText(e).width, g = h;
|
|
303
179
|
t.globalAlpha = 0.8, t.fillStyle = "rgba(255, 255, 255, 0.9)", t.fillRect(
|
|
304
|
-
|
|
305
|
-
|
|
180
|
+
l - u / 2 - 4,
|
|
181
|
+
d - g / 2 - 2,
|
|
306
182
|
u + 8,
|
|
307
|
-
|
|
308
|
-
), t.globalAlpha = 1, t.fillStyle =
|
|
183
|
+
g + 4
|
|
184
|
+
), t.globalAlpha = 1, t.fillStyle = n, t.fillText(e, l, d), t.restore();
|
|
309
185
|
}
|
|
310
186
|
}));
|
|
311
187
|
}
|
|
@@ -327,34 +203,18 @@ class x {
|
|
|
327
203
|
hasNode(t) {
|
|
328
204
|
return this.nodesMap.has(t.toString());
|
|
329
205
|
}
|
|
330
|
-
getNodeCount() {
|
|
331
|
-
return this.nodesMap.size;
|
|
332
|
-
}
|
|
333
206
|
/**
|
|
334
207
|
* Calculate dynamic cooldown time based on node count
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
`Dynamic Cooldown Time: ${t} nodes × 150ms = ${i}ms, final: ${r}ms`
|
|
342
|
-
), r;
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Get the current calculated cooldown time
|
|
208
|
+
* size calculated by node size + math(n) for better performance on first render
|
|
209
|
+
* large size of graph consuming high memory when animating first render
|
|
210
|
+
* so when render large graph, less cooldown time == quick display == high memory usage
|
|
211
|
+
* more cooldown time == slow animating display == lower memory usage
|
|
212
|
+
* Minimum: 4s (4000ms)
|
|
213
|
+
* Normal: node.length * 125%
|
|
346
214
|
*/
|
|
347
215
|
getCooldownTime() {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Manually update the cooldown time based on current node count
|
|
352
|
-
*/
|
|
353
|
-
updateCooldownTime() {
|
|
354
|
-
this.graph.cooldownTime(this.calculateCooldownTime());
|
|
355
|
-
}
|
|
356
|
-
getLinkCount() {
|
|
357
|
-
return this.graph.graphData().links.length;
|
|
216
|
+
const i = this.data.nodes.length * (125 / 100);
|
|
217
|
+
return Math.max(4e3, i);
|
|
358
218
|
}
|
|
359
219
|
getDataSize() {
|
|
360
220
|
const { nodes: t, links: i } = this.graph.graphData();
|
|
@@ -367,31 +227,31 @@ class x {
|
|
|
367
227
|
return Array.from(this.nodesMap.keys());
|
|
368
228
|
}
|
|
369
229
|
updateNode(t, i) {
|
|
370
|
-
const
|
|
371
|
-
if (
|
|
372
|
-
Object.assign(
|
|
373
|
-
const
|
|
374
|
-
return
|
|
230
|
+
const o = this.nodesMap.get(t.toString());
|
|
231
|
+
if (o) {
|
|
232
|
+
Object.assign(o, i);
|
|
233
|
+
const e = this.data.nodes.findIndex((s) => s.id.toString() === t.toString());
|
|
234
|
+
return e !== -1 && (this.data.nodes[e] = o), this.refreshGraph(), !0;
|
|
375
235
|
}
|
|
376
236
|
return !1;
|
|
377
237
|
}
|
|
378
238
|
removeNode(t) {
|
|
379
239
|
const i = t.toString();
|
|
380
|
-
return this.nodesMap.has(i) ? (this.nodesMap.delete(i), this.data.nodes = this.data.nodes.filter((
|
|
381
|
-
const
|
|
382
|
-
return
|
|
383
|
-
}), this.graph.cooldownTime(this.
|
|
240
|
+
return this.nodesMap.has(i) ? (this.nodesMap.delete(i), this.data.nodes = this.data.nodes.filter((o) => o.id.toString() !== i), this.data.links = this.data.links.filter((o) => {
|
|
241
|
+
const e = typeof o.source == "object" ? o.source.id : o.source, s = typeof o.target == "object" ? o.target.id : o.target;
|
|
242
|
+
return e.toString() !== i && s.toString() !== i;
|
|
243
|
+
}), this.graph.cooldownTime(this.getCooldownTime()), this.refreshGraph(), !0) : !1;
|
|
384
244
|
}
|
|
385
245
|
async addData(t) {
|
|
386
246
|
t.nodes.forEach((i) => {
|
|
387
247
|
this.nodesMap.has(i.id.toString()) || this.nodesMap.set(i.id.toString(), i);
|
|
388
248
|
}), t.links.forEach((i) => {
|
|
389
|
-
const
|
|
390
|
-
this.linkMap.has(
|
|
249
|
+
const o = this.createLinkKey(i.source, i.target);
|
|
250
|
+
this.linkMap.has(o) ? console.log("link already exists", i) : this.linkMap.set(o, i);
|
|
391
251
|
}), this.data = {
|
|
392
252
|
nodes: Array.from(this.nodesMap.values()),
|
|
393
253
|
links: Array.from(this.linkMap.values())
|
|
394
|
-
},
|
|
254
|
+
}, this.graph.cooldownTime(this.getCooldownTime()), this.graph.graphData(this.data);
|
|
395
255
|
}
|
|
396
256
|
setLabelThreshold(t) {
|
|
397
257
|
this.options.labelThreshold = t, this.render();
|
|
@@ -414,7 +274,7 @@ class x {
|
|
|
414
274
|
reset() {
|
|
415
275
|
this.graph.pauseAnimation();
|
|
416
276
|
const t = this.graph.d3Force("simulation");
|
|
417
|
-
t && t.stop(), this.data = { nodes: [], links: [] }, this.nodesMap.clear(), this.graph = new
|
|
277
|
+
t && t.stop(), this.data = { nodes: [], links: [] }, this.nodesMap.clear(), this.graph = new f(this.container), this.initGraph();
|
|
418
278
|
}
|
|
419
279
|
getData() {
|
|
420
280
|
return this.data;
|
|
@@ -426,8 +286,8 @@ class x {
|
|
|
426
286
|
return this.data.links;
|
|
427
287
|
}
|
|
428
288
|
createLinkKey(t, i) {
|
|
429
|
-
const
|
|
430
|
-
return `${
|
|
289
|
+
const o = typeof t == "object" ? t.id : t, e = typeof i == "object" ? i.id : i;
|
|
290
|
+
return `${o}-${e}`;
|
|
431
291
|
}
|
|
432
292
|
/**
|
|
433
293
|
* Set graph data (chainable method)
|
|
@@ -437,12 +297,12 @@ class x {
|
|
|
437
297
|
return this.nodesMap.clear(), this.linkMap.clear(), t.nodes.forEach((i) => {
|
|
438
298
|
this.nodesMap.has(i.id.toString()) || this.nodesMap.set(i.id.toString(), i);
|
|
439
299
|
}), t.links.forEach((i) => {
|
|
440
|
-
const
|
|
441
|
-
this.linkMap.has(
|
|
300
|
+
const o = this.createLinkKey(i.source, i.target);
|
|
301
|
+
this.linkMap.has(o) || this.linkMap.set(o, i);
|
|
442
302
|
}), this.data = {
|
|
443
303
|
nodes: Array.from(this.nodesMap.values()),
|
|
444
304
|
links: Array.from(this.linkMap.values())
|
|
445
|
-
}, this.graph.cooldownTime(this.
|
|
305
|
+
}, this.graph.cooldownTime(this.getCooldownTime()), this.graph.graphData(this.data), this;
|
|
446
306
|
}
|
|
447
307
|
/**
|
|
448
308
|
* Enable or disable group visualization
|
|
@@ -457,8 +317,8 @@ class x {
|
|
|
457
317
|
groupLabelSize: 16,
|
|
458
318
|
groupLabelThreshold: 0.8,
|
|
459
319
|
groupPadding: 20
|
|
460
|
-
}).forEach(([
|
|
461
|
-
this.options[
|
|
320
|
+
}).forEach(([o, e]) => {
|
|
321
|
+
this.options[o] === void 0 && (this.options[o] = e);
|
|
462
322
|
}), this.applyOptions(), this.refreshGraph(), this;
|
|
463
323
|
}
|
|
464
324
|
/**
|
|
@@ -479,8 +339,8 @@ class x {
|
|
|
479
339
|
getGroups() {
|
|
480
340
|
const t = /* @__PURE__ */ new Set();
|
|
481
341
|
return this.data.nodes.forEach((i) => {
|
|
482
|
-
const
|
|
483
|
-
|
|
342
|
+
const o = this.getNodeGroupId(i);
|
|
343
|
+
o && t.add(o);
|
|
484
344
|
}), Array.from(t);
|
|
485
345
|
}
|
|
486
346
|
/**
|
|
@@ -500,5 +360,5 @@ class x {
|
|
|
500
360
|
}
|
|
501
361
|
}
|
|
502
362
|
export {
|
|
503
|
-
|
|
363
|
+
C as ForceGraph
|
|
504
364
|
};
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
(function(f,y){typeof exports=="object"&&typeof module<"u"?y(exports,require("force-graph"),require("d3")):typeof define=="function"&&define.amd?define(["exports","force-graph","d3"],y):(f=typeof globalThis<"u"?globalThis:f||self,y(f.ForceGraphLib={},f.ForceGraph,f.d3))})(this,(function(f,y,B){"use strict";function G(r){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const i in r)if(i!=="default"){const e=Object.getOwnPropertyDescriptor(r,i);Object.defineProperty(t,i,e.get?e:{enumerable:!0,get:()=>r[i]})}}return t.default=r,Object.freeze(t)}const v=G(B);function w(r,t){(t==null||t>r.length)&&(t=r.length);for(var i=0,e=Array(t);i<t;i++)e[i]=r[i];return e}function A(r){if(Array.isArray(r))return r}function I(r){if(Array.isArray(r))return w(r)}function P(r){if(typeof Symbol<"u"&&r[Symbol.iterator]!=null||r["@@iterator"]!=null)return Array.from(r)}function O(r,t){var i=r==null?null:typeof Symbol<"u"&&r[Symbol.iterator]||r["@@iterator"];if(i!=null){var e,n,a,s,h=[],u=!0,o=!1;try{if(a=(i=i.call(r)).next,t!==0)for(;!(u=(e=a.call(i)).done)&&(h.push(e.value),h.length!==t);u=!0);}catch(l){o=!0,n=l}finally{try{if(!u&&i.return!=null&&(s=i.return(),Object(s)!==s))return}finally{if(o)throw n}}return h}}function z(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
2
|
-
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function N(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
|
|
3
|
-
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function L(r,t){return A(r)||O(r,t)||T(r,t)||z()}function M(r){return I(r)||P(r)||T(r)||N()}function T(r,t){if(r){if(typeof r=="string")return w(r,t);var i={}.toString.call(r).slice(8,-1);return i==="Object"&&r.constructor&&(i=r.constructor.name),i==="Map"||i==="Set"?Array.from(r):i==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?w(r,t):void 0}}function D(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:function(o){return o.cluster},t,i=[],e=k(1),n=k(.2),a=0,s=new Map;function h(o){var l=new Map(M(s.entries()).map(function(p){var d=L(p,2),m=d[0],b=d[1];return[m,E(b,e,t)]})),g=new Map(M(s.entries()).map(function(p){var d=L(p,2),m=d[0],b=d[1];return[m,n(m,b)]})),c=["x",t>1&&"y",t>2&&"z"].filter(function(p){return p}),C=c.map(function(p){return"v".concat(p)});i.forEach(function(p){var d=r(p);if(l.has(d)){var m=l.get(d),b=c.map(function(S){return m[S]-p[S]});if(!(W.apply(void 0,M(b))<=a)){var x=g.get(d)*o;C.forEach(function(S,F){return p[S]+=b[F]*x})}}})}function u(){s.clear(),i.forEach(function(o){var l=r(o);s.has(l)||s.set(l,[]),s.get(l).push(o)}),s.forEach(function(o,l){return o.length<=1&&s.delete(l)})}return h.initialize=function(o){i=o;for(var l=arguments.length,g=new Array(l>1?l-1:0),c=1;c<l;c++)g[c-1]=arguments[c];t=g.find(function(C){return[1,2,3].includes(C)})||2,u()},h.clusterId=function(o){return arguments.length?(r=o,u(),h):r},h.weight=function(o){return arguments.length?(e=typeof o=="function"?o:k(+o),h):e},h.strength=function(o){return arguments.length?(n=typeof o=="function"?o:k(+o),h):n},h.distanceMin=function(o){return arguments.length?(a=o,h):a},h}function W(r){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,i=arguments.length>2&&arguments[2]!==void 0?arguments[2]:0;return Math.sqrt(r*r+t*t+i*i)}function k(r){return function(){return r}}function E(r,t,i){var e=0,n=0,a=0,s=0;r.forEach(function(u){var o=t(u);n+=u.x*o,i>1&&(a+=u.y*o),i>2&&(s+=u.z*o),e+=o});var h={};return h.x=n/e,i>1&&(h.y=a/e),i>2&&(h.z=s/e),h}class j{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupBounds=new Map;constructor(t,i={nodes:[],links:[]},e={}){this.container=t,this.data=i,this.graph=new y(this.container),i.nodes.forEach(s=>{this.nodesMap.set(s.id.toString(),s)});const n={labelThreshold:1.5,showGroups:!1,...e},a=n.showGroups?{groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}:{};this.options={...n,...a},this.initGraph()}initGraph(){console.log(this.options.width,this.options.width),this.graph.width(this.options.width??800).height(this.options.height??400).onNodeClick(t=>{console.log("Node clicked:",t),this.options.nodeClickHandler&&this.options.nodeClickHandler(t),this.focusPosition({x:t.x,y:t.y})}).cooldownTime(this.calculateCooldownTime()).d3Force("cluster",this.options.cluster?D().clusterId(this.options.cluster):null).d3Force("collide",this.options.collide?v.forceCollide().radius(t=>this.options.collide?this.options.collide(t):t.marker.radius):null),this.options.keepDragPosition&&this.graph.onNodeDragEnd(t=>{t.fx=t.x,t.fy=t.y}),this.graphData(this.data),this.applyOptions(),this.render(),this.graph.onEngineStop(()=>this.graph.zoomToFit(400))}renderer(){return this.graph}focusPosition(t={}){if(Object.values(t).length){if(t.id){const{x:i,y:e}=this.nodesMap.get(t.id)??{x:0,y:0};i&&e&&(t={x:i,y:e})}t&&(this.graph.centerAt(t.x,t.y,1e3),this.graph.zoom(8,2e3))}}render(){this.graph.d3Force("charge",v.forceManyBody().strength(-100)).d3Force("link",v.forceLink().id(t=>t.index??"").distance(30)).d3Force("center",v.forceCenter())}applyOptions(){this.graph.nodeCanvasObject((t,i,e)=>{t===this.data.nodes[0]&&this.renderGroups(i,e);const n=this.getNodeSize(t)*2,a=typeof this.options.nodeBorderWidth=="function"?this.options.nodeBorderWidth(t):this.options.nodeBorderWidth;i.beginPath(),i.arc(t.x||0,t.y||0,n,0,2*Math.PI),i.fillStyle=this.getNodeColor(t),i.fill(),a&&a>0&&(i.beginPath(),i.arc(t.x||0,t.y||0,n,0,2*Math.PI),i.strokeStyle=this.getNodeBorderColor(t),i.lineWidth=a,i.stroke());const s=this.getNodeLabel(t);if(s&&e>=(this.options.labelThreshold||1.5)){const h=typeof this.options.nodeLabelColor=="function"?this.options.nodeLabelColor(t):this.options.nodeLabelColor??"#555";i.font=`${Math.max(n,8)}px Arial`,i.fillStyle=h,i.textAlign="center",i.textBaseline="middle",i.fillText(s,t.x||0,(t.y||0)+n+2)}}),this.applyLinkOptions()}getNodeSize(t){return typeof this.options.nodeSize=="function"?this.options.nodeSize(t)||t.marker?.radius:this.options.nodeSize||t.marker?.radius||1}getNodeLabel(t){return typeof this.options.nodeLabel=="function"?this.options.nodeLabel(t):this.graph.zoom()<(this.options.labelThreshold||1.5)?"":t.label||t.id}updateData(t){const i=new Set(this.data.nodes.map(s=>s.id.toString())),e=t.nodes.filter(s=>!i.has(s.id.toString()));e.forEach(s=>{this.nodesMap.set(s.id.toString(),s)});const n=new Set(this.data.links.map(s=>this.createLinkKey(s.source,s.target))),a=t.links.filter(s=>!n.has(this.createLinkKey(s.source,s.target)));this.data={nodes:[...this.data.nodes,...e],links:[...this.data.links,...a]},this.refreshGraph()}getNodeColor(t){if(typeof this.options.nodeColor=="function"){const i=this.options.nodeColor(t);if(i)return i}return t.color??""}getNodeBorderColor(t){return typeof this.options.nodeBorderColor=="function"?this.options.nodeBorderColor(t):this.options.nodeBorderColor||"#333"}applyLinkOptions(){this.options.linkWidth!==void 0&&this.graph.linkWidth(t=>this.getLinkProperty(this.options.linkWidth,t)??1),this.options.linkCurvature!==void 0&&this.graph.linkCurvature(this.getLinkCurvature.bind(this)),this.options.linkDirectionalParticles&&this.graph.linkDirectionalParticles(t=>this.getLinkProperty(this.options.linkDirectionalParticles,t)??0),this.options.linkDirectionalParticleSpeed!==void 0&&this.graph.linkDirectionalParticleSpeed(t=>this.getLinkProperty(this.options.linkDirectionalParticleSpeed,t)??0),this.options.linkDirectionalParticleWidth!==void 0&&this.graph.linkDirectionalParticleWidth(t=>this.getLinkProperty(this.options.linkDirectionalParticleWidth,t)??0),this.options.linkDirectionalParticleColor!==void 0&&this.graph.linkDirectionalParticleColor(t=>this.getLinkProperty(this.options.linkDirectionalParticleColor,t)??"#aaa")}getLinkCurvature(t){return typeof this.options.linkCurvature=="function"?this.options.linkCurvature(t):typeof this.options.linkCurvature=="string"?t[this.options.linkCurvature]||0:typeof this.options.linkCurvature=="number"?this.options.linkCurvature:t.curvature||0}getLinkProperty(t,i){return typeof t=="function"?t(i):t}calculateGroupBounds(){if(!this.options.showGroups)return;this.groupBounds.clear();const t=this.options.groupPadding||20,i=new Map;this.data.nodes.forEach(e=>{const n=this.getNodeGroupId(e);n&&(i.has(n)||i.set(n,[]),i.get(n).push(e))}),i.forEach((e,n)=>{if(e.length===0)return;let a=1/0,s=1/0,h=-1/0,u=-1/0;e.forEach(o=>{if(o.x!==void 0&&o.y!==void 0){const l=this.getNodeSize(o);a=Math.min(a,o.x-l),s=Math.min(s,o.y-l),h=Math.max(h,o.x+l),u=Math.max(u,o.y+l)}}),a!==1/0&&this.groupBounds.set(n,{minX:a-t,minY:s-t,maxX:h+t,maxY:u+t,nodes:e})})}getNodeGroupId(t){if(this.options.groupBy){if(typeof this.options.groupBy=="function")return this.options.groupBy(t);if(typeof this.options.groupBy=="string")return t[this.options.groupBy]}}renderGroups(t,i){this.options.showGroups&&(this.calculateGroupBounds(),this.groupBounds.forEach((e,n)=>{const a=this.getGroupBorderColor(n),s=this.options.groupBorderWidth||2,h=this.options.groupBorderOpacity||.3;t.save(),t.globalAlpha=h,t.strokeStyle=a,t.lineWidth=s/i,t.setLineDash([10/i,5/i]),t.strokeRect(e.minX,e.minY,e.maxX-e.minX,e.maxY-e.minY),t.restore();const u=this.options.groupLabelThreshold||.8;if(i<=u){const o=this.getGroupLabelColor(n),l=(this.options.groupLabelSize||16)/i;t.save(),t.font=`bold ${l}px Arial`,t.fillStyle=o,t.textAlign="center",t.textBaseline="middle";const g=(e.minX+e.maxX)/2,c=e.minY-l/2,p=t.measureText(n).width,d=l;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(g-p/2-4,c-d/2-2,p+8,d+4),t.globalAlpha=1,t.fillStyle=o,t.fillText(n,g,c),t.restore()}}))}getGroupBorderColor(t){return typeof this.options.groupBorderColor=="function"?this.options.groupBorderColor(t):this.options.groupBorderColor||"#666"}getGroupLabelColor(t){return typeof this.options.groupLabelColor=="function"?this.options.groupLabelColor(t):this.options.groupLabelColor||"#333"}getNodeById(t){return this.nodesMap.get(t.toString())}hasNode(t){return this.nodesMap.has(t.toString())}getNodeCount(){return this.nodesMap.size}calculateCooldownTime(){const t=this.data.nodes.length,i=t*5/2,e=Math.max(2500,i);return console.log(`Dynamic Cooldown Time: ${t} nodes × 150ms = ${i}ms, final: ${e}ms`),e}getCooldownTime(){return this.calculateCooldownTime()}updateCooldownTime(){this.graph.cooldownTime(this.calculateCooldownTime())}getLinkCount(){return this.graph.graphData().links.length}getDataSize(){const{nodes:t,links:i}=this.graph.graphData();return{nodes:t.length,links:i.length}}getAllNodeIds(){return Array.from(this.nodesMap.keys())}updateNode(t,i){const e=this.nodesMap.get(t.toString());if(e){Object.assign(e,i);const n=this.data.nodes.findIndex(a=>a.id.toString()===t.toString());return n!==-1&&(this.data.nodes[n]=e),this.refreshGraph(),!0}return!1}removeNode(t){const i=t.toString();return this.nodesMap.has(i)?(this.nodesMap.delete(i),this.data.nodes=this.data.nodes.filter(e=>e.id.toString()!==i),this.data.links=this.data.links.filter(e=>{const n=typeof e.source=="object"?e.source.id:e.source,a=typeof e.target=="object"?e.target.id:e.target;return n.toString()!==i&&a.toString()!==i}),this.graph.cooldownTime(this.calculateCooldownTime()),this.refreshGraph(),!0):!1}async addData(t){t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const e=this.createLinkKey(i.source,i.target);this.linkMap.has(e)?console.log("link already exists",i):this.linkMap.set(e,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},console.log("this.data",this.data,t),this.graph.cooldownTime(this.calculateCooldownTime()),this.graph.graphData(this.data)}setLabelThreshold(t){this.options.labelThreshold=t,this.render()}setOptions(t){this.options={...this.options,...t},this.applyOptions()}refreshGraph(){this.graphData(this.data)}reinitialize(){this.initGraph()}reset(){this.graph.pauseAnimation();const t=this.graph.d3Force("simulation");t&&t.stop(),this.data={nodes:[],links:[]},this.nodesMap.clear(),this.graph=new y(this.container),this.initGraph()}getData(){return this.data}getNodesData(){return this.data.nodes}getLinksData(){return this.data.links}createLinkKey(t,i){const e=typeof t=="object"?t.id:t,n=typeof i=="object"?i.id:i;return`${e}-${n}`}graphData(t){return this.nodesMap.clear(),this.linkMap.clear(),t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const e=this.createLinkKey(i.source,i.target);this.linkMap.has(e)||this.linkMap.set(e,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.graph.cooldownTime(this.calculateCooldownTime()),this.graph.graphData(this.data),this}showGroups(t){return this.options.showGroups=t,t&&Object.entries({groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}).forEach(([e,n])=>{this.options[e]===void 0&&(this.options[e]=n)}),this.applyOptions(),this.refreshGraph(),this}setGroupBy(t){return this.options.groupBy=t,this.applyOptions(),this.refreshGraph(),this}setGroupOptions(t){return t.borderColor!==void 0&&(this.options.groupBorderColor=t.borderColor),t.borderWidth!==void 0&&(this.options.groupBorderWidth=t.borderWidth),t.borderOpacity!==void 0&&(this.options.groupBorderOpacity=t.borderOpacity),t.labelColor!==void 0&&(this.options.groupLabelColor=t.labelColor),t.labelSize!==void 0&&(this.options.groupLabelSize=t.labelSize),t.labelThreshold!==void 0&&(this.options.groupLabelThreshold=t.labelThreshold),t.padding!==void 0&&(this.options.groupPadding=t.padding),this.applyOptions(),this.refreshGraph(),this}getGroups(){const t=new Set;return this.data.nodes.forEach(i=>{const e=this.getNodeGroupId(i);e&&t.add(e)}),Array.from(t)}getNodesInGroup(t){return this.data.nodes.filter(i=>this.getNodeGroupId(i)===t)}getOptions(){return{...this.options}}destroy(){this.worker&&(this.worker.terminate(),this.worker=null),this.graph._destructor()}}f.ForceGraph=j,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(a,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("force-graph"),require("d3")):typeof define=="function"&&define.amd?define(["exports","force-graph","d3"],l):(a=typeof globalThis<"u"?globalThis:a||self,l(a.ForceGraphLib={},a.ForceGraph,a.d3))})(this,(function(a,l,b){"use strict";function k(d){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(d){for(const i in d)if(i!=="default"){const o=Object.getOwnPropertyDescriptor(d,i);Object.defineProperty(t,i,o.get?o:{enumerable:!0,get:()=>d[i]})}}return t.default=d,Object.freeze(t)}const m=k(b);class C{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupBounds=new Map;constructor(t,i={nodes:[],links:[]},o={}){this.container=t,this.data=i,this.graph=new l(this.container),i.nodes.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e={labelThreshold:1.5,showGroups:!1,...o},s=e.showGroups?{groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}:{};this.options={...e,...s},this.initGraph()}initGraph(){this.applyOptions(),this.render(),this.graphData(this.data),this.graph.onEngineStop(()=>this.graph.zoomToFit(400))}renderer(){return this.graph}focusPosition(t={}){if(Object.values(t).length){if(t.id){const{x:i,y:o}=this.nodesMap.get(t.id)??{x:0,y:0};i&&o&&(t={x:i,y:o})}t&&(this.graph.centerAt(t.x,t.y,1e3),this.graph.zoom(8,2e3))}}render(){this.graph.width(this.options.width??800).height(this.options.height??400).cooldownTime(this.getCooldownTime()).d3Force("charge",m.forceManyBody().strength(this.options.nodeGap??-50)),typeof this.options.nodeClickHandler=="function"&&this.graph.onNodeClick(t=>{this.options.nodeClickHandler(t)})}force(t,i){return this.graph.d3Force(t,i)}applyOptions(){this.options.keepDragPosition&&this.graph.onNodeDragEnd(t=>{t.fx=t.x,t.fy=t.y}),this.options.pointerInteraction||this.graph.enablePointerInteraction(!1),this.graph.nodeCanvasObject((t,i,o)=>{t===this.data.nodes[0]&&this.renderGroups(i,o);const e=this.getNodeSize(t)*2,s=typeof this.options.nodeBorderWidth=="function"?this.options.nodeBorderWidth(t):this.options.nodeBorderWidth;i.beginPath(),i.arc(t.x||0,t.y||0,e,0,2*Math.PI),i.fillStyle=this.getNodeColor(t),i.fill(),s&&s>0&&(i.beginPath(),i.arc(t.x||0,t.y||0,e,0,2*Math.PI),i.strokeStyle=this.getNodeBorderColor(t),i.lineWidth=s,i.stroke());const r=this.getNodeLabel(t);if(r&&this.shouldShowLabel(t,o)){const p=typeof this.options.nodeLabelColor=="function"?this.options.nodeLabelColor(t):this.options.nodeLabelColor??"#555",n=(this.options.labelFontSize||14)/o;i.font=`${n}px Arial`,i.fillStyle=p,i.textAlign="center",i.textBaseline="middle",i.fillText(r,t.x||0,(t.y||0)+e+n/2+2)}}),this.applyLinkOptions()}getNodeSize(t){return typeof this.options.nodeSize=="function"?this.options.nodeSize(t)||t.marker?.radius:this.options.nodeSize||t.marker?.radius||1}getNodeLabel(t){return typeof this.options.nodeLabel=="function"?this.options.nodeLabel(t):t.label||t.id}shouldShowLabel(t,i){const o=this.getNodeSize(t)*2,s=(this.options.labelFontSize||14)/i,r=this.options.labelThreshold||1.5;return s<=o*r}updateData(t){const i=new Set(this.data.nodes.map(r=>r.id.toString())),o=t.nodes.filter(r=>!i.has(r.id.toString()));o.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e=new Set(this.data.links.map(r=>this.createLinkKey(r.source,r.target))),s=t.links.filter(r=>!e.has(this.createLinkKey(r.source,r.target)));this.data={nodes:[...this.data.nodes,...o],links:[...this.data.links,...s]},this.refreshGraph()}getNodeColor(t){if(typeof this.options.nodeColor=="function"){const i=this.options.nodeColor(t);if(i)return i}return t.color??""}getNodeBorderColor(t){return typeof this.options.nodeBorderColor=="function"?this.options.nodeBorderColor(t):this.options.nodeBorderColor||"#333"}applyLinkOptions(){this.options.linkWidth!==void 0&&this.graph.linkWidth(t=>this.getLinkProperty(this.options.linkWidth,t)??1),this.options.linkCurvature!==void 0&&this.graph.linkCurvature(this.getLinkCurvature.bind(this)),this.options.linkDirectionalParticles&&this.graph.linkDirectionalParticles(t=>this.getLinkProperty(this.options.linkDirectionalParticles,t)??0),this.options.linkDirectionalParticleSpeed!==void 0&&this.graph.linkDirectionalParticleSpeed(t=>this.getLinkProperty(this.options.linkDirectionalParticleSpeed,t)??0),this.options.linkDirectionalParticleWidth!==void 0&&this.graph.linkDirectionalParticleWidth(t=>this.getLinkProperty(this.options.linkDirectionalParticleWidth,t)??0),this.options.linkDirectionalParticleColor!==void 0&&this.graph.linkDirectionalParticleColor(t=>this.getLinkProperty(this.options.linkDirectionalParticleColor,t)??"#aaa")}getLinkCurvature(t){return typeof this.options.linkCurvature=="function"?this.options.linkCurvature(t):typeof this.options.linkCurvature=="string"?t[this.options.linkCurvature]||0:typeof this.options.linkCurvature=="number"?this.options.linkCurvature:t.curvature||0}getLinkProperty(t,i){return typeof t=="function"?t(i):t}calculateGroupBounds(){if(!this.options.showGroups)return;this.groupBounds.clear();const t=this.options.groupPadding||20,i=new Map;this.data.nodes.forEach(o=>{const e=this.getNodeGroupId(o);e&&(i.has(e)||i.set(e,[]),i.get(e).push(o))}),i.forEach((o,e)=>{if(o.length===0)return;let s=1/0,r=1/0,p=-1/0,u=-1/0;o.forEach(n=>{if(n.x!==void 0&&n.y!==void 0){const h=this.getNodeSize(n);s=Math.min(s,n.x-h),r=Math.min(r,n.y-h),p=Math.max(p,n.x+h),u=Math.max(u,n.y+h)}}),s!==1/0&&this.groupBounds.set(e,{minX:s-t,minY:r-t,maxX:p+t,maxY:u+t,nodes:o})})}getNodeGroupId(t){if(this.options.groupBy){if(typeof this.options.groupBy=="function")return this.options.groupBy(t);if(typeof this.options.groupBy=="string")return t[this.options.groupBy]}}renderGroups(t,i){this.options.showGroups&&(this.calculateGroupBounds(),this.groupBounds.forEach((o,e)=>{const s=this.getGroupBorderColor(e),r=this.options.groupBorderWidth||2,p=this.options.groupBorderOpacity||.3;t.save(),t.globalAlpha=p,t.strokeStyle=s,t.lineWidth=r/i,t.setLineDash([10/i,5/i]),t.strokeRect(o.minX,o.minY,o.maxX-o.minX,o.maxY-o.minY),t.restore();const u=this.options.groupLabelThreshold||.8;if(i<=u){const n=this.getGroupLabelColor(e),h=(this.options.groupLabelSize||16)/i;t.save(),t.font=`bold ${h}px Arial`,t.fillStyle=n,t.textAlign="center",t.textBaseline="middle";const g=(o.minX+o.maxX)/2,f=o.minY-h/2,c=t.measureText(e).width,y=h;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(g-c/2-4,f-y/2-2,c+8,y+4),t.globalAlpha=1,t.fillStyle=n,t.fillText(e,g,f),t.restore()}}))}getGroupBorderColor(t){return typeof this.options.groupBorderColor=="function"?this.options.groupBorderColor(t):this.options.groupBorderColor||"#666"}getGroupLabelColor(t){return typeof this.options.groupLabelColor=="function"?this.options.groupLabelColor(t):this.options.groupLabelColor||"#333"}getNodeById(t){return this.nodesMap.get(t.toString())}hasNode(t){return this.nodesMap.has(t.toString())}getCooldownTime(){const i=this.data.nodes.length*(125/100);return Math.max(4e3,i)}getDataSize(){const{nodes:t,links:i}=this.graph.graphData();return{nodes:t.length,links:i.length}}getAllNodeIds(){return Array.from(this.nodesMap.keys())}updateNode(t,i){const o=this.nodesMap.get(t.toString());if(o){Object.assign(o,i);const e=this.data.nodes.findIndex(s=>s.id.toString()===t.toString());return e!==-1&&(this.data.nodes[e]=o),this.refreshGraph(),!0}return!1}removeNode(t){const i=t.toString();return this.nodesMap.has(i)?(this.nodesMap.delete(i),this.data.nodes=this.data.nodes.filter(o=>o.id.toString()!==i),this.data.links=this.data.links.filter(o=>{const e=typeof o.source=="object"?o.source.id:o.source,s=typeof o.target=="object"?o.target.id:o.target;return e.toString()!==i&&s.toString()!==i}),this.graph.cooldownTime(this.getCooldownTime()),this.refreshGraph(),!0):!1}async addData(t){t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const o=this.createLinkKey(i.source,i.target);this.linkMap.has(o)?console.log("link already exists",i):this.linkMap.set(o,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.graph.cooldownTime(this.getCooldownTime()),this.graph.graphData(this.data)}setLabelThreshold(t){this.options.labelThreshold=t,this.render()}setOptions(t){this.options={...this.options,...t},this.applyOptions()}refreshGraph(){this.graphData(this.data)}reinitialize(){this.initGraph()}reset(){this.graph.pauseAnimation();const t=this.graph.d3Force("simulation");t&&t.stop(),this.data={nodes:[],links:[]},this.nodesMap.clear(),this.graph=new l(this.container),this.initGraph()}getData(){return this.data}getNodesData(){return this.data.nodes}getLinksData(){return this.data.links}createLinkKey(t,i){const o=typeof t=="object"?t.id:t,e=typeof i=="object"?i.id:i;return`${o}-${e}`}graphData(t){return this.nodesMap.clear(),this.linkMap.clear(),t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const o=this.createLinkKey(i.source,i.target);this.linkMap.has(o)||this.linkMap.set(o,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.graph.cooldownTime(this.getCooldownTime()),this.graph.graphData(this.data),this}showGroups(t){return this.options.showGroups=t,t&&Object.entries({groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}).forEach(([o,e])=>{this.options[o]===void 0&&(this.options[o]=e)}),this.applyOptions(),this.refreshGraph(),this}setGroupBy(t){return this.options.groupBy=t,this.applyOptions(),this.refreshGraph(),this}setGroupOptions(t){return t.borderColor!==void 0&&(this.options.groupBorderColor=t.borderColor),t.borderWidth!==void 0&&(this.options.groupBorderWidth=t.borderWidth),t.borderOpacity!==void 0&&(this.options.groupBorderOpacity=t.borderOpacity),t.labelColor!==void 0&&(this.options.groupLabelColor=t.labelColor),t.labelSize!==void 0&&(this.options.groupLabelSize=t.labelSize),t.labelThreshold!==void 0&&(this.options.groupLabelThreshold=t.labelThreshold),t.padding!==void 0&&(this.options.groupPadding=t.padding),this.applyOptions(),this.refreshGraph(),this}getGroups(){const t=new Set;return this.data.nodes.forEach(i=>{const o=this.getNodeGroupId(i);o&&t.add(o)}),Array.from(t)}getNodesInGroup(t){return this.data.nodes.filter(i=>this.getNodeGroupId(i)===t)}getOptions(){return{...this.options}}destroy(){this.worker&&(this.worker.terminate(),this.worker=null),this.graph._destructor()}}a.ForceGraph=C,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ import { GraphData as GraphData_2 } from 'force-graph';
|
|
|
3
3
|
import { LinkObject } from 'force-graph';
|
|
4
4
|
import { NodeObject } from 'force-graph';
|
|
5
5
|
|
|
6
|
+
declare interface ForceFn<N extends NodeObject = NodeObject> {
|
|
7
|
+
(alpha: number): void;
|
|
8
|
+
initialize?: (nodes: N[], ...args: any[]) => void;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
export declare class ForceGraph {
|
|
7
13
|
private container;
|
|
8
14
|
private graph;
|
|
@@ -21,9 +27,15 @@ export declare class ForceGraph {
|
|
|
21
27
|
y?: number;
|
|
22
28
|
}): void;
|
|
23
29
|
render(): void;
|
|
24
|
-
|
|
30
|
+
force(key: ForceType, func: ForceFn<NodeData>): default_2<NodeData, LinkObject<NodeData>>;
|
|
31
|
+
private applyOptions;
|
|
25
32
|
private getNodeSize;
|
|
26
33
|
private getNodeLabel;
|
|
34
|
+
/**
|
|
35
|
+
* Check if label should be shown based on threshold logic
|
|
36
|
+
* The threshold determines when labels become too big relative to nodes
|
|
37
|
+
*/
|
|
38
|
+
private shouldShowLabel;
|
|
27
39
|
updateData(data: GraphData): void;
|
|
28
40
|
private getNodeColor;
|
|
29
41
|
private getNodeBorderColor;
|
|
@@ -52,22 +64,16 @@ export declare class ForceGraph {
|
|
|
52
64
|
private getGroupLabelColor;
|
|
53
65
|
getNodeById(id: string | number): NodeData | undefined;
|
|
54
66
|
hasNode(id: string | number): boolean;
|
|
55
|
-
getNodeCount(): number;
|
|
56
67
|
/**
|
|
57
68
|
* Calculate dynamic cooldown time based on node count
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*
|
|
69
|
+
* size calculated by node size + math(n) for better performance on first render
|
|
70
|
+
* large size of graph consuming high memory when animating first render
|
|
71
|
+
* so when render large graph, less cooldown time == quick display == high memory usage
|
|
72
|
+
* more cooldown time == slow animating display == lower memory usage
|
|
73
|
+
* Minimum: 4s (4000ms)
|
|
74
|
+
* Normal: node.length * 125%
|
|
64
75
|
*/
|
|
65
76
|
getCooldownTime(): number;
|
|
66
|
-
/**
|
|
67
|
-
* Manually update the cooldown time based on current node count
|
|
68
|
-
*/
|
|
69
|
-
updateCooldownTime(): void;
|
|
70
|
-
getLinkCount(): number;
|
|
71
77
|
getDataSize(): {
|
|
72
78
|
nodes: number;
|
|
73
79
|
links: number;
|
|
@@ -131,6 +137,8 @@ export declare class ForceGraph {
|
|
|
131
137
|
destroy(): void;
|
|
132
138
|
}
|
|
133
139
|
|
|
140
|
+
declare type ForceType = 'link' | 'charge' | 'center' | 'cluster' | string;
|
|
141
|
+
|
|
134
142
|
export declare interface GraphData extends GraphData_2<NodeData, LinkData> {
|
|
135
143
|
}
|
|
136
144
|
|
|
@@ -142,14 +150,16 @@ export declare interface GraphOptions {
|
|
|
142
150
|
linkWidth?: number | ((link: LinkData) => number);
|
|
143
151
|
nodeLabel?: string | ((node: NodeData) => string);
|
|
144
152
|
nodeLabelColor?: string | ((node: NodeData) => string);
|
|
153
|
+
labelFontSize?: number;
|
|
145
154
|
nodeColor?: string | ((node: NodeData) => string);
|
|
146
155
|
nodeBorderColor?: string | ((node: NodeData) => string);
|
|
147
156
|
nodeBorderWidth?: number | ((node: NodeData) => number);
|
|
157
|
+
nodeGap?: number;
|
|
148
158
|
linkLabel?: string | ((link: LinkData) => string);
|
|
149
159
|
nodeIcon?: string | ((node: NodeData) => string);
|
|
150
160
|
cluster?: (node: NodeData) => boolean | undefined | null;
|
|
151
|
-
collide?: (node: NodeData) => number;
|
|
152
161
|
loading?: boolean;
|
|
162
|
+
pointerInteraction?: boolean;
|
|
153
163
|
keepDragPosition?: boolean;
|
|
154
164
|
nodeClickHandler?: (node: NodeData) => void;
|
|
155
165
|
linkCurvature?: number | string | ((link: LinkData) => number);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logixode/force-graph-lib",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"description": "Force-directed graph visualization library.",
|
|
6
6
|
"author": "Rohmad Kurniadi",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"homepage": "https://github.
|
|
8
|
+
"homepage": "https://logixode.github.io/force-graph-lib/introduction.html",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/logixode/force-graph-lib.git"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"build": "bunx --bun vite build && npx tsc --project tsconfig.lib.json --noEmit",
|
|
39
39
|
"preview": "bunx --bun vite preview",
|
|
40
40
|
"docs:dev": "bunx --bun vitepress dev",
|
|
41
|
-
"docs:build": "
|
|
41
|
+
"docs:build": "vitepress build",
|
|
42
42
|
"docs:preview": "bunx --bun vitepress preview"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
@@ -49,25 +49,28 @@
|
|
|
49
49
|
"axios": "^1.11.0",
|
|
50
50
|
"class-variance-authority": "^0.7.1",
|
|
51
51
|
"clsx": "^2.1.1",
|
|
52
|
+
"json-editor-vue": "^0.18.1",
|
|
53
|
+
"jsoneditor": "^10.4.1",
|
|
52
54
|
"lucide-vue-next": "^0.543.0",
|
|
53
55
|
"reka-ui": "^2.5.0",
|
|
54
56
|
"tailwind-merge": "^3.3.1",
|
|
55
57
|
"tw-animate-css": "^1.3.8",
|
|
56
58
|
"vite-plugin-dts": "^4.5.4",
|
|
59
|
+
"vitepress": "^2.0.0-alpha.12",
|
|
57
60
|
"vue-sonner": "^2.0.8"
|
|
58
61
|
},
|
|
59
|
-
"peerDependencies": {
|
|
60
|
-
"d3": "^7.9.0",
|
|
61
|
-
"d3-force-clustering": "^1.0.0",
|
|
62
|
-
"force-graph": "^1.51.1"
|
|
63
|
-
},
|
|
64
62
|
"devDependencies": {
|
|
63
|
+
"d3": "^7.9.0",
|
|
64
|
+
"force-graph": "^1.50.1",
|
|
65
65
|
"@types/d3": "^7.4.3",
|
|
66
|
+
"d3-force-clustering": "^1.0.0",
|
|
66
67
|
"autoprefixer": "^10.4.21",
|
|
67
68
|
"postcss": "^8.5.6",
|
|
68
69
|
"tailwindcss": "3",
|
|
69
70
|
"typescript": "~5.8.3",
|
|
70
|
-
"vite": "^7.1.2"
|
|
71
|
-
|
|
71
|
+
"vite": "^7.1.2"
|
|
72
|
+
},
|
|
73
|
+
"overrides": {
|
|
74
|
+
"ajv": "^8.12.0"
|
|
72
75
|
}
|
|
73
76
|
}
|