@logixode/force-graph-lib 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rohmad Kurniadi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # Force Graph Library
2
+
3
+ A TypeScript library for creating interactive force-directed graphs with advanced features and optimizations for handling large datasets.
4
+
5
+ ## Features
6
+
7
+ - 🎯 Dynamic label threshold control
8
+ - 🔄 Multiple layout algorithms (Force-directed and Circle Pack)
9
+ - 🎨 Customizable styling for nodes and edges
10
+ - 📊 Efficient handling of large datasets
11
+ - 🔄 Pagination support for incremental data loading
12
+ - 🔄 Graph refresh and reset capabilities
13
+ - ⚡ Non-blocking calculations using Web Workers
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install force-graph-lib
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```typescript
24
+ import { ForceGraph } from 'force-graph-lib'
25
+
26
+ // Initialize the graph
27
+ const container = document.getElementById('graph-container')
28
+ const graph = new ForceGraph(
29
+ container,
30
+ {
31
+ nodes: [
32
+ { id: '1', name: 'Node 1' },
33
+ { id: '2', name: 'Node 2' },
34
+ ],
35
+ links: [{ source: '1', target: '2' }],
36
+ },
37
+ {
38
+ labelThreshold: 1.2,
39
+ nodeSize: (node) => node.size || 5,
40
+ nodeLabel: (node) => node.name,
41
+ nodeIcon: '●',
42
+ },
43
+ )
44
+
45
+ // Add more data incrementally
46
+ graph.addData({
47
+ nodes: [{ id: '3', name: 'Node 3' }],
48
+ links: [{ source: '2', target: '3' }],
49
+ })
50
+
51
+ // Change layout
52
+ graph.setLayout('circlepack')
53
+
54
+ // Update label threshold
55
+ graph.setLabelThreshold(1.5)
56
+
57
+ // Refresh or reset the graph
58
+ graph.refreshGraph()
59
+ graph.resetGraph()
60
+
61
+ // Check loading state
62
+ console.log(graph.isLoading())
63
+
64
+ // Clean up
65
+ graph.destroy()
66
+ ```
67
+
68
+ ## API Reference
69
+
70
+ ### Constructor
71
+
72
+ ```typescript
73
+ new ForceGraph(container: HTMLElement, data: GraphData, options?: GraphOptions)
74
+ ```
75
+
76
+ ### GraphData Interface
77
+
78
+ ```typescript
79
+ interface GraphData {
80
+ nodes: Array<{
81
+ id: string
82
+ [key: string]: any
83
+ }>
84
+ links: Array<{
85
+ source: string
86
+ target: string
87
+ [key: string]: any
88
+ }>
89
+ }
90
+ ```
91
+
92
+ ### GraphOptions Interface
93
+
94
+ ```typescript
95
+ interface GraphOptions {
96
+ labelThreshold?: number
97
+ layout?: 'force' | 'circlepack'
98
+ nodeSize?: number | ((node: any) => number)
99
+ linkWidth?: number | ((link: any) => number)
100
+ nodeLabel?: string | ((node: any) => string)
101
+ linkLabel?: string | ((link: any) => string)
102
+ nodeIcon?: string | ((node: any) => string)
103
+ }
104
+ ```
105
+
106
+ ### Methods
107
+
108
+ - `addData(newData: GraphData)`: Add new nodes and edges to the graph
109
+ - `setLayout(layout: 'force' | 'circlepack')`: Change the graph layout algorithm
110
+ - `setLabelThreshold(threshold: number)`: Set the zoom threshold for showing labels
111
+ - `updateStyles(options: Partial<GraphOptions>)`: Update graph styling options
112
+ - `refreshGraph()`: Refresh the graph with current data
113
+ - `resetGraph()`: Reset the graph to its initial state
114
+ - `isLoading()`: Check if the graph is currently calculating layout
115
+ - `destroy()`: Clean up resources and remove the graph
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,504 @@
1
+ import S from "force-graph";
2
+ import * as k from "d3";
3
+ function w(o, t) {
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 {
131
+ container;
132
+ graph;
133
+ data = { nodes: [], links: [] };
134
+ nodesMap = /* @__PURE__ */ new Map();
135
+ linkMap = /* @__PURE__ */ new Map();
136
+ options;
137
+ worker = null;
138
+ groupBounds = /* @__PURE__ */ new Map();
139
+ constructor(t, i = { nodes: [], links: [] }, r = {}) {
140
+ this.container = t, this.data = i, this.graph = new S(this.container), i.nodes.forEach((s) => {
141
+ this.nodesMap.set(s.id.toString(), s);
142
+ });
143
+ const n = {
144
+ labelThreshold: 1.5,
145
+ showGroups: !1,
146
+ ...r
147
+ }, a = n.showGroups ? {
148
+ groupBy: "topic",
149
+ groupBorderColor: "#666",
150
+ groupBorderWidth: 2,
151
+ groupBorderOpacity: 0.3,
152
+ groupLabelColor: "#333",
153
+ groupLabelSize: 16,
154
+ groupLabelThreshold: 0.8,
155
+ groupPadding: 20
156
+ } : {};
157
+ this.options = {
158
+ ...n,
159
+ ...a
160
+ }, this.initGraph();
161
+ }
162
+ initGraph() {
163
+ console.log(this.options.width, this.options.width), this.graph.width(this.options.width ?? 800).height(this.options.height ?? 400).onNodeClick((t) => {
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));
174
+ }
175
+ renderer() {
176
+ return this.graph;
177
+ }
178
+ focusPosition(t = {}) {
179
+ if (Object.values(t).length) {
180
+ if (t.id) {
181
+ const { x: i, y: r } = this.nodesMap.get(t.id) ?? { x: 0, y: 0 };
182
+ i && r && (t = { x: i, y: r });
183
+ }
184
+ t && (this.graph.centerAt(t.x, t.y, 1e3), this.graph.zoom(8, 2e3));
185
+ }
186
+ }
187
+ render() {
188
+ this.graph.d3Force("charge", k.forceManyBody().strength(-100)).d3Force(
189
+ "link",
190
+ k.forceLink().id((t) => t.index ?? "").distance(30)
191
+ ).d3Force("center", k.forceCenter());
192
+ }
193
+ applyOptions() {
194
+ this.graph.nodeCanvasObject((t, i, r) => {
195
+ t === this.data.nodes[0] && this.renderGroups(i, r);
196
+ const n = this.getNodeSize(t) * 2, a = typeof this.options.nodeBorderWidth == "function" ? this.options.nodeBorderWidth(t) : this.options.nodeBorderWidth;
197
+ 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());
198
+ const s = this.getNodeLabel(t);
199
+ if (s && r >= (this.options.labelThreshold || 1.5)) {
200
+ const h = typeof this.options.nodeLabelColor == "function" ? this.options.nodeLabelColor(t) : this.options.nodeLabelColor ?? "#555";
201
+ 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);
202
+ }
203
+ }), this.applyLinkOptions();
204
+ }
205
+ getNodeSize(t) {
206
+ return typeof this.options.nodeSize == "function" ? this.options.nodeSize(t) || t.marker?.radius : this.options.nodeSize || t.marker?.radius || 1;
207
+ }
208
+ getNodeLabel(t) {
209
+ return typeof this.options.nodeLabel == "function" ? this.options.nodeLabel(t) : this.graph.zoom() < (this.options.labelThreshold || 1.5) ? "" : t.label || t.id;
210
+ }
211
+ updateData(t) {
212
+ const i = new Set(this.data.nodes.map((s) => s.id.toString())), r = t.nodes.filter((s) => !i.has(s.id.toString()));
213
+ r.forEach((s) => {
214
+ this.nodesMap.set(s.id.toString(), s);
215
+ });
216
+ const n = new Set(
217
+ this.data.links.map((s) => this.createLinkKey(s.source, s.target))
218
+ ), a = t.links.filter(
219
+ (s) => !n.has(this.createLinkKey(s.source, s.target))
220
+ );
221
+ this.data = {
222
+ nodes: [...this.data.nodes, ...r],
223
+ links: [...this.data.links, ...a]
224
+ }, this.refreshGraph();
225
+ }
226
+ getNodeColor(t) {
227
+ if (typeof this.options.nodeColor == "function") {
228
+ const i = this.options.nodeColor(t);
229
+ if (i) return i;
230
+ }
231
+ return t.color ?? "";
232
+ }
233
+ getNodeBorderColor(t) {
234
+ return typeof this.options.nodeBorderColor == "function" ? this.options.nodeBorderColor(t) : this.options.nodeBorderColor || "#333";
235
+ }
236
+ applyLinkOptions() {
237
+ 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(
238
+ (t) => this.getLinkProperty(this.options.linkDirectionalParticles, t) ?? 0
239
+ ), this.options.linkDirectionalParticleSpeed !== void 0 && this.graph.linkDirectionalParticleSpeed(
240
+ (t) => this.getLinkProperty(this.options.linkDirectionalParticleSpeed, t) ?? 0
241
+ ), this.options.linkDirectionalParticleWidth !== void 0 && this.graph.linkDirectionalParticleWidth(
242
+ (t) => this.getLinkProperty(this.options.linkDirectionalParticleWidth, t) ?? 0
243
+ ), this.options.linkDirectionalParticleColor !== void 0 && this.graph.linkDirectionalParticleColor(
244
+ (t) => this.getLinkProperty(this.options.linkDirectionalParticleColor, t) ?? "#aaa"
245
+ );
246
+ }
247
+ getLinkCurvature(t) {
248
+ 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;
249
+ }
250
+ getLinkProperty(t, i) {
251
+ return typeof t == "function" ? t(i) : t;
252
+ }
253
+ /**
254
+ * Calculate group boundaries based on node positions
255
+ */
256
+ calculateGroupBounds() {
257
+ if (!this.options.showGroups) return;
258
+ this.groupBounds.clear();
259
+ const t = this.options.groupPadding || 20, i = /* @__PURE__ */ new Map();
260
+ this.data.nodes.forEach((r) => {
261
+ const n = this.getNodeGroupId(r);
262
+ n && (i.has(n) || i.set(n, []), i.get(n).push(r));
263
+ }), i.forEach((r, n) => {
264
+ if (r.length === 0) return;
265
+ let a = 1 / 0, s = 1 / 0, h = -1 / 0, p = -1 / 0;
266
+ r.forEach((e) => {
267
+ if (e.x !== void 0 && e.y !== void 0) {
268
+ const l = this.getNodeSize(e);
269
+ a = Math.min(a, e.x - l), s = Math.min(s, e.y - l), h = Math.max(h, e.x + l), p = Math.max(p, e.y + l);
270
+ }
271
+ }), a !== 1 / 0 && this.groupBounds.set(n, {
272
+ minX: a - t,
273
+ minY: s - t,
274
+ maxX: h + t,
275
+ maxY: p + t,
276
+ nodes: r
277
+ });
278
+ });
279
+ }
280
+ /**
281
+ * Get the group ID for a node
282
+ */
283
+ getNodeGroupId(t) {
284
+ if (this.options.groupBy) {
285
+ if (typeof this.options.groupBy == "function")
286
+ return this.options.groupBy(t);
287
+ if (typeof this.options.groupBy == "string")
288
+ return t[this.options.groupBy];
289
+ }
290
+ }
291
+ /**
292
+ * Render group borders and labels
293
+ */
294
+ renderGroups(t, i) {
295
+ this.options.showGroups && (this.calculateGroupBounds(), this.groupBounds.forEach((r, n) => {
296
+ const a = this.getGroupBorderColor(n), s = this.options.groupBorderWidth || 2, h = this.options.groupBorderOpacity || 0.3;
297
+ t.save(), t.globalAlpha = h, t.strokeStyle = a, t.lineWidth = s / i, t.setLineDash([10 / i, 5 / i]), t.strokeRect(r.minX, r.minY, r.maxX - r.minX, r.maxY - r.minY), t.restore();
298
+ const p = this.options.groupLabelThreshold || 0.8;
299
+ if (i <= p) {
300
+ const e = this.getGroupLabelColor(n), l = (this.options.groupLabelSize || 16) / i;
301
+ t.save(), t.font = `bold ${l}px Arial`, t.fillStyle = e, t.textAlign = "center", t.textBaseline = "middle";
302
+ const f = (r.minX + r.maxX) / 2, c = r.minY - l / 2, u = t.measureText(n).width, d = l;
303
+ t.globalAlpha = 0.8, t.fillStyle = "rgba(255, 255, 255, 0.9)", t.fillRect(
304
+ f - u / 2 - 4,
305
+ c - d / 2 - 2,
306
+ u + 8,
307
+ d + 4
308
+ ), t.globalAlpha = 1, t.fillStyle = e, t.fillText(n, f, c), t.restore();
309
+ }
310
+ }));
311
+ }
312
+ /**
313
+ * Get group border color
314
+ */
315
+ getGroupBorderColor(t) {
316
+ return typeof this.options.groupBorderColor == "function" ? this.options.groupBorderColor(t) : this.options.groupBorderColor || "#666";
317
+ }
318
+ /**
319
+ * Get group label color
320
+ */
321
+ getGroupLabelColor(t) {
322
+ return typeof this.options.groupLabelColor == "function" ? this.options.groupLabelColor(t) : this.options.groupLabelColor || "#333";
323
+ }
324
+ getNodeById(t) {
325
+ return this.nodesMap.get(t.toString());
326
+ }
327
+ hasNode(t) {
328
+ return this.nodesMap.has(t.toString());
329
+ }
330
+ getNodeCount() {
331
+ return this.nodesMap.size;
332
+ }
333
+ /**
334
+ * Calculate dynamic cooldown time based on node count
335
+ * Minimum: 2500ms
336
+ * Normal: node.length * 150ms
337
+ */
338
+ calculateCooldownTime() {
339
+ const t = this.data.nodes.length, i = t * 5 / 2, r = Math.max(2500, i);
340
+ return console.log(
341
+ `Dynamic Cooldown Time: ${t} nodes × 150ms = ${i}ms, final: ${r}ms`
342
+ ), r;
343
+ }
344
+ /**
345
+ * Get the current calculated cooldown time
346
+ */
347
+ getCooldownTime() {
348
+ return this.calculateCooldownTime();
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;
358
+ }
359
+ getDataSize() {
360
+ const { nodes: t, links: i } = this.graph.graphData();
361
+ return {
362
+ nodes: t.length,
363
+ links: i.length
364
+ };
365
+ }
366
+ getAllNodeIds() {
367
+ return Array.from(this.nodesMap.keys());
368
+ }
369
+ updateNode(t, i) {
370
+ const r = this.nodesMap.get(t.toString());
371
+ if (r) {
372
+ Object.assign(r, i);
373
+ const n = this.data.nodes.findIndex((a) => a.id.toString() === t.toString());
374
+ return n !== -1 && (this.data.nodes[n] = r), this.refreshGraph(), !0;
375
+ }
376
+ return !1;
377
+ }
378
+ removeNode(t) {
379
+ const i = t.toString();
380
+ return this.nodesMap.has(i) ? (this.nodesMap.delete(i), this.data.nodes = this.data.nodes.filter((r) => r.id.toString() !== i), this.data.links = this.data.links.filter((r) => {
381
+ const n = typeof r.source == "object" ? r.source.id : r.source, a = typeof r.target == "object" ? r.target.id : r.target;
382
+ return n.toString() !== i && a.toString() !== i;
383
+ }), this.graph.cooldownTime(this.calculateCooldownTime()), this.refreshGraph(), !0) : !1;
384
+ }
385
+ async addData(t) {
386
+ t.nodes.forEach((i) => {
387
+ this.nodesMap.has(i.id.toString()) || this.nodesMap.set(i.id.toString(), i);
388
+ }), t.links.forEach((i) => {
389
+ const r = this.createLinkKey(i.source, i.target);
390
+ this.linkMap.has(r) ? console.log("link already exists", i) : this.linkMap.set(r, i);
391
+ }), this.data = {
392
+ nodes: Array.from(this.nodesMap.values()),
393
+ links: Array.from(this.linkMap.values())
394
+ }, console.log("this.data", this.data, t), this.graph.cooldownTime(this.calculateCooldownTime()), this.graph.graphData(this.data);
395
+ }
396
+ setLabelThreshold(t) {
397
+ this.options.labelThreshold = t, this.render();
398
+ }
399
+ setOptions(t) {
400
+ this.options = { ...this.options, ...t }, this.applyOptions();
401
+ }
402
+ /**
403
+ * Lightweight refresh - only updates graph data
404
+ */
405
+ refreshGraph() {
406
+ this.graphData(this.data);
407
+ }
408
+ /**
409
+ * Complete reinitialization - use when major changes are needed
410
+ */
411
+ reinitialize() {
412
+ this.initGraph();
413
+ }
414
+ reset() {
415
+ this.graph.pauseAnimation();
416
+ const t = this.graph.d3Force("simulation");
417
+ t && t.stop(), this.data = { nodes: [], links: [] }, this.nodesMap.clear(), this.graph = new S(this.container), this.initGraph();
418
+ }
419
+ getData() {
420
+ return this.data;
421
+ }
422
+ getNodesData() {
423
+ return this.data.nodes;
424
+ }
425
+ getLinksData() {
426
+ return this.data.links;
427
+ }
428
+ createLinkKey(t, i) {
429
+ const r = typeof t == "object" ? t.id : t, n = typeof i == "object" ? i.id : i;
430
+ return `${r}-${n}`;
431
+ }
432
+ /**
433
+ * Set graph data (chainable method)
434
+ * @param data - Graph data to set
435
+ */
436
+ graphData(t) {
437
+ return this.nodesMap.clear(), this.linkMap.clear(), t.nodes.forEach((i) => {
438
+ this.nodesMap.has(i.id.toString()) || this.nodesMap.set(i.id.toString(), i);
439
+ }), t.links.forEach((i) => {
440
+ const r = this.createLinkKey(i.source, i.target);
441
+ this.linkMap.has(r) || this.linkMap.set(r, i);
442
+ }), this.data = {
443
+ nodes: Array.from(this.nodesMap.values()),
444
+ links: Array.from(this.linkMap.values())
445
+ }, this.graph.cooldownTime(this.calculateCooldownTime()), this.graph.graphData(this.data), this;
446
+ }
447
+ /**
448
+ * Enable or disable group visualization
449
+ */
450
+ showGroups(t) {
451
+ return this.options.showGroups = t, t && Object.entries({
452
+ groupBy: "topic",
453
+ groupBorderColor: "#666",
454
+ groupBorderWidth: 2,
455
+ groupBorderOpacity: 0.3,
456
+ groupLabelColor: "#333",
457
+ groupLabelSize: 16,
458
+ groupLabelThreshold: 0.8,
459
+ groupPadding: 20
460
+ }).forEach(([r, n]) => {
461
+ this.options[r] === void 0 && (this.options[r] = n);
462
+ }), this.applyOptions(), this.refreshGraph(), this;
463
+ }
464
+ /**
465
+ * Set the property to group nodes by
466
+ */
467
+ setGroupBy(t) {
468
+ return this.options.groupBy = t, this.applyOptions(), this.refreshGraph(), this;
469
+ }
470
+ /**
471
+ * Set group visualization options
472
+ */
473
+ setGroupOptions(t) {
474
+ 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;
475
+ }
476
+ /**
477
+ * Get all available groups
478
+ */
479
+ getGroups() {
480
+ const t = /* @__PURE__ */ new Set();
481
+ return this.data.nodes.forEach((i) => {
482
+ const r = this.getNodeGroupId(i);
483
+ r && t.add(r);
484
+ }), Array.from(t);
485
+ }
486
+ /**
487
+ * Get nodes in a specific group
488
+ */
489
+ getNodesInGroup(t) {
490
+ return this.data.nodes.filter((i) => this.getNodeGroupId(i) === t);
491
+ }
492
+ /**
493
+ * Get current options
494
+ */
495
+ getOptions() {
496
+ return { ...this.options };
497
+ }
498
+ destroy() {
499
+ this.worker && (this.worker.terminate(), this.worker = null), this.graph._destructor();
500
+ }
501
+ }
502
+ export {
503
+ x as ForceGraph
504
+ };
@@ -0,0 +1,3 @@
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"})});
@@ -0,0 +1,195 @@
1
+ import { default as default_2 } from 'force-graph';
2
+ import { GraphData as GraphData_2 } from 'force-graph';
3
+ import { LinkObject } from 'force-graph';
4
+ import { NodeObject } from 'force-graph';
5
+
6
+ export declare class ForceGraph {
7
+ private container;
8
+ private graph;
9
+ private data;
10
+ private nodesMap;
11
+ private linkMap;
12
+ private options;
13
+ private worker;
14
+ private groupBounds;
15
+ constructor(container: HTMLElement, initialData?: GraphData, options?: GraphOptions);
16
+ private initGraph;
17
+ renderer(): default_2<NodeData, LinkObject<NodeData>>;
18
+ focusPosition(nodeData?: {
19
+ id?: string;
20
+ x?: number;
21
+ y?: number;
22
+ }): void;
23
+ render(): void;
24
+ applyOptions(): void;
25
+ private getNodeSize;
26
+ private getNodeLabel;
27
+ updateData(data: GraphData): void;
28
+ private getNodeColor;
29
+ private getNodeBorderColor;
30
+ private applyLinkOptions;
31
+ private getLinkCurvature;
32
+ private getLinkProperty;
33
+ /**
34
+ * Calculate group boundaries based on node positions
35
+ */
36
+ private calculateGroupBounds;
37
+ /**
38
+ * Get the group ID for a node
39
+ */
40
+ private getNodeGroupId;
41
+ /**
42
+ * Render group borders and labels
43
+ */
44
+ private renderGroups;
45
+ /**
46
+ * Get group border color
47
+ */
48
+ private getGroupBorderColor;
49
+ /**
50
+ * Get group label color
51
+ */
52
+ private getGroupLabelColor;
53
+ getNodeById(id: string | number): NodeData | undefined;
54
+ hasNode(id: string | number): boolean;
55
+ getNodeCount(): number;
56
+ /**
57
+ * Calculate dynamic cooldown time based on node count
58
+ * Minimum: 2500ms
59
+ * Normal: node.length * 150ms
60
+ */
61
+ private calculateCooldownTime;
62
+ /**
63
+ * Get the current calculated cooldown time
64
+ */
65
+ getCooldownTime(): number;
66
+ /**
67
+ * Manually update the cooldown time based on current node count
68
+ */
69
+ updateCooldownTime(): void;
70
+ getLinkCount(): number;
71
+ getDataSize(): {
72
+ nodes: number;
73
+ links: number;
74
+ };
75
+ getAllNodeIds(): string[];
76
+ updateNode(id: string | number, updates: Partial<NodeData>): boolean;
77
+ removeNode(id: string | number): boolean;
78
+ addData(newData: GraphData): Promise<void>;
79
+ setLabelThreshold(threshold: number): void;
80
+ setOptions(options: Partial<GraphOptions>): void;
81
+ /**
82
+ * Lightweight refresh - only updates graph data
83
+ */
84
+ refreshGraph(): void;
85
+ /**
86
+ * Complete reinitialization - use when major changes are needed
87
+ */
88
+ reinitialize(): void;
89
+ reset(): void;
90
+ getData(): GraphData;
91
+ getNodesData(): GraphData['nodes'];
92
+ getLinksData(): GraphData['links'];
93
+ private createLinkKey;
94
+ /**
95
+ * Set graph data (chainable method)
96
+ * @param data - Graph data to set
97
+ */
98
+ graphData(data: GraphData): ForceGraph;
99
+ /**
100
+ * Enable or disable group visualization
101
+ */
102
+ showGroups(show: boolean): ForceGraph;
103
+ /**
104
+ * Set the property to group nodes by
105
+ */
106
+ setGroupBy(groupBy: string | ((node: NodeData) => string | undefined)): ForceGraph;
107
+ /**
108
+ * Set group visualization options
109
+ */
110
+ setGroupOptions(options: {
111
+ borderColor?: string | ((groupId: string) => string);
112
+ borderWidth?: number;
113
+ borderOpacity?: number;
114
+ labelColor?: string | ((groupId: string) => string);
115
+ labelSize?: number;
116
+ labelThreshold?: number;
117
+ padding?: number;
118
+ }): ForceGraph;
119
+ /**
120
+ * Get all available groups
121
+ */
122
+ getGroups(): string[];
123
+ /**
124
+ * Get nodes in a specific group
125
+ */
126
+ getNodesInGroup(groupId: string): NodeData[];
127
+ /**
128
+ * Get current options
129
+ */
130
+ getOptions(): GraphOptions;
131
+ destroy(): void;
132
+ }
133
+
134
+ export declare interface GraphData extends GraphData_2<NodeData, LinkData> {
135
+ }
136
+
137
+ export declare interface GraphOptions {
138
+ height?: number;
139
+ width?: number;
140
+ labelThreshold?: number;
141
+ nodeSize?: number | ((node: NodeData) => number);
142
+ linkWidth?: number | ((link: LinkData) => number);
143
+ nodeLabel?: string | ((node: NodeData) => string);
144
+ nodeLabelColor?: string | ((node: NodeData) => string);
145
+ nodeColor?: string | ((node: NodeData) => string);
146
+ nodeBorderColor?: string | ((node: NodeData) => string);
147
+ nodeBorderWidth?: number | ((node: NodeData) => number);
148
+ linkLabel?: string | ((link: LinkData) => string);
149
+ nodeIcon?: string | ((node: NodeData) => string);
150
+ cluster?: (node: NodeData) => boolean | undefined | null;
151
+ collide?: (node: NodeData) => number;
152
+ loading?: boolean;
153
+ keepDragPosition?: boolean;
154
+ nodeClickHandler?: (node: NodeData) => void;
155
+ linkCurvature?: number | string | ((link: LinkData) => number);
156
+ linkDirectionalParticles?: number | ((link: LinkData) => number);
157
+ linkDirectionalParticleSpeed?: number | ((link: LinkData) => number);
158
+ linkDirectionalParticleWidth?: number | ((link: LinkData) => number);
159
+ linkDirectionalParticleColor?: string | ((link: LinkData) => string);
160
+ showGroups?: boolean;
161
+ groupBy?: string | ((node: NodeData) => string | undefined);
162
+ groupBorderColor?: string | ((groupId: string) => string);
163
+ groupBorderWidth?: number;
164
+ groupBorderOpacity?: number;
165
+ groupLabelColor?: string | ((groupId: string) => string);
166
+ groupLabelSize?: number;
167
+ groupLabelThreshold?: number;
168
+ groupPadding?: number;
169
+ }
170
+
171
+ export declare interface LinkData extends LinkObject<NodeData> {
172
+ source: string | number | NodeData;
173
+ target: string | number | NodeData;
174
+ weight?: number;
175
+ color?: string;
176
+ curvature?: number;
177
+ [key: string]: any;
178
+ }
179
+
180
+ export declare interface NodeData extends NodeObject {
181
+ id: string | number;
182
+ x?: number;
183
+ y?: number;
184
+ fx?: number;
185
+ fy?: number;
186
+ color?: string;
187
+ value?: number;
188
+ label?: string;
189
+ platform?: string;
190
+ type?: string;
191
+ topic?: string;
192
+ [key: string]: any;
193
+ }
194
+
195
+ export { }
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@logixode/force-graph-lib",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "files": [
7
+ "dist/force-graph-lib.es.js",
8
+ "dist/force-graph-lib.umd.js",
9
+ "dist/index.d.ts"
10
+ ],
11
+ "main": "./dist/force-graph-lib.umd.js",
12
+ "module": "./dist/force-graph-lib.es.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/force-graph-lib.es.js",
18
+ "require": "./dist/force-graph-lib.umd.js"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "run-p type-check \"build-only {@}\" --",
24
+ "build:lib": "VITE_BUILD_TARGET=lib vite build && npx tsc --project tsconfig.lib.json --noEmit",
25
+ "preview": "vite preview",
26
+ "test:unit": "vitest",
27
+ "test:e2e": "playwright test",
28
+ "build-only": "vite build",
29
+ "type-check": "vue-tsc --build",
30
+ "lint": "eslint . --fix",
31
+ "format": "prettier --write src/"
32
+ },
33
+ "dependencies": {
34
+ "@formkit/auto-animate": "^0.8.2",
35
+ "@tailwindcss/vite": "^4.1.11",
36
+ "@tanstack/vue-query": "^5.83.1",
37
+ "@vueuse/core": "^13.6.0",
38
+ "axios": "^1.11.0",
39
+ "class-variance-authority": "^0.7.1",
40
+ "clsx": "^2.1.1",
41
+ "d3-force-clustering": "^1.0.0",
42
+ "lucide-vue-next": "^0.525.0",
43
+ "reka-ui": "^2.4.1",
44
+ "tailwind-merge": "^3.3.1",
45
+ "tailwindcss": "^4.1.11",
46
+ "tw-animate-css": "^1.3.5",
47
+ "vue-router": "^4.5.1",
48
+ "vue-sonner": "^2.0.2"
49
+ },
50
+ "peerDependencies": {
51
+ "d3": "^7.9.0",
52
+ "force-graph": "^1.50.1",
53
+ "vue": "^3.5.17"
54
+ },
55
+ "devDependencies": {
56
+ "@playwright/test": "^1.53.1",
57
+ "@tailwindcss/cli": "^4.1.11",
58
+ "@tsconfig/node22": "^22.0.2",
59
+ "@types/d3": "^7.4.3",
60
+ "@types/jsdom": "^21.1.7",
61
+ "@types/node": "^22.16.5",
62
+ "@vitejs/plugin-vue": "^6.0.0",
63
+ "@vitest/eslint-plugin": "^1.2.7",
64
+ "@vue/eslint-config-prettier": "^10.2.0",
65
+ "@vue/eslint-config-typescript": "^14.5.1",
66
+ "@vue/test-utils": "^2.4.6",
67
+ "@vue/tsconfig": "^0.7.0",
68
+ "eslint": "^9.29.0",
69
+ "eslint-plugin-playwright": "^2.2.0",
70
+ "eslint-plugin-vue": "~10.2.0",
71
+ "jiti": "^2.4.2",
72
+ "jsdom": "^26.1.0",
73
+ "npm-run-all2": "^8.0.4",
74
+ "prettier": "3.5.3",
75
+ "typescript": "~5.8.0",
76
+ "unplugin-vue-router": "^0.14.0",
77
+ "vite": "^7.0.0",
78
+ "vite-plugin-dts": "^4.5.4",
79
+ "vite-plugin-vue-devtools": "^7.7.7",
80
+ "vitest": "^3.2.4",
81
+ "vue-tsc": "^2.2.10"
82
+ }
83
+ }