@logixode/force-graph-lib 0.5.0 → 0.6.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 +80 -56
- package/dist/force-graph-lib.umd.cjs +1 -1
- package/dist/index.d.ts +13 -6
- package/package.json +1 -1
package/dist/force-graph-lib.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import "d3";
|
|
3
|
-
class
|
|
1
|
+
import y from "force-graph";
|
|
2
|
+
import * as g from "d3";
|
|
3
|
+
class S {
|
|
4
4
|
container;
|
|
5
5
|
graph;
|
|
6
6
|
data = { nodes: [], links: [] };
|
|
@@ -8,10 +8,11 @@ class C {
|
|
|
8
8
|
linkMap = /* @__PURE__ */ new Map();
|
|
9
9
|
options;
|
|
10
10
|
worker = null;
|
|
11
|
-
|
|
11
|
+
groupHulls = /* @__PURE__ */ new Map();
|
|
12
12
|
isFirstRender = !0;
|
|
13
|
+
autoColorScale = g.scaleOrdinal(g.schemeCategory10);
|
|
13
14
|
constructor(t, o = { nodes: [], links: [] }, i = {}) {
|
|
14
|
-
this.container = t, this.data = o, this.graph = new
|
|
15
|
+
this.container = t, this.data = o, this.graph = new y(this.container), o.nodes.forEach((r) => {
|
|
15
16
|
this.nodesMap.set(r.id.toString(), r);
|
|
16
17
|
});
|
|
17
18
|
const e = {
|
|
@@ -21,17 +22,18 @@ class C {
|
|
|
21
22
|
...i
|
|
22
23
|
}, s = e.showGroups ? {
|
|
23
24
|
groupBy: "topic",
|
|
25
|
+
groupFillColor: "rgba(0, 0, 0, 0.05)",
|
|
26
|
+
groupFillOpacity: 1,
|
|
24
27
|
groupBorderColor: "#666",
|
|
25
28
|
groupBorderWidth: 2,
|
|
26
29
|
groupBorderOpacity: 0.3,
|
|
27
30
|
groupLabelColor: "#333",
|
|
28
31
|
groupLabelSize: 16,
|
|
29
|
-
groupLabelThreshold: 0.8,
|
|
30
32
|
groupPadding: 20
|
|
31
33
|
} : {};
|
|
32
34
|
this.options = {
|
|
33
|
-
...
|
|
34
|
-
...
|
|
35
|
+
...s,
|
|
36
|
+
...e
|
|
35
37
|
}, this.initGraph();
|
|
36
38
|
}
|
|
37
39
|
initGraph() {
|
|
@@ -66,12 +68,12 @@ class C {
|
|
|
66
68
|
t.fx = t.x, t.fy = t.y;
|
|
67
69
|
}), this.options.pointerInteraction || this.graph.enablePointerInteraction(!1), this.graph.nodeCanvasObject((t, o, i) => {
|
|
68
70
|
t === this.data.nodes[0] && this.renderGroups(o, i);
|
|
69
|
-
const e = this.getNodeSize(t) * 2, s = typeof this.options.nodeBorderWidth == "function" ? this.options.nodeBorderWidth(t) : this.options.nodeBorderWidth, r = this.resolveNodeShape(t),
|
|
70
|
-
this.drawNodeShape(o,
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
const
|
|
74
|
-
o.font = `${
|
|
71
|
+
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;
|
|
72
|
+
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());
|
|
73
|
+
const a = this.getNodeLabel(t);
|
|
74
|
+
if (a && this.shouldShowLabel(t, i)) {
|
|
75
|
+
const h = typeof this.options.nodeLabelColor == "function" ? this.options.nodeLabelColor(t) : this.options.nodeLabelColor ?? "#555", d = (this.options.labelFontSize || 14) / i;
|
|
76
|
+
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
77
|
}
|
|
76
78
|
}), this.applyLinkOptions();
|
|
77
79
|
}
|
|
@@ -133,18 +135,18 @@ class C {
|
|
|
133
135
|
break;
|
|
134
136
|
}
|
|
135
137
|
case "star": {
|
|
136
|
-
const r = e,
|
|
137
|
-
for (let
|
|
138
|
-
const
|
|
139
|
-
|
|
138
|
+
const r = e, l = e * 0.4, n = 5;
|
|
139
|
+
for (let a = 0; a < n * 2; a++) {
|
|
140
|
+
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);
|
|
141
|
+
a === 0 ? t.moveTo(d, u) : t.lineTo(d, u);
|
|
140
142
|
}
|
|
141
143
|
t.closePath();
|
|
142
144
|
break;
|
|
143
145
|
}
|
|
144
146
|
case "hexagon": {
|
|
145
147
|
for (let r = 0; r < 6; r++) {
|
|
146
|
-
const
|
|
147
|
-
r === 0 ? t.moveTo(
|
|
148
|
+
const l = Math.PI / 3 * r - Math.PI / 6, n = o + e * Math.cos(l), a = i + e * Math.sin(l);
|
|
149
|
+
r === 0 ? t.moveTo(n, a) : t.lineTo(n, a);
|
|
148
150
|
}
|
|
149
151
|
t.closePath();
|
|
150
152
|
break;
|
|
@@ -175,30 +177,43 @@ class C {
|
|
|
175
177
|
return typeof t == "function" ? t(o) : t;
|
|
176
178
|
}
|
|
177
179
|
/**
|
|
178
|
-
* Calculate group
|
|
180
|
+
* Calculate group convex hulls based on node positions
|
|
179
181
|
*/
|
|
180
|
-
|
|
182
|
+
calculateGroupHulls() {
|
|
181
183
|
if (!this.options.showGroups) return;
|
|
182
|
-
this.
|
|
184
|
+
this.groupHulls.clear();
|
|
183
185
|
const t = this.options.groupPadding || 20, o = /* @__PURE__ */ new Map();
|
|
184
186
|
this.data.nodes.forEach((i) => {
|
|
185
187
|
const e = this.getNodeGroupId(i);
|
|
186
188
|
e && (o.has(e) || o.set(e, []), o.get(e).push(i));
|
|
187
189
|
}), o.forEach((i, e) => {
|
|
188
190
|
if (i.length === 0) return;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
const s = [];
|
|
192
|
+
let r = 1 / 0, l = 0;
|
|
193
|
+
if (i.forEach((n) => {
|
|
191
194
|
if (n.x !== void 0 && n.y !== void 0) {
|
|
192
|
-
const
|
|
193
|
-
|
|
195
|
+
const h = this.getNodeSize(n) + t;
|
|
196
|
+
for (let p = 0; p < 8; p++) {
|
|
197
|
+
const d = p * Math.PI / 4;
|
|
198
|
+
s.push([
|
|
199
|
+
n.x + h * Math.cos(d),
|
|
200
|
+
n.y + h * Math.sin(d)
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
n.y - h < r && (r = n.y - h, l = n.x);
|
|
194
204
|
}
|
|
195
|
-
}), s
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
}), s.length >= 3) {
|
|
206
|
+
const n = g.polygonHull(s);
|
|
207
|
+
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, {
|
|
210
|
+
points: n,
|
|
211
|
+
labelX: l,
|
|
212
|
+
labelY: r,
|
|
213
|
+
nodes: i
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
202
217
|
});
|
|
203
218
|
}
|
|
204
219
|
/**
|
|
@@ -213,31 +228,39 @@ class C {
|
|
|
213
228
|
}
|
|
214
229
|
}
|
|
215
230
|
/**
|
|
216
|
-
* Render group
|
|
231
|
+
* Render group hulls and labels
|
|
217
232
|
*/
|
|
218
233
|
renderGroups(t, o) {
|
|
219
|
-
this.options.showGroups
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
})
|
|
234
|
+
if (!this.options.showGroups) return;
|
|
235
|
+
this.calculateGroupHulls();
|
|
236
|
+
const i = g.line().curve(g.curveCatmullRomClosed).context(t);
|
|
237
|
+
this.groupHulls.forEach((e, s) => {
|
|
238
|
+
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;
|
|
239
|
+
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();
|
|
240
|
+
const p = this.getGroupLabelColor(s), d = (this.options.groupLabelSize || 16) / o;
|
|
241
|
+
t.save(), t.font = `bold ${d}px Arial`, t.fillStyle = p, t.textAlign = "center", t.textBaseline = "middle";
|
|
242
|
+
const u = e.labelY - d / 2, f = t.measureText(s).width, c = d;
|
|
243
|
+
t.globalAlpha = 0.8, t.fillStyle = "rgba(255, 255, 255, 0.9)", t.fillRect(
|
|
244
|
+
e.labelX - f / 2 - 4,
|
|
245
|
+
u - c / 2 - 2,
|
|
246
|
+
f + 8,
|
|
247
|
+
c + 4
|
|
248
|
+
), t.globalAlpha = 1, t.fillStyle = p, t.fillText(s, e.labelX, u), t.restore();
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get group fill color
|
|
253
|
+
*/
|
|
254
|
+
getGroupFillColor(t) {
|
|
255
|
+
let o;
|
|
256
|
+
return typeof this.options.groupFillColor == "function" ? o = this.options.groupFillColor(t) : o = this.options.groupFillColor, o === "auto" ? this.autoColorScale(t) : o;
|
|
235
257
|
}
|
|
236
258
|
/**
|
|
237
259
|
* Get group border color
|
|
238
260
|
*/
|
|
239
261
|
getGroupBorderColor(t) {
|
|
240
|
-
|
|
262
|
+
let o;
|
|
263
|
+
return typeof this.options.groupBorderColor == "function" ? o = this.options.groupBorderColor(t) : o = this.options.groupBorderColor, o === "auto" ? this.autoColorScale(t) : o || "#666";
|
|
241
264
|
}
|
|
242
265
|
/**
|
|
243
266
|
* Get group label color
|
|
@@ -322,7 +345,7 @@ class C {
|
|
|
322
345
|
this.initGraph();
|
|
323
346
|
}
|
|
324
347
|
reset() {
|
|
325
|
-
this.graph.pauseAnimation(), this.data = { nodes: [], links: [] }, this.nodesMap.clear(), this.graph = new
|
|
348
|
+
this.graph.pauseAnimation(), this.data = { nodes: [], links: [] }, this.nodesMap.clear(), this.graph = new y(this.container), this.initGraph();
|
|
326
349
|
}
|
|
327
350
|
getData() {
|
|
328
351
|
return this.data;
|
|
@@ -358,12 +381,13 @@ class C {
|
|
|
358
381
|
showGroups(t) {
|
|
359
382
|
return this.options.showGroups = t, t && Object.entries({
|
|
360
383
|
groupBy: "topic",
|
|
384
|
+
groupFillColor: "rgba(0, 0, 0, 0.05)",
|
|
385
|
+
groupFillOpacity: 1,
|
|
361
386
|
groupBorderColor: "#666",
|
|
362
387
|
groupBorderWidth: 2,
|
|
363
388
|
groupBorderOpacity: 0.3,
|
|
364
389
|
groupLabelColor: "#333",
|
|
365
390
|
groupLabelSize: 16,
|
|
366
|
-
groupLabelThreshold: 0.8,
|
|
367
391
|
groupPadding: 20
|
|
368
392
|
}).forEach(([i, e]) => {
|
|
369
393
|
this.options[i] === void 0 && (this.options[i] = e);
|
|
@@ -379,7 +403,7 @@ class C {
|
|
|
379
403
|
* Set group visualization options
|
|
380
404
|
*/
|
|
381
405
|
setGroupOptions(t) {
|
|
382
|
-
return t.
|
|
406
|
+
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;
|
|
383
407
|
}
|
|
384
408
|
/**
|
|
385
409
|
* Get all available groups
|
|
@@ -408,5 +432,5 @@ class C {
|
|
|
408
432
|
}
|
|
409
433
|
}
|
|
410
434
|
export {
|
|
411
|
-
|
|
435
|
+
S as ForceGraph
|
|
412
436
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(d,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("force-graph"),require("d3")):typeof define=="function"&&define.amd?define(["exports","force-graph","d3"],u):(d=typeof globalThis<"u"?globalThis:d||self,u(d.ForceGraphLib={},d.ForceGraph))})(this,(function(d,u){"use strict";class k{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupBounds=new Map;isFirstRender=!0;constructor(t,o={nodes:[],links:[]},i={}){this.container=t,this.data=o,this.graph=new u(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",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(()=>{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()})}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),a=t.x||0,h=t.y||0;this.drawNodeShape(o,a,h,e,r),o.fillStyle=this.getNodeColor(t),o.fill(),s&&s>0&&(this.drawNodeShape(o,a,h,e,r),o.strokeStyle=this.getNodeBorderColor(t),o.lineWidth=s,o.stroke());const n=this.getNodeLabel(t);if(n&&this.shouldShowLabel(t,i)){const p=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=p,o.textAlign="center",o.textBaseline="middle",o.fillText(n,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}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,a=e*.4,h=5;for(let n=0;n<h*2;n++){const p=n%2===0?r:a,f=Math.PI/h*n-Math.PI/2,l=o+p*Math.cos(f),g=i+p*Math.sin(f);n===0?t.moveTo(l,g):t.lineTo(l,g)}t.closePath();break}case"hexagon":{for(let r=0;r<6;r++){const a=Math.PI/3*r-Math.PI/6,h=o+e*Math.cos(a),n=i+e*Math.sin(a);r===0?t.moveTo(h,n):t.lineTo(h,n)}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}calculateGroupBounds(){if(!this.options.showGroups)return;this.groupBounds.clear();const t=this.options.groupPadding||20,o=new Map;this.data.nodes.forEach(i=>{const e=this.getNodeGroupId(i);e&&(o.has(e)||o.set(e,[]),o.get(e).push(i))}),o.forEach((i,e)=>{if(i.length===0)return;let s=1/0,r=1/0,a=-1/0,h=-1/0;i.forEach(n=>{if(n.x!==void 0&&n.y!==void 0){const p=this.getNodeSize(n);s=Math.min(s,n.x-p),r=Math.min(r,n.y-p),a=Math.max(a,n.x+p),h=Math.max(h,n.y+p)}}),s!==1/0&&this.groupBounds.set(e,{minX:s-t,minY:r-t,maxX:a+t,maxY:h+t,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]}}renderGroups(t,o){this.options.showGroups&&(this.calculateGroupBounds(),this.groupBounds.forEach((i,e)=>{const s=this.getGroupBorderColor(e),r=this.options.groupBorderWidth||2,a=this.options.groupBorderOpacity||.3;t.save(),t.globalAlpha=a,t.strokeStyle=s,t.lineWidth=r/o,t.setLineDash([10/o,5/o]),t.strokeRect(i.minX,i.minY,i.maxX-i.minX,i.maxY-i.minY),t.restore();const h=this.options.groupLabelThreshold||.8;if(o<=h){const n=this.getGroupLabelColor(e),p=(this.options.groupLabelSize||16)/o;t.save(),t.font=`bold ${p}px Arial`,t.fillStyle=n,t.textAlign="center",t.textBaseline="middle";const f=(i.minX+i.maxX)/2,l=i.minY-p/2,c=t.measureText(e).width,y=p;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(f-c/2-4,l-y/2-2,c+8,y+4),t.globalAlpha=1,t.fillStyle=n,t.fillText(e,f,l),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(){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 u(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(),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",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,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.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(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()}}d.ForceGraph=k,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
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,C){"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(C);class v{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupHulls=new Map;isFirstRender=!0;autoColorScale=c.scaleOrdinal(c.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()})}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,o=new Map;this.data.nodes.forEach(i=>{const e=this.getNodeGroupId(i);e&&(o.has(e)||o.set(e,[]),o.get(e).push(i))}),o.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=c.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]}}renderGroups(t,o){if(!this.options.showGroups)return;this.calculateGroupHulls();const i=c.line().curve(c.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,b=t.measureText(s).width,k=d;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(e.labelX-b/2-4,y-k/2-2,b+8,k+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(),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"})}));
|
package/dist/index.d.ts
CHANGED
|
@@ -39,8 +39,9 @@ export declare class ForceGraph<TNode extends NodeData & {
|
|
|
39
39
|
private linkMap;
|
|
40
40
|
private options;
|
|
41
41
|
private worker;
|
|
42
|
-
private
|
|
42
|
+
private groupHulls;
|
|
43
43
|
private isFirstRender;
|
|
44
|
+
private autoColorScale;
|
|
44
45
|
constructor(container: HTMLElement, initialData?: GraphData<TNode, TLink>, options?: GraphOptions<TNode, TLink>);
|
|
45
46
|
private initGraph;
|
|
46
47
|
renderer(): default_2<TNode, TLink>;
|
|
@@ -68,17 +69,21 @@ export declare class ForceGraph<TNode extends NodeData & {
|
|
|
68
69
|
private getLinkCurvature;
|
|
69
70
|
private getLinkProperty;
|
|
70
71
|
/**
|
|
71
|
-
* Calculate group
|
|
72
|
+
* Calculate group convex hulls based on node positions
|
|
72
73
|
*/
|
|
73
|
-
private
|
|
74
|
+
private calculateGroupHulls;
|
|
74
75
|
/**
|
|
75
76
|
* Get the group ID for a node
|
|
76
77
|
*/
|
|
77
78
|
private getNodeGroupId;
|
|
78
79
|
/**
|
|
79
|
-
* Render group
|
|
80
|
+
* Render group hulls and labels
|
|
80
81
|
*/
|
|
81
82
|
private renderGroups;
|
|
83
|
+
/**
|
|
84
|
+
* Get group fill color
|
|
85
|
+
*/
|
|
86
|
+
private getGroupFillColor;
|
|
82
87
|
/**
|
|
83
88
|
* Get group border color
|
|
84
89
|
*/
|
|
@@ -139,12 +144,13 @@ export declare class ForceGraph<TNode extends NodeData & {
|
|
|
139
144
|
* Set group visualization options
|
|
140
145
|
*/
|
|
141
146
|
setGroupOptions(options: {
|
|
147
|
+
fillColor?: string | ((groupId: string) => string);
|
|
148
|
+
fillOpacity?: number;
|
|
142
149
|
borderColor?: string | ((groupId: string) => string);
|
|
143
150
|
borderWidth?: number;
|
|
144
151
|
borderOpacity?: number;
|
|
145
152
|
labelColor?: string | ((groupId: string) => string);
|
|
146
153
|
labelSize?: number;
|
|
147
|
-
labelThreshold?: number;
|
|
148
154
|
padding?: number;
|
|
149
155
|
}): ForceGraph<TNode, TLink>;
|
|
150
156
|
/**
|
|
@@ -196,12 +202,13 @@ export declare interface GraphOptions<N extends NodeData = NodeData, L extends L
|
|
|
196
202
|
onGraphUpdated?: () => void;
|
|
197
203
|
showGroups?: boolean;
|
|
198
204
|
groupBy?: string | ((node: N) => string | undefined);
|
|
205
|
+
groupFillColor?: string | ((groupId: string) => string);
|
|
206
|
+
groupFillOpacity?: number;
|
|
199
207
|
groupBorderColor?: string | ((groupId: string) => string);
|
|
200
208
|
groupBorderWidth?: number;
|
|
201
209
|
groupBorderOpacity?: number;
|
|
202
210
|
groupLabelColor?: string | ((groupId: string) => string);
|
|
203
211
|
groupLabelSize?: number;
|
|
204
|
-
groupLabelThreshold?: number;
|
|
205
212
|
groupPadding?: number;
|
|
206
213
|
cooldownTime?: number;
|
|
207
214
|
cooldownTicks?: number;
|