@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.
@@ -1,133 +1,6 @@
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 {
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: [] }, 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);
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 n = {
16
+ const e = {
144
17
  labelThreshold: 1.5,
18
+ // Show labels when they're not bigger than the node
145
19
  showGroups: !1,
146
- ...r
147
- }, a = n.showGroups ? {
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
- ...n,
159
- ...a
32
+ ...e,
33
+ ...s
160
34
  }, this.initGraph();
161
35
  }
162
36
  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));
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: r } = this.nodesMap.get(t.id) ?? { x: 0, y: 0 };
182
- i && r && (t = { x: i, y: r });
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", k.forceManyBody().strength(-100)).d3Force(
189
- "link",
190
- k.forceLink().id((t) => t.index ?? "").distance(30)
191
- ).d3Force("center", k.forceCenter());
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.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);
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) : this.graph.zoom() < (this.options.labelThreshold || 1.5) ? "" : t.label || t.id;
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((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);
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 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))
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, ...r],
223
- links: [...this.data.links, ...a]
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((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);
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
- }), a !== 1 / 0 && this.groupBounds.set(n, {
272
- minX: a - t,
273
- minY: s - t,
274
- maxX: h + t,
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: r
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((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();
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 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;
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
- f - u / 2 - 4,
305
- c - d / 2 - 2,
180
+ l - u / 2 - 4,
181
+ d - g / 2 - 2,
306
182
  u + 8,
307
- d + 4
308
- ), t.globalAlpha = 1, t.fillStyle = e, t.fillText(n, f, c), t.restore();
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
- * 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
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
- 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;
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 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;
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((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;
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 r = this.createLinkKey(i.source, i.target);
390
- this.linkMap.has(r) ? console.log("link already exists", i) : this.linkMap.set(r, i);
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
- }, console.log("this.data", this.data, t), this.graph.cooldownTime(this.calculateCooldownTime()), this.graph.graphData(this.data);
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 S(this.container), this.initGraph();
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 r = typeof t == "object" ? t.id : t, n = typeof i == "object" ? i.id : i;
430
- return `${r}-${n}`;
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 r = this.createLinkKey(i.source, i.target);
441
- this.linkMap.has(r) || this.linkMap.set(r, i);
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.calculateCooldownTime()), this.graph.graphData(this.data), 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(([r, n]) => {
461
- this.options[r] === void 0 && (this.options[r] = n);
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 r = this.getNodeGroupId(i);
483
- r && t.add(r);
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
- x as ForceGraph
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
- applyOptions(): void;
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
- * Minimum: 2500ms
59
- * Normal: node.length * 150ms
60
- */
61
- private calculateCooldownTime;
62
- /**
63
- * Get the current calculated cooldown time
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",
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.com/logixode/force-graph-lib",
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": "bunx --bun vitepress 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
- "vitepress": "^2.0.0-alpha.12"
71
+ "vite": "^7.1.2"
72
+ },
73
+ "overrides": {
74
+ "ajv": "^8.12.0"
72
75
  }
73
76
  }