@logixode/force-graph-lib 0.6.1 → 0.7.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/dist/force-graph-lib.js +72 -43
- package/dist/force-graph-lib.umd.cjs +1 -1
- package/dist/index.d.ts +18 -0
- package/package.json +1 -1
package/dist/force-graph-lib.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
1
|
+
import C from "force-graph";
|
|
2
|
+
import * as c from "d3";
|
|
3
3
|
class S {
|
|
4
4
|
container;
|
|
5
5
|
graph;
|
|
@@ -11,9 +11,10 @@ class S {
|
|
|
11
11
|
groupHulls = /* @__PURE__ */ new Map();
|
|
12
12
|
nodeGroupsCache = null;
|
|
13
13
|
isFirstRender = !0;
|
|
14
|
-
autoColorScale =
|
|
14
|
+
autoColorScale = c.scaleOrdinal(c.schemeCategory10);
|
|
15
|
+
charCache = /* @__PURE__ */ new Map();
|
|
15
16
|
constructor(t, o = { nodes: [], links: [] }, i = {}) {
|
|
16
|
-
this.container = t, this.data = o, this.graph = new
|
|
17
|
+
this.container = t, this.data = o, this.graph = new C(this.container), o.nodes.forEach((r) => {
|
|
17
18
|
this.nodesMap.set(r.id.toString(), r);
|
|
18
19
|
});
|
|
19
20
|
const e = {
|
|
@@ -71,12 +72,21 @@ class S {
|
|
|
71
72
|
t.fx = t.x, t.fy = t.y;
|
|
72
73
|
}), this.options.pointerInteraction || this.graph.enablePointerInteraction(!1), this.graph.nodeCanvasObject((t, o, i) => {
|
|
73
74
|
t === this.data.nodes[0] && this.renderGroups(o, i);
|
|
74
|
-
const e = this.getNodeSize(t) * 2, s = typeof this.options.nodeBorderWidth == "function" ? this.options.nodeBorderWidth(t) : this.options.nodeBorderWidth, r = this.resolveNodeShape(t),
|
|
75
|
-
this.drawNodeShape(o,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
const e = this.getNodeSize(t) * 2, s = typeof this.options.nodeBorderWidth == "function" ? this.options.nodeBorderWidth(t) : this.options.nodeBorderWidth, r = this.resolveNodeShape(t), p = t.x || 0, n = t.y || 0;
|
|
76
|
+
if (this.drawNodeShape(o, p, n, e, r), o.fillStyle = this.getNodeColor(t), o.fill(), s && s > 0 && (this.drawNodeShape(o, p, n, e, r), o.strokeStyle = this.getNodeBorderColor(t), o.lineWidth = s, o.stroke()), this.options.nodeChar) {
|
|
77
|
+
const a = this.options.nodeChar(t);
|
|
78
|
+
if (a?.char) {
|
|
79
|
+
const d = `${a.char}|${a.color ?? "#000"}`, l = this.charCache.get(d);
|
|
80
|
+
if (l) {
|
|
81
|
+
const u = a.fontSize ?? e * (this.options.nodeCharSizeRatio ?? 0.8);
|
|
82
|
+
o.drawImage(l, p - u / 2, n - u / 2, u, u);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const h = this.getNodeLabel(t);
|
|
87
|
+
if (h && this.shouldShowLabel(t, i)) {
|
|
88
|
+
const a = typeof this.options.nodeLabelColor == "function" ? this.options.nodeLabelColor(t) : this.options.nodeLabelColor ?? "#555", l = (this.options.labelFontSize || 14) / i;
|
|
89
|
+
o.font = `${l}px Arial`, o.fillStyle = a, o.textAlign = "center", o.textBaseline = "middle", o.fillText(h, t.x || 0, (t.y || 0) + e + l / 2 + 2);
|
|
80
90
|
}
|
|
81
91
|
}), this.applyLinkOptions();
|
|
82
92
|
}
|
|
@@ -86,6 +96,25 @@ class S {
|
|
|
86
96
|
getNodeLabel(t) {
|
|
87
97
|
return typeof this.options.nodeLabel == "function" ? this.options.nodeLabel(t) : t?.label || t.id;
|
|
88
98
|
}
|
|
99
|
+
/** Render a single char/emoji to a 64×64 OffscreenCanvas and return an ImageBitmap */
|
|
100
|
+
buildCharBitmap(t, o) {
|
|
101
|
+
const e = new OffscreenCanvas(64, 64), s = e.getContext("2d");
|
|
102
|
+
return s.font = `${64 * 0.8}px sans-serif`, s.textAlign = "center", s.textBaseline = "middle", s.fillStyle = o, s.fillText(t, 64 / 2, 64 / 2), e.transferToImageBitmap();
|
|
103
|
+
}
|
|
104
|
+
/** Idempotent — only adds cache entries for chars not yet cached */
|
|
105
|
+
ensureCharCache() {
|
|
106
|
+
if (this.options.nodeChar)
|
|
107
|
+
for (const t of this.data.nodes) {
|
|
108
|
+
const o = this.options.nodeChar(t);
|
|
109
|
+
if (!o?.char) continue;
|
|
110
|
+
const i = o.color ?? "#000", e = `${o.char}|${i}`;
|
|
111
|
+
this.charCache.has(e) || this.charCache.set(e, this.buildCharBitmap(o.char, i));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Release GPU memory and clear the cache */
|
|
115
|
+
clearCharCache() {
|
|
116
|
+
this.charCache.forEach((t) => t.close()), this.charCache.clear();
|
|
117
|
+
}
|
|
89
118
|
/**
|
|
90
119
|
* Check if label should be shown based on threshold logic
|
|
91
120
|
* The threshold determines when labels become too big relative to nodes
|
|
@@ -107,7 +136,7 @@ class S {
|
|
|
107
136
|
this.data = {
|
|
108
137
|
nodes: [...this.data.nodes, ...i],
|
|
109
138
|
links: [...this.data.links, ...s]
|
|
110
|
-
}, this.refreshGraph();
|
|
139
|
+
}, this.ensureCharCache(), this.refreshGraph();
|
|
111
140
|
}
|
|
112
141
|
getNodeColor(t) {
|
|
113
142
|
if (typeof this.options.nodeColor == "function") {
|
|
@@ -138,18 +167,18 @@ class S {
|
|
|
138
167
|
break;
|
|
139
168
|
}
|
|
140
169
|
case "star": {
|
|
141
|
-
const r = e,
|
|
142
|
-
for (let
|
|
143
|
-
const
|
|
144
|
-
|
|
170
|
+
const r = e, p = e * 0.4, n = 5;
|
|
171
|
+
for (let h = 0; h < n * 2; h++) {
|
|
172
|
+
const a = h % 2 === 0 ? r : p, d = Math.PI / n * h - Math.PI / 2, l = o + a * Math.cos(d), u = i + a * Math.sin(d);
|
|
173
|
+
h === 0 ? t.moveTo(l, u) : t.lineTo(l, u);
|
|
145
174
|
}
|
|
146
175
|
t.closePath();
|
|
147
176
|
break;
|
|
148
177
|
}
|
|
149
178
|
case "hexagon": {
|
|
150
179
|
for (let r = 0; r < 6; r++) {
|
|
151
|
-
const
|
|
152
|
-
r === 0 ? t.moveTo(n,
|
|
180
|
+
const p = Math.PI / 3 * r - Math.PI / 6, n = o + e * Math.cos(p), h = i + e * Math.sin(p);
|
|
181
|
+
r === 0 ? t.moveTo(n, h) : t.lineTo(n, h);
|
|
153
182
|
}
|
|
154
183
|
t.closePath();
|
|
155
184
|
break;
|
|
@@ -189,26 +218,26 @@ class S {
|
|
|
189
218
|
this.ensureNodeGroupsCache(), this.nodeGroupsCache.forEach((i, e) => {
|
|
190
219
|
if (i.length === 0) return;
|
|
191
220
|
const s = [];
|
|
192
|
-
let r = 1 / 0,
|
|
221
|
+
let r = 1 / 0, p = 0;
|
|
193
222
|
if (i.forEach((n) => {
|
|
194
223
|
if (n.x !== void 0 && n.y !== void 0) {
|
|
195
|
-
const
|
|
196
|
-
for (let
|
|
197
|
-
const
|
|
224
|
+
const a = this.getNodeSize(n) + t;
|
|
225
|
+
for (let d = 0; d < 8; d++) {
|
|
226
|
+
const l = d * Math.PI / 4;
|
|
198
227
|
s.push([
|
|
199
|
-
n.x +
|
|
200
|
-
n.y +
|
|
228
|
+
n.x + a * Math.cos(l),
|
|
229
|
+
n.y + a * Math.sin(l)
|
|
201
230
|
]);
|
|
202
231
|
}
|
|
203
|
-
n.y -
|
|
232
|
+
n.y - a < r && (r = n.y - a, p = n.x);
|
|
204
233
|
}
|
|
205
234
|
}), s.length >= 3) {
|
|
206
|
-
const n =
|
|
235
|
+
const n = c.polygonHull(s);
|
|
207
236
|
if (n) {
|
|
208
|
-
const
|
|
209
|
-
|
|
237
|
+
const h = n.filter((a) => Math.abs(a[1] - r) < t);
|
|
238
|
+
h.length > 0 && (p = h.reduce((a, d) => a + d[0], 0) / h.length), this.groupHulls.set(e, {
|
|
210
239
|
points: n,
|
|
211
|
-
labelX:
|
|
240
|
+
labelX: p,
|
|
212
241
|
labelY: r,
|
|
213
242
|
nodes: i
|
|
214
243
|
});
|
|
@@ -241,19 +270,19 @@ class S {
|
|
|
241
270
|
*/
|
|
242
271
|
renderGroups(t, o) {
|
|
243
272
|
if (!this.options.showGroups) return;
|
|
244
|
-
const i =
|
|
273
|
+
const i = c.line().curve(c.curveCatmullRomClosed).context(t);
|
|
245
274
|
this.groupHulls.forEach((e, s) => {
|
|
246
|
-
const r = this.getGroupFillColor(s),
|
|
247
|
-
t.save(), t.beginPath(), i(e.points), r && (t.globalAlpha =
|
|
248
|
-
const
|
|
249
|
-
t.save(), t.font = `bold ${
|
|
250
|
-
const u = e.labelY -
|
|
275
|
+
const r = this.getGroupFillColor(s), p = this.options.groupFillOpacity ?? 1, n = this.getGroupBorderColor(s), h = this.options.groupBorderWidth ?? 0, a = this.options.groupBorderOpacity ?? 0.3;
|
|
276
|
+
t.save(), t.beginPath(), i(e.points), r && (t.globalAlpha = p, t.fillStyle = r, t.fill()), h > 0 && n && (t.globalAlpha = a, t.strokeStyle = n, t.lineWidth = h / o, t.setLineDash([]), t.stroke()), t.restore();
|
|
277
|
+
const d = this.getGroupLabelColor(s), l = (this.options.groupLabelSize || 16) / o;
|
|
278
|
+
t.save(), t.font = `bold ${l}px Arial`, t.fillStyle = d, t.textAlign = "center", t.textBaseline = "middle";
|
|
279
|
+
const u = e.labelY - l / 2, f = t.measureText(s).width, g = l;
|
|
251
280
|
t.globalAlpha = 0.8, t.fillStyle = "rgba(255, 255, 255, 0.9)", t.fillRect(
|
|
252
281
|
e.labelX - f / 2 - 4,
|
|
253
|
-
u -
|
|
282
|
+
u - g / 2 - 2,
|
|
254
283
|
f + 8,
|
|
255
|
-
|
|
256
|
-
), t.globalAlpha = 1, t.fillStyle =
|
|
284
|
+
g + 4
|
|
285
|
+
), t.globalAlpha = 1, t.fillStyle = d, t.fillText(s, e.labelX, u), t.restore();
|
|
257
286
|
});
|
|
258
287
|
}
|
|
259
288
|
/**
|
|
@@ -332,13 +361,13 @@ class S {
|
|
|
332
361
|
}), this.data = {
|
|
333
362
|
nodes: Array.from(this.nodesMap.values()),
|
|
334
363
|
links: Array.from(this.linkMap.values())
|
|
335
|
-
}, this.graph.cooldownTime(this.getCooldownTime()), this.graph.graphData(this.data);
|
|
364
|
+
}, this.ensureCharCache(), this.graph.cooldownTime(this.getCooldownTime()), this.graph.graphData(this.data);
|
|
336
365
|
}
|
|
337
366
|
setLabelThreshold(t) {
|
|
338
367
|
this.options.labelThreshold = t, this.render();
|
|
339
368
|
}
|
|
340
369
|
setOptions(t) {
|
|
341
|
-
this.options = { ...this.options, ...t }, this.applyOptions();
|
|
370
|
+
this.options = { ...this.options, ...t }, t.nodeChar !== void 0 && (this.clearCharCache(), this.ensureCharCache()), this.applyOptions();
|
|
342
371
|
}
|
|
343
372
|
/**
|
|
344
373
|
* Lightweight refresh - only updates graph data
|
|
@@ -353,7 +382,7 @@ class S {
|
|
|
353
382
|
this.initGraph();
|
|
354
383
|
}
|
|
355
384
|
reset() {
|
|
356
|
-
this.graph.pauseAnimation(), this.data = { nodes: [], links: [] }, this.nodesMap.clear(), this.graph = new
|
|
385
|
+
this.graph.pauseAnimation(), this.data = { nodes: [], links: [] }, this.nodesMap.clear(), this.graph = new C(this.container), this.initGraph();
|
|
357
386
|
}
|
|
358
387
|
getData() {
|
|
359
388
|
return this.data;
|
|
@@ -373,7 +402,7 @@ class S {
|
|
|
373
402
|
* @param data - Graph data to set
|
|
374
403
|
*/
|
|
375
404
|
graphData(t) {
|
|
376
|
-
return this.nodesMap.clear(), this.linkMap.clear(), this.nodeGroupsCache = null, t.nodes.forEach((o) => {
|
|
405
|
+
return this.nodesMap.clear(), this.linkMap.clear(), this.nodeGroupsCache = null, this.clearCharCache(), t.nodes.forEach((o) => {
|
|
377
406
|
this.nodesMap.has(o.id.toString()) || this.nodesMap.set(o.id.toString(), o);
|
|
378
407
|
}), t.links.forEach((o) => {
|
|
379
408
|
const i = this.createLinkKey(o.source, o.target);
|
|
@@ -381,7 +410,7 @@ class S {
|
|
|
381
410
|
}), this.data = {
|
|
382
411
|
nodes: Array.from(this.nodesMap.values()),
|
|
383
412
|
links: Array.from(this.linkMap.values())
|
|
384
|
-
}, this.graph.cooldownTime(this.getCooldownTime()), this.graph.graphData(this.data), this;
|
|
413
|
+
}, this.ensureCharCache(), this.graph.cooldownTime(this.getCooldownTime()), this.graph.graphData(this.data), this;
|
|
385
414
|
}
|
|
386
415
|
/**
|
|
387
416
|
* Enable or disable group visualization
|
|
@@ -436,7 +465,7 @@ class S {
|
|
|
436
465
|
return { ...this.options };
|
|
437
466
|
}
|
|
438
467
|
destroy() {
|
|
439
|
-
this.worker && (this.worker.terminate(), this.worker = null), this.graph._destructor();
|
|
468
|
+
this.worker && (this.worker.terminate(), this.worker = null), this.clearCharCache(), this.graph._destructor();
|
|
440
469
|
}
|
|
441
470
|
}
|
|
442
471
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(u,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("force-graph"),require("d3")):typeof define=="function"&&define.amd?define(["exports","force-graph","d3"],f):(u=typeof globalThis<"u"?globalThis:u||self,f(u.ForceGraphLib={},u.ForceGraph,u.d3))})(this,(function(u,f,k){"use strict";function S(c){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(c){for(const o in c)if(o!=="default"){const i=Object.getOwnPropertyDescriptor(c,o);Object.defineProperty(t,o,i.get?i:{enumerable:!0,get:()=>c[o]})}}return t.default=c,Object.freeze(t)}const g=S(k);class v{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupHulls=new Map;nodeGroupsCache=null;isFirstRender=!0;autoColorScale=g.scaleOrdinal(g.schemeCategory10);constructor(t,o={nodes:[],links:[]},i={}){this.container=t,this.data=o,this.graph=new f(this.container),o.nodes.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e={labelThreshold:1.5,showGroups:!1,...i},s=e.showGroups?{groupBy:"topic",groupFillColor:"rgba(0, 0, 0, 0.05)",groupFillOpacity:1,groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupPadding:20}:{};this.options={...s,...e},this.initGraph()}initGraph(){this.applyOptions(),this.render(),this.graphData(this.data),this.graph.onEngineStop(()=>{if(!this.data.nodes.length){this.isFirstRender=!0;return}this.isFirstRender?(this.options.onRenderComplete?this.options.onRenderComplete():this.graph.zoomToFit(400),this.isFirstRender=!1):this.options.onGraphUpdated&&this.options.onGraphUpdated()}),this.graph.onEngineTick(()=>{this.calculateGroupHulls()})}renderer(){return this.graph}focusPosition(t={}){if(Object.values(t).length){if(t.id){const{x:o,y:i}=this.nodesMap.get(t.id)??{x:0,y:0};o&&i&&(t={x:o,y:i})}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),this.options.cooldownTicks!==void 0&&this.graph.cooldownTicks(this.options.cooldownTicks)}force(t,o){return this.graph.d3Force(t,o)}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,o,i)=>{t===this.data.nodes[0]&&this.renderGroups(o,i);const e=this.getNodeSize(t)*2,s=typeof this.options.nodeBorderWidth=="function"?this.options.nodeBorderWidth(t):this.options.nodeBorderWidth,r=this.resolveNodeShape(t),l=t.x||0,n=t.y||0;this.drawNodeShape(o,l,n,e,r),o.fillStyle=this.getNodeColor(t),o.fill(),s&&s>0&&(this.drawNodeShape(o,l,n,e,r),o.strokeStyle=this.getNodeBorderColor(t),o.lineWidth=s,o.stroke());const a=this.getNodeLabel(t);if(a&&this.shouldShowLabel(t,i)){const h=typeof this.options.nodeLabelColor=="function"?this.options.nodeLabelColor(t):this.options.nodeLabelColor??"#555",d=(this.options.labelFontSize||14)/i;o.font=`${d}px Arial`,o.fillStyle=h,o.textAlign="center",o.textBaseline="middle",o.fillText(a,t.x||0,(t.y||0)+e+d/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,o){const i=this.getNodeSize(t)*2,s=(this.options.labelFontSize||14)/o,r=this.options.labelThreshold||1.5;return s<=i*r}updateData(t){const o=new Set(this.data.nodes.map(r=>r.id.toString())),i=t.nodes.filter(r=>!o.has(r.id.toString()));i.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,...i],links:[...this.data.links,...s]},this.refreshGraph()}getNodeColor(t){if(typeof this.options.nodeColor=="function"){const o=this.options.nodeColor(t);if(o)return o}return t?.color??""}getNodeBorderColor(t){return typeof this.options.nodeBorderColor=="function"?this.options.nodeBorderColor(t):this.options.nodeBorderColor||"#333"}resolveNodeShape(t){return t.shape?t.shape:typeof this.options.nodeShape=="function"?this.options.nodeShape(t):this.options.nodeShape??"circle"}drawNodeShape(t,o,i,e,s){switch(t.beginPath(),s){case"square":{t.rect(o-e,i-e,e*2,e*2);break}case"triangle":{const r=e*Math.sqrt(3);t.moveTo(o,i-e),t.lineTo(o+r/2,i+e/2),t.lineTo(o-r/2,i+e/2),t.closePath();break}case"diamond":{t.moveTo(o,i-e),t.lineTo(o+e,i),t.lineTo(o,i+e),t.lineTo(o-e,i),t.closePath();break}case"star":{const r=e,l=e*.4,n=5;for(let a=0;a<n*2;a++){const h=a%2===0?r:l,p=Math.PI/n*a-Math.PI/2,d=o+h*Math.cos(p),y=i+h*Math.sin(p);a===0?t.moveTo(d,y):t.lineTo(d,y)}t.closePath();break}case"hexagon":{for(let r=0;r<6;r++){const l=Math.PI/3*r-Math.PI/6,n=o+e*Math.cos(l),a=i+e*Math.sin(l);r===0?t.moveTo(n,a):t.lineTo(n,a)}t.closePath();break}case"circle":default:t.arc(o,i,e,0,2*Math.PI);break}}applyLinkOptions(){this.graph.linkColor(t=>typeof this.options.linkColor=="function"?this.options.linkColor(t):this.options.linkColor?this.options.linkColor:t.color??""),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,o){return typeof t=="function"?t(o):t}calculateGroupHulls(){if(!this.options.showGroups)return;this.groupHulls.clear();const t=this.options.groupPadding||20;this.ensureNodeGroupsCache(),this.nodeGroupsCache.forEach((i,e)=>{if(i.length===0)return;const s=[];let r=1/0,l=0;if(i.forEach(n=>{if(n.x!==void 0&&n.y!==void 0){const h=this.getNodeSize(n)+t;for(let p=0;p<8;p++){const d=p*Math.PI/4;s.push([n.x+h*Math.cos(d),n.y+h*Math.sin(d)])}n.y-h<r&&(r=n.y-h,l=n.x)}}),s.length>=3){const n=g.polygonHull(s);if(n){const a=n.filter(h=>Math.abs(h[1]-r)<t);a.length>0&&(l=a.reduce((h,p)=>h+p[0],0)/a.length),this.groupHulls.set(e,{points:n,labelX:l,labelY:r,nodes:i})}}})}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]}}ensureNodeGroupsCache(){this.nodeGroupsCache||(this.nodeGroupsCache=new Map,this.data.nodes.forEach(t=>{const o=this.getNodeGroupId(t);o&&(this.nodeGroupsCache.has(o)||this.nodeGroupsCache.set(o,[]),this.nodeGroupsCache.get(o).push(t))}))}renderGroups(t,o){if(!this.options.showGroups)return;const i=g.line().curve(g.curveCatmullRomClosed).context(t);this.groupHulls.forEach((e,s)=>{const r=this.getGroupFillColor(s),l=this.options.groupFillOpacity??1,n=this.getGroupBorderColor(s),a=this.options.groupBorderWidth??0,h=this.options.groupBorderOpacity??.3;t.save(),t.beginPath(),i(e.points),r&&(t.globalAlpha=l,t.fillStyle=r,t.fill()),a>0&&n&&(t.globalAlpha=h,t.strokeStyle=n,t.lineWidth=a/o,t.setLineDash([]),t.stroke()),t.restore();const p=this.getGroupLabelColor(s),d=(this.options.groupLabelSize||16)/o;t.save(),t.font=`bold ${d}px Arial`,t.fillStyle=p,t.textAlign="center",t.textBaseline="middle";const y=e.labelY-d/2,C=t.measureText(s).width,b=d;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(e.labelX-C/2-4,y-b/2-2,C+8,b+4),t.globalAlpha=1,t.fillStyle=p,t.fillText(s,e.labelX,y),t.restore()})}getGroupFillColor(t){let o;return typeof this.options.groupFillColor=="function"?o=this.options.groupFillColor(t):o=this.options.groupFillColor,o==="auto"?this.autoColorScale(t):o}getGroupBorderColor(t){let o;return typeof this.options.groupBorderColor=="function"?o=this.options.groupBorderColor(t):o=this.options.groupBorderColor,o==="auto"?this.autoColorScale(t):o||"#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(){if(this.options.cooldownTime!==void 0)return this.options.cooldownTime;const o=this.data.nodes.length*(125/100);return Math.max(4e3,o)}getDataSize(){const{nodes:t,links:o}=this.graph.graphData();return{nodes:t.length,links:o.length}}getAllNodeIds(){return Array.from(this.nodesMap.keys())}updateNode(t,o){const i=this.nodesMap.get(t.toString());if(i){Object.assign(i,o);const e=this.data.nodes.findIndex(s=>s.id.toString()===t.toString());return e!==-1&&(this.data.nodes[e]=i),this.refreshGraph(),!0}return!1}removeNode(t){const o=t.toString();return this.nodesMap.has(o)?(this.nodesMap.delete(o),this.data.nodes=this.data.nodes.filter(i=>i.id.toString()!==o),this.data.links=this.data.links.filter(i=>{const e=typeof i.source=="object"?i.source.id:i.source,s=typeof i.target=="object"?i.target.id:i.target;return e?.toString()!==o&&s?.toString()!==o}),this.refreshGraph(),!0):!1}async addData(t){t.nodes.forEach(o=>{this.nodesMap.has(o.id.toString())||this.nodesMap.set(o.id.toString(),o)}),t.links.forEach(o=>{const i=this.createLinkKey(o.source,o.target);this.linkMap.has(i)?console.log("link already exists",o):this.linkMap.set(i,o)}),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(),this.data={nodes:[],links:[]},this.nodesMap.clear(),this.graph=new f(this.container),this.initGraph()}getData(){return this.data}getNodesData(){return this.data.nodes}getLinksData(){return this.data.links}createLinkKey(t,o){const i=t===void 0?"undefined":typeof t=="object"?t.id:t,e=o===void 0?"undefined":typeof o=="object"?o.id:o;return`${i}-${e}`}graphData(t){return this.nodesMap.clear(),this.linkMap.clear(),this.nodeGroupsCache=null,t.nodes.forEach(o=>{this.nodesMap.has(o.id.toString())||this.nodesMap.set(o.id.toString(),o)}),t.links.forEach(o=>{const i=this.createLinkKey(o.source,o.target);this.linkMap.has(i)||this.linkMap.set(i,o)}),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",groupFillColor:"rgba(0, 0, 0, 0.05)",groupFillOpacity:1,groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupPadding:20}).forEach(([i,e])=>{this.options[i]===void 0&&(this.options[i]=e)}),this.applyOptions(),this.refreshGraph(),this}setGroupBy(t){return this.options.groupBy=t,this.applyOptions(),this.refreshGraph(),this}setGroupOptions(t){return t.fillColor!==void 0&&(this.options.groupFillColor=t.fillColor),t.fillOpacity!==void 0&&(this.options.groupFillOpacity=t.fillOpacity),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.padding!==void 0&&(this.options.groupPadding=t.padding),this.applyOptions(),this.refreshGraph(),this}getGroups(){const t=new Set;return this.data.nodes.forEach(o=>{const i=this.getNodeGroupId(o);i&&t.add(i)}),Array.from(t)}getNodesInGroup(t){return this.data.nodes.filter(o=>this.getNodeGroupId(o)===t)}getOptions(){return{...this.options}}destroy(){this.worker&&(this.worker.terminate(),this.worker=null),this.graph._destructor()}}u.ForceGraph=v,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(c,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("force-graph"),require("d3")):typeof define=="function"&&define.amd?define(["exports","force-graph","d3"],f):(c=typeof globalThis<"u"?globalThis:c||self,f(c.ForceGraphLib={},c.ForceGraph,c.d3))})(this,(function(c,f,k){"use strict";function S(g){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(g){for(const o in g)if(o!=="default"){const i=Object.getOwnPropertyDescriptor(g,o);Object.defineProperty(t,o,i.get?i:{enumerable:!0,get:()=>g[o]})}}return t.default=g,Object.freeze(t)}const C=S(k);class m{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupHulls=new Map;nodeGroupsCache=null;isFirstRender=!0;autoColorScale=C.scaleOrdinal(C.schemeCategory10);charCache=new Map;constructor(t,o={nodes:[],links:[]},i={}){this.container=t,this.data=o,this.graph=new f(this.container),o.nodes.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e={labelThreshold:1.5,showGroups:!1,...i},s=e.showGroups?{groupBy:"topic",groupFillColor:"rgba(0, 0, 0, 0.05)",groupFillOpacity:1,groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupPadding:20}:{};this.options={...s,...e},this.initGraph()}initGraph(){this.applyOptions(),this.render(),this.graphData(this.data),this.graph.onEngineStop(()=>{if(!this.data.nodes.length){this.isFirstRender=!0;return}this.isFirstRender?(this.options.onRenderComplete?this.options.onRenderComplete():this.graph.zoomToFit(400),this.isFirstRender=!1):this.options.onGraphUpdated&&this.options.onGraphUpdated()}),this.graph.onEngineTick(()=>{this.calculateGroupHulls()})}renderer(){return this.graph}focusPosition(t={}){if(Object.values(t).length){if(t.id){const{x:o,y:i}=this.nodesMap.get(t.id)??{x:0,y:0};o&&i&&(t={x:o,y:i})}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),this.options.cooldownTicks!==void 0&&this.graph.cooldownTicks(this.options.cooldownTicks)}force(t,o){return this.graph.d3Force(t,o)}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,o,i)=>{t===this.data.nodes[0]&&this.renderGroups(o,i);const e=this.getNodeSize(t)*2,s=typeof this.options.nodeBorderWidth=="function"?this.options.nodeBorderWidth(t):this.options.nodeBorderWidth,r=this.resolveNodeShape(t),p=t.x||0,n=t.y||0;if(this.drawNodeShape(o,p,n,e,r),o.fillStyle=this.getNodeColor(t),o.fill(),s&&s>0&&(this.drawNodeShape(o,p,n,e,r),o.strokeStyle=this.getNodeBorderColor(t),o.lineWidth=s,o.stroke()),this.options.nodeChar){const a=this.options.nodeChar(t);if(a?.char){const d=`${a.char}|${a.color??"#000"}`,l=this.charCache.get(d);if(l){const u=a.fontSize??e*(this.options.nodeCharSizeRatio??.8);o.drawImage(l,p-u/2,n-u/2,u,u)}}}const h=this.getNodeLabel(t);if(h&&this.shouldShowLabel(t,i)){const a=typeof this.options.nodeLabelColor=="function"?this.options.nodeLabelColor(t):this.options.nodeLabelColor??"#555",l=(this.options.labelFontSize||14)/i;o.font=`${l}px Arial`,o.fillStyle=a,o.textAlign="center",o.textBaseline="middle",o.fillText(h,t.x||0,(t.y||0)+e+l/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}buildCharBitmap(t,o){const e=new OffscreenCanvas(64,64),s=e.getContext("2d");return s.font=`${64*.8}px sans-serif`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=o,s.fillText(t,64/2,64/2),e.transferToImageBitmap()}ensureCharCache(){if(this.options.nodeChar)for(const t of this.data.nodes){const o=this.options.nodeChar(t);if(!o?.char)continue;const i=o.color??"#000",e=`${o.char}|${i}`;this.charCache.has(e)||this.charCache.set(e,this.buildCharBitmap(o.char,i))}}clearCharCache(){this.charCache.forEach(t=>t.close()),this.charCache.clear()}shouldShowLabel(t,o){const i=this.getNodeSize(t)*2,s=(this.options.labelFontSize||14)/o,r=this.options.labelThreshold||1.5;return s<=i*r}updateData(t){const o=new Set(this.data.nodes.map(r=>r.id.toString())),i=t.nodes.filter(r=>!o.has(r.id.toString()));i.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,...i],links:[...this.data.links,...s]},this.ensureCharCache(),this.refreshGraph()}getNodeColor(t){if(typeof this.options.nodeColor=="function"){const o=this.options.nodeColor(t);if(o)return o}return t?.color??""}getNodeBorderColor(t){return typeof this.options.nodeBorderColor=="function"?this.options.nodeBorderColor(t):this.options.nodeBorderColor||"#333"}resolveNodeShape(t){return t.shape?t.shape:typeof this.options.nodeShape=="function"?this.options.nodeShape(t):this.options.nodeShape??"circle"}drawNodeShape(t,o,i,e,s){switch(t.beginPath(),s){case"square":{t.rect(o-e,i-e,e*2,e*2);break}case"triangle":{const r=e*Math.sqrt(3);t.moveTo(o,i-e),t.lineTo(o+r/2,i+e/2),t.lineTo(o-r/2,i+e/2),t.closePath();break}case"diamond":{t.moveTo(o,i-e),t.lineTo(o+e,i),t.lineTo(o,i+e),t.lineTo(o-e,i),t.closePath();break}case"star":{const r=e,p=e*.4,n=5;for(let h=0;h<n*2;h++){const a=h%2===0?r:p,d=Math.PI/n*h-Math.PI/2,l=o+a*Math.cos(d),u=i+a*Math.sin(d);h===0?t.moveTo(l,u):t.lineTo(l,u)}t.closePath();break}case"hexagon":{for(let r=0;r<6;r++){const p=Math.PI/3*r-Math.PI/6,n=o+e*Math.cos(p),h=i+e*Math.sin(p);r===0?t.moveTo(n,h):t.lineTo(n,h)}t.closePath();break}case"circle":default:t.arc(o,i,e,0,2*Math.PI);break}}applyLinkOptions(){this.graph.linkColor(t=>typeof this.options.linkColor=="function"?this.options.linkColor(t):this.options.linkColor?this.options.linkColor:t.color??""),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,o){return typeof t=="function"?t(o):t}calculateGroupHulls(){if(!this.options.showGroups)return;this.groupHulls.clear();const t=this.options.groupPadding||20;this.ensureNodeGroupsCache(),this.nodeGroupsCache.forEach((i,e)=>{if(i.length===0)return;const s=[];let r=1/0,p=0;if(i.forEach(n=>{if(n.x!==void 0&&n.y!==void 0){const a=this.getNodeSize(n)+t;for(let d=0;d<8;d++){const l=d*Math.PI/4;s.push([n.x+a*Math.cos(l),n.y+a*Math.sin(l)])}n.y-a<r&&(r=n.y-a,p=n.x)}}),s.length>=3){const n=C.polygonHull(s);if(n){const h=n.filter(a=>Math.abs(a[1]-r)<t);h.length>0&&(p=h.reduce((a,d)=>a+d[0],0)/h.length),this.groupHulls.set(e,{points:n,labelX:p,labelY:r,nodes:i})}}})}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]}}ensureNodeGroupsCache(){this.nodeGroupsCache||(this.nodeGroupsCache=new Map,this.data.nodes.forEach(t=>{const o=this.getNodeGroupId(t);o&&(this.nodeGroupsCache.has(o)||this.nodeGroupsCache.set(o,[]),this.nodeGroupsCache.get(o).push(t))}))}renderGroups(t,o){if(!this.options.showGroups)return;const i=C.line().curve(C.curveCatmullRomClosed).context(t);this.groupHulls.forEach((e,s)=>{const r=this.getGroupFillColor(s),p=this.options.groupFillOpacity??1,n=this.getGroupBorderColor(s),h=this.options.groupBorderWidth??0,a=this.options.groupBorderOpacity??.3;t.save(),t.beginPath(),i(e.points),r&&(t.globalAlpha=p,t.fillStyle=r,t.fill()),h>0&&n&&(t.globalAlpha=a,t.strokeStyle=n,t.lineWidth=h/o,t.setLineDash([]),t.stroke()),t.restore();const d=this.getGroupLabelColor(s),l=(this.options.groupLabelSize||16)/o;t.save(),t.font=`bold ${l}px Arial`,t.fillStyle=d,t.textAlign="center",t.textBaseline="middle";const u=e.labelY-l/2,y=t.measureText(s).width,b=l;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(e.labelX-y/2-4,u-b/2-2,y+8,b+4),t.globalAlpha=1,t.fillStyle=d,t.fillText(s,e.labelX,u),t.restore()})}getGroupFillColor(t){let o;return typeof this.options.groupFillColor=="function"?o=this.options.groupFillColor(t):o=this.options.groupFillColor,o==="auto"?this.autoColorScale(t):o}getGroupBorderColor(t){let o;return typeof this.options.groupBorderColor=="function"?o=this.options.groupBorderColor(t):o=this.options.groupBorderColor,o==="auto"?this.autoColorScale(t):o||"#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(){if(this.options.cooldownTime!==void 0)return this.options.cooldownTime;const o=this.data.nodes.length*(125/100);return Math.max(4e3,o)}getDataSize(){const{nodes:t,links:o}=this.graph.graphData();return{nodes:t.length,links:o.length}}getAllNodeIds(){return Array.from(this.nodesMap.keys())}updateNode(t,o){const i=this.nodesMap.get(t.toString());if(i){Object.assign(i,o);const e=this.data.nodes.findIndex(s=>s.id.toString()===t.toString());return e!==-1&&(this.data.nodes[e]=i),this.refreshGraph(),!0}return!1}removeNode(t){const o=t.toString();return this.nodesMap.has(o)?(this.nodesMap.delete(o),this.data.nodes=this.data.nodes.filter(i=>i.id.toString()!==o),this.data.links=this.data.links.filter(i=>{const e=typeof i.source=="object"?i.source.id:i.source,s=typeof i.target=="object"?i.target.id:i.target;return e?.toString()!==o&&s?.toString()!==o}),this.refreshGraph(),!0):!1}async addData(t){t.nodes.forEach(o=>{this.nodesMap.has(o.id.toString())||this.nodesMap.set(o.id.toString(),o)}),t.links.forEach(o=>{const i=this.createLinkKey(o.source,o.target);this.linkMap.has(i)?console.log("link already exists",o):this.linkMap.set(i,o)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.ensureCharCache(),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},t.nodeChar!==void 0&&(this.clearCharCache(),this.ensureCharCache()),this.applyOptions()}refreshGraph(){this.graphData(this.data)}reinitialize(){this.initGraph()}reset(){this.graph.pauseAnimation(),this.data={nodes:[],links:[]},this.nodesMap.clear(),this.graph=new f(this.container),this.initGraph()}getData(){return this.data}getNodesData(){return this.data.nodes}getLinksData(){return this.data.links}createLinkKey(t,o){const i=t===void 0?"undefined":typeof t=="object"?t.id:t,e=o===void 0?"undefined":typeof o=="object"?o.id:o;return`${i}-${e}`}graphData(t){return this.nodesMap.clear(),this.linkMap.clear(),this.nodeGroupsCache=null,this.clearCharCache(),t.nodes.forEach(o=>{this.nodesMap.has(o.id.toString())||this.nodesMap.set(o.id.toString(),o)}),t.links.forEach(o=>{const i=this.createLinkKey(o.source,o.target);this.linkMap.has(i)||this.linkMap.set(i,o)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.ensureCharCache(),this.graph.cooldownTime(this.getCooldownTime()),this.graph.graphData(this.data),this}showGroups(t){return this.options.showGroups=t,t&&Object.entries({groupBy:"topic",groupFillColor:"rgba(0, 0, 0, 0.05)",groupFillOpacity:1,groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupPadding:20}).forEach(([i,e])=>{this.options[i]===void 0&&(this.options[i]=e)}),this.applyOptions(),this.refreshGraph(),this}setGroupBy(t){return this.options.groupBy=t,this.applyOptions(),this.refreshGraph(),this}setGroupOptions(t){return t.fillColor!==void 0&&(this.options.groupFillColor=t.fillColor),t.fillOpacity!==void 0&&(this.options.groupFillOpacity=t.fillOpacity),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.padding!==void 0&&(this.options.groupPadding=t.padding),this.applyOptions(),this.refreshGraph(),this}getGroups(){const t=new Set;return this.data.nodes.forEach(o=>{const i=this.getNodeGroupId(o);i&&t.add(i)}),Array.from(t)}getNodesInGroup(t){return this.data.nodes.filter(o=>this.getNodeGroupId(o)===t)}getOptions(){return{...this.options}}destroy(){this.worker&&(this.worker.terminate(),this.worker=null),this.clearCharCache(),this.graph._destructor()}}c.ForceGraph=m,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export declare class ForceGraph<TNode extends NodeData & {
|
|
|
43
43
|
private nodeGroupsCache;
|
|
44
44
|
private isFirstRender;
|
|
45
45
|
private autoColorScale;
|
|
46
|
+
private charCache;
|
|
46
47
|
constructor(container: HTMLElement, initialData?: GraphData<TNode, TLink>, options?: GraphOptions<TNode, TLink>);
|
|
47
48
|
private initGraph;
|
|
48
49
|
renderer(): default_2<TNode, TLink>;
|
|
@@ -56,6 +57,12 @@ export declare class ForceGraph<TNode extends NodeData & {
|
|
|
56
57
|
private applyOptions;
|
|
57
58
|
private getNodeSize;
|
|
58
59
|
private getNodeLabel;
|
|
60
|
+
/** Render a single char/emoji to a 64×64 OffscreenCanvas and return an ImageBitmap */
|
|
61
|
+
private buildCharBitmap;
|
|
62
|
+
/** Idempotent — only adds cache entries for chars not yet cached */
|
|
63
|
+
private ensureCharCache;
|
|
64
|
+
/** Release GPU memory and clear the cache */
|
|
65
|
+
private clearCharCache;
|
|
59
66
|
/**
|
|
60
67
|
* Check if label should be shown based on threshold logic
|
|
61
68
|
* The threshold determines when labels become too big relative to nodes
|
|
@@ -194,6 +201,8 @@ export declare interface GraphOptions<N extends NodeData = NodeData, L extends L
|
|
|
194
201
|
nodeGap?: number;
|
|
195
202
|
linkLabel?: string | ((link: L) => string);
|
|
196
203
|
nodeIcon?: string | ((node: N) => string);
|
|
204
|
+
nodeChar?: (node: N) => NodeChar | null | undefined;
|
|
205
|
+
nodeCharSizeRatio?: number;
|
|
197
206
|
loading?: boolean;
|
|
198
207
|
pointerInteraction?: boolean;
|
|
199
208
|
keepDragPosition?: boolean;
|
|
@@ -228,6 +237,15 @@ export declare interface LinkData<N extends NodeData = NodeData> extends LinkObj
|
|
|
228
237
|
[key: string]: any;
|
|
229
238
|
}
|
|
230
239
|
|
|
240
|
+
export declare interface NodeChar {
|
|
241
|
+
/** Character, letter, number, or emoji to display inside the node */
|
|
242
|
+
char: string;
|
|
243
|
+
/** Absolute size in world units. If omitted, uses node size * nodeCharSizeRatio */
|
|
244
|
+
fontSize?: number;
|
|
245
|
+
/** Fill color. Defaults to '#000' */
|
|
246
|
+
color?: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
231
249
|
export declare interface NodeData extends NodeObject {
|
|
232
250
|
id: string | number;
|
|
233
251
|
x?: number;
|