@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.
@@ -1,5 +1,5 @@
1
- import y from "force-graph";
2
- import * as g from "d3";
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 = g.scaleOrdinal(g.schemeCategory10);
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 y(this.container), o.nodes.forEach((r) => {
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), l = t.x || 0, n = t.y || 0;
75
- 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());
76
- const a = this.getNodeLabel(t);
77
- if (a && this.shouldShowLabel(t, i)) {
78
- const h = typeof this.options.nodeLabelColor == "function" ? this.options.nodeLabelColor(t) : this.options.nodeLabelColor ?? "#555", d = (this.options.labelFontSize || 14) / i;
79
- 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);
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, l = e * 0.4, n = 5;
142
- for (let a = 0; a < n * 2; a++) {
143
- const h = a % 2 === 0 ? r : l, p = Math.PI / n * a - Math.PI / 2, d = o + h * Math.cos(p), u = i + h * Math.sin(p);
144
- a === 0 ? t.moveTo(d, u) : t.lineTo(d, u);
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 l = Math.PI / 3 * r - Math.PI / 6, n = o + e * Math.cos(l), a = i + e * Math.sin(l);
152
- r === 0 ? t.moveTo(n, a) : t.lineTo(n, a);
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, l = 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 h = this.getNodeSize(n) + t;
196
- for (let p = 0; p < 8; p++) {
197
- const d = p * Math.PI / 4;
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 + h * Math.cos(d),
200
- n.y + h * Math.sin(d)
228
+ n.x + a * Math.cos(l),
229
+ n.y + a * Math.sin(l)
201
230
  ]);
202
231
  }
203
- n.y - h < r && (r = n.y - h, l = n.x);
232
+ n.y - a < r && (r = n.y - a, p = n.x);
204
233
  }
205
234
  }), s.length >= 3) {
206
- const n = g.polygonHull(s);
235
+ const n = c.polygonHull(s);
207
236
  if (n) {
208
- const a = n.filter((h) => Math.abs(h[1] - r) < t);
209
- a.length > 0 && (l = a.reduce((h, p) => h + p[0], 0) / a.length), this.groupHulls.set(e, {
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: l,
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 = g.line().curve(g.curveCatmullRomClosed).context(t);
273
+ const i = c.line().curve(c.curveCatmullRomClosed).context(t);
245
274
  this.groupHulls.forEach((e, s) => {
246
- const r = this.getGroupFillColor(s), l = this.options.groupFillOpacity ?? 1, n = this.getGroupBorderColor(s), a = this.options.groupBorderWidth ?? 0, h = this.options.groupBorderOpacity ?? 0.3;
247
- 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();
248
- const p = this.getGroupLabelColor(s), d = (this.options.groupLabelSize || 16) / o;
249
- t.save(), t.font = `bold ${d}px Arial`, t.fillStyle = p, t.textAlign = "center", t.textBaseline = "middle";
250
- const u = e.labelY - d / 2, f = t.measureText(s).width, c = d;
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 - c / 2 - 2,
282
+ u - g / 2 - 2,
254
283
  f + 8,
255
- c + 4
256
- ), t.globalAlpha = 1, t.fillStyle = p, t.fillText(s, e.labelX, u), t.restore();
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 y(this.container), this.initGraph();
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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@logixode/force-graph-lib",
3
3
  "private": false,
4
- "version": "0.6.1",
4
+ "version": "0.7.0",
5
5
  "description": "Force-directed graph visualization library.",
6
6
  "author": "Rohmad Kurniadi",
7
7
  "license": "MIT",