@logixode/force-graph-lib 0.1.6 → 0.1.9

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.
@@ -71,10 +71,10 @@ class C {
71
71
  }), this.applyLinkOptions();
72
72
  }
73
73
  getNodeSize(t) {
74
- return typeof this.options.nodeSize == "function" ? this.options.nodeSize(t) || t.marker?.radius : this.options.nodeSize || t.marker?.radius || 1;
74
+ return typeof this.options.nodeSize == "function" ? this.options.nodeSize(t) || t?.marker?.radius : this.options.nodeSize || t?.marker?.radius || 1;
75
75
  }
76
76
  getNodeLabel(t) {
77
- return typeof this.options.nodeLabel == "function" ? this.options.nodeLabel(t) : t.label || t.id;
77
+ return typeof this.options.nodeLabel == "function" ? this.options.nodeLabel(t) : t?.label || t.id;
78
78
  }
79
79
  /**
80
80
  * Check if label should be shown based on threshold logic
@@ -104,13 +104,15 @@ class C {
104
104
  const i = this.options.nodeColor(t);
105
105
  if (i) return i;
106
106
  }
107
- return t.color ?? "";
107
+ return t?.color ?? "";
108
108
  }
109
109
  getNodeBorderColor(t) {
110
110
  return typeof this.options.nodeBorderColor == "function" ? this.options.nodeBorderColor(t) : this.options.nodeBorderColor || "#333";
111
111
  }
112
112
  applyLinkOptions() {
113
- 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(
113
+ this.options.linkWidth !== void 0 && this.graph.linkWidth(
114
+ (t) => this.getLinkProperty(this.options.linkWidth, t) ?? 1
115
+ ), this.options.linkCurvature !== void 0 && this.graph.linkCurvature(this.getLinkCurvature.bind(this)), this.options.linkDirectionalParticles && this.graph.linkDirectionalParticles(
114
116
  (t) => this.getLinkProperty(this.options.linkDirectionalParticles, t) ?? 0
115
117
  ), this.options.linkDirectionalParticleSpeed !== void 0 && this.graph.linkDirectionalParticleSpeed(
116
118
  (t) => this.getLinkProperty(this.options.linkDirectionalParticleSpeed, t) ?? 0
@@ -239,7 +241,7 @@ class C {
239
241
  const i = t.toString();
240
242
  return this.nodesMap.has(i) ? (this.nodesMap.delete(i), this.data.nodes = this.data.nodes.filter((o) => o.id.toString() !== i), this.data.links = this.data.links.filter((o) => {
241
243
  const e = typeof o.source == "object" ? o.source.id : o.source, s = typeof o.target == "object" ? o.target.id : o.target;
242
- return e.toString() !== i && s.toString() !== i;
244
+ return e?.toString() !== i && s?.toString() !== i;
243
245
  }), this.graph.cooldownTime(this.getCooldownTime()), this.refreshGraph(), !0) : !1;
244
246
  }
245
247
  async addData(t) {
@@ -286,7 +288,7 @@ class C {
286
288
  return this.data.links;
287
289
  }
288
290
  createLinkKey(t, i) {
289
- const o = typeof t == "object" ? t.id : t, e = typeof i == "object" ? i.id : i;
291
+ const o = t === void 0 ? "undefined" : typeof t == "object" ? t.id : t, e = i === void 0 ? "undefined" : typeof i == "object" ? i.id : i;
290
292
  return `${o}-${e}`;
291
293
  }
292
294
  /**
@@ -1 +1 @@
1
- (function(a,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("force-graph"),require("d3")):typeof define=="function"&&define.amd?define(["exports","force-graph","d3"],l):(a=typeof globalThis<"u"?globalThis:a||self,l(a.ForceGraphLib={},a.ForceGraph,a.d3))})(this,(function(a,l,b){"use strict";function k(d){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(d){for(const i in d)if(i!=="default"){const o=Object.getOwnPropertyDescriptor(d,i);Object.defineProperty(t,i,o.get?o:{enumerable:!0,get:()=>d[i]})}}return t.default=d,Object.freeze(t)}const m=k(b);class C{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupBounds=new Map;constructor(t,i={nodes:[],links:[]},o={}){this.container=t,this.data=i,this.graph=new l(this.container),i.nodes.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e={labelThreshold:1.5,showGroups:!1,...o},s=e.showGroups?{groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}:{};this.options={...e,...s},this.initGraph()}initGraph(){this.applyOptions(),this.render(),this.graphData(this.data),this.graph.onEngineStop(()=>this.graph.zoomToFit(400))}renderer(){return this.graph}focusPosition(t={}){if(Object.values(t).length){if(t.id){const{x:i,y:o}=this.nodesMap.get(t.id)??{x:0,y:0};i&&o&&(t={x:i,y:o})}t&&(this.graph.centerAt(t.x,t.y,1e3),this.graph.zoom(8,2e3))}}render(){this.graph.width(this.options.width??800).height(this.options.height??400).cooldownTime(this.getCooldownTime()).d3Force("charge",m.forceManyBody().strength(this.options.nodeGap??-50)),typeof this.options.nodeClickHandler=="function"&&this.graph.onNodeClick(t=>{this.options.nodeClickHandler(t)})}force(t,i){return this.graph.d3Force(t,i)}applyOptions(){this.options.keepDragPosition&&this.graph.onNodeDragEnd(t=>{t.fx=t.x,t.fy=t.y}),this.options.pointerInteraction||this.graph.enablePointerInteraction(!1),this.graph.nodeCanvasObject((t,i,o)=>{t===this.data.nodes[0]&&this.renderGroups(i,o);const e=this.getNodeSize(t)*2,s=typeof this.options.nodeBorderWidth=="function"?this.options.nodeBorderWidth(t):this.options.nodeBorderWidth;i.beginPath(),i.arc(t.x||0,t.y||0,e,0,2*Math.PI),i.fillStyle=this.getNodeColor(t),i.fill(),s&&s>0&&(i.beginPath(),i.arc(t.x||0,t.y||0,e,0,2*Math.PI),i.strokeStyle=this.getNodeBorderColor(t),i.lineWidth=s,i.stroke());const r=this.getNodeLabel(t);if(r&&this.shouldShowLabel(t,o)){const p=typeof this.options.nodeLabelColor=="function"?this.options.nodeLabelColor(t):this.options.nodeLabelColor??"#555",n=(this.options.labelFontSize||14)/o;i.font=`${n}px Arial`,i.fillStyle=p,i.textAlign="center",i.textBaseline="middle",i.fillText(r,t.x||0,(t.y||0)+e+n/2+2)}}),this.applyLinkOptions()}getNodeSize(t){return typeof this.options.nodeSize=="function"?this.options.nodeSize(t)||t.marker?.radius:this.options.nodeSize||t.marker?.radius||1}getNodeLabel(t){return typeof this.options.nodeLabel=="function"?this.options.nodeLabel(t):t.label||t.id}shouldShowLabel(t,i){const o=this.getNodeSize(t)*2,s=(this.options.labelFontSize||14)/i,r=this.options.labelThreshold||1.5;return s<=o*r}updateData(t){const i=new Set(this.data.nodes.map(r=>r.id.toString())),o=t.nodes.filter(r=>!i.has(r.id.toString()));o.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e=new Set(this.data.links.map(r=>this.createLinkKey(r.source,r.target))),s=t.links.filter(r=>!e.has(this.createLinkKey(r.source,r.target)));this.data={nodes:[...this.data.nodes,...o],links:[...this.data.links,...s]},this.refreshGraph()}getNodeColor(t){if(typeof this.options.nodeColor=="function"){const i=this.options.nodeColor(t);if(i)return i}return t.color??""}getNodeBorderColor(t){return typeof this.options.nodeBorderColor=="function"?this.options.nodeBorderColor(t):this.options.nodeBorderColor||"#333"}applyLinkOptions(){this.options.linkWidth!==void 0&&this.graph.linkWidth(t=>this.getLinkProperty(this.options.linkWidth,t)??1),this.options.linkCurvature!==void 0&&this.graph.linkCurvature(this.getLinkCurvature.bind(this)),this.options.linkDirectionalParticles&&this.graph.linkDirectionalParticles(t=>this.getLinkProperty(this.options.linkDirectionalParticles,t)??0),this.options.linkDirectionalParticleSpeed!==void 0&&this.graph.linkDirectionalParticleSpeed(t=>this.getLinkProperty(this.options.linkDirectionalParticleSpeed,t)??0),this.options.linkDirectionalParticleWidth!==void 0&&this.graph.linkDirectionalParticleWidth(t=>this.getLinkProperty(this.options.linkDirectionalParticleWidth,t)??0),this.options.linkDirectionalParticleColor!==void 0&&this.graph.linkDirectionalParticleColor(t=>this.getLinkProperty(this.options.linkDirectionalParticleColor,t)??"#aaa")}getLinkCurvature(t){return typeof this.options.linkCurvature=="function"?this.options.linkCurvature(t):typeof this.options.linkCurvature=="string"?t[this.options.linkCurvature]||0:typeof this.options.linkCurvature=="number"?this.options.linkCurvature:t.curvature||0}getLinkProperty(t,i){return typeof t=="function"?t(i):t}calculateGroupBounds(){if(!this.options.showGroups)return;this.groupBounds.clear();const t=this.options.groupPadding||20,i=new Map;this.data.nodes.forEach(o=>{const e=this.getNodeGroupId(o);e&&(i.has(e)||i.set(e,[]),i.get(e).push(o))}),i.forEach((o,e)=>{if(o.length===0)return;let s=1/0,r=1/0,p=-1/0,u=-1/0;o.forEach(n=>{if(n.x!==void 0&&n.y!==void 0){const h=this.getNodeSize(n);s=Math.min(s,n.x-h),r=Math.min(r,n.y-h),p=Math.max(p,n.x+h),u=Math.max(u,n.y+h)}}),s!==1/0&&this.groupBounds.set(e,{minX:s-t,minY:r-t,maxX:p+t,maxY:u+t,nodes:o})})}getNodeGroupId(t){if(this.options.groupBy){if(typeof this.options.groupBy=="function")return this.options.groupBy(t);if(typeof this.options.groupBy=="string")return t[this.options.groupBy]}}renderGroups(t,i){this.options.showGroups&&(this.calculateGroupBounds(),this.groupBounds.forEach((o,e)=>{const s=this.getGroupBorderColor(e),r=this.options.groupBorderWidth||2,p=this.options.groupBorderOpacity||.3;t.save(),t.globalAlpha=p,t.strokeStyle=s,t.lineWidth=r/i,t.setLineDash([10/i,5/i]),t.strokeRect(o.minX,o.minY,o.maxX-o.minX,o.maxY-o.minY),t.restore();const u=this.options.groupLabelThreshold||.8;if(i<=u){const n=this.getGroupLabelColor(e),h=(this.options.groupLabelSize||16)/i;t.save(),t.font=`bold ${h}px Arial`,t.fillStyle=n,t.textAlign="center",t.textBaseline="middle";const g=(o.minX+o.maxX)/2,f=o.minY-h/2,c=t.measureText(e).width,y=h;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(g-c/2-4,f-y/2-2,c+8,y+4),t.globalAlpha=1,t.fillStyle=n,t.fillText(e,g,f),t.restore()}}))}getGroupBorderColor(t){return typeof this.options.groupBorderColor=="function"?this.options.groupBorderColor(t):this.options.groupBorderColor||"#666"}getGroupLabelColor(t){return typeof this.options.groupLabelColor=="function"?this.options.groupLabelColor(t):this.options.groupLabelColor||"#333"}getNodeById(t){return this.nodesMap.get(t.toString())}hasNode(t){return this.nodesMap.has(t.toString())}getCooldownTime(){const i=this.data.nodes.length*(125/100);return Math.max(4e3,i)}getDataSize(){const{nodes:t,links:i}=this.graph.graphData();return{nodes:t.length,links:i.length}}getAllNodeIds(){return Array.from(this.nodesMap.keys())}updateNode(t,i){const o=this.nodesMap.get(t.toString());if(o){Object.assign(o,i);const e=this.data.nodes.findIndex(s=>s.id.toString()===t.toString());return e!==-1&&(this.data.nodes[e]=o),this.refreshGraph(),!0}return!1}removeNode(t){const i=t.toString();return this.nodesMap.has(i)?(this.nodesMap.delete(i),this.data.nodes=this.data.nodes.filter(o=>o.id.toString()!==i),this.data.links=this.data.links.filter(o=>{const e=typeof o.source=="object"?o.source.id:o.source,s=typeof o.target=="object"?o.target.id:o.target;return e.toString()!==i&&s.toString()!==i}),this.graph.cooldownTime(this.getCooldownTime()),this.refreshGraph(),!0):!1}async addData(t){t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const o=this.createLinkKey(i.source,i.target);this.linkMap.has(o)?console.log("link already exists",i):this.linkMap.set(o,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.graph.cooldownTime(this.getCooldownTime()),this.graph.graphData(this.data)}setLabelThreshold(t){this.options.labelThreshold=t,this.render()}setOptions(t){this.options={...this.options,...t},this.applyOptions()}refreshGraph(){this.graphData(this.data)}reinitialize(){this.initGraph()}reset(){this.graph.pauseAnimation();const t=this.graph.d3Force("simulation");t&&t.stop(),this.data={nodes:[],links:[]},this.nodesMap.clear(),this.graph=new l(this.container),this.initGraph()}getData(){return this.data}getNodesData(){return this.data.nodes}getLinksData(){return this.data.links}createLinkKey(t,i){const o=typeof t=="object"?t.id:t,e=typeof i=="object"?i.id:i;return`${o}-${e}`}graphData(t){return this.nodesMap.clear(),this.linkMap.clear(),t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const o=this.createLinkKey(i.source,i.target);this.linkMap.has(o)||this.linkMap.set(o,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.graph.cooldownTime(this.getCooldownTime()),this.graph.graphData(this.data),this}showGroups(t){return this.options.showGroups=t,t&&Object.entries({groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}).forEach(([o,e])=>{this.options[o]===void 0&&(this.options[o]=e)}),this.applyOptions(),this.refreshGraph(),this}setGroupBy(t){return this.options.groupBy=t,this.applyOptions(),this.refreshGraph(),this}setGroupOptions(t){return t.borderColor!==void 0&&(this.options.groupBorderColor=t.borderColor),t.borderWidth!==void 0&&(this.options.groupBorderWidth=t.borderWidth),t.borderOpacity!==void 0&&(this.options.groupBorderOpacity=t.borderOpacity),t.labelColor!==void 0&&(this.options.groupLabelColor=t.labelColor),t.labelSize!==void 0&&(this.options.groupLabelSize=t.labelSize),t.labelThreshold!==void 0&&(this.options.groupLabelThreshold=t.labelThreshold),t.padding!==void 0&&(this.options.groupPadding=t.padding),this.applyOptions(),this.refreshGraph(),this}getGroups(){const t=new Set;return this.data.nodes.forEach(i=>{const o=this.getNodeGroupId(i);o&&t.add(o)}),Array.from(t)}getNodesInGroup(t){return this.data.nodes.filter(i=>this.getNodeGroupId(i)===t)}getOptions(){return{...this.options}}destroy(){this.worker&&(this.worker.terminate(),this.worker=null),this.graph._destructor()}}a.ForceGraph=C,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(a,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("force-graph"),require("d3")):typeof define=="function"&&define.amd?define(["exports","force-graph","d3"],l):(a=typeof globalThis<"u"?globalThis:a||self,l(a.ForceGraphLib={},a.ForceGraph,a.d3))})(this,(function(a,l,b){"use strict";function k(d){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(d){for(const i in d)if(i!=="default"){const o=Object.getOwnPropertyDescriptor(d,i);Object.defineProperty(t,i,o.get?o:{enumerable:!0,get:()=>d[i]})}}return t.default=d,Object.freeze(t)}const m=k(b);class C{container;graph;data={nodes:[],links:[]};nodesMap=new Map;linkMap=new Map;options;worker=null;groupBounds=new Map;constructor(t,i={nodes:[],links:[]},o={}){this.container=t,this.data=i,this.graph=new l(this.container),i.nodes.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e={labelThreshold:1.5,showGroups:!1,...o},s=e.showGroups?{groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}:{};this.options={...e,...s},this.initGraph()}initGraph(){this.applyOptions(),this.render(),this.graphData(this.data),this.graph.onEngineStop(()=>this.graph.zoomToFit(400))}renderer(){return this.graph}focusPosition(t={}){if(Object.values(t).length){if(t.id){const{x:i,y:o}=this.nodesMap.get(t.id)??{x:0,y:0};i&&o&&(t={x:i,y:o})}t&&(this.graph.centerAt(t.x,t.y,1e3),this.graph.zoom(8,2e3))}}render(){this.graph.width(this.options.width??800).height(this.options.height??400).cooldownTime(this.getCooldownTime()).d3Force("charge",m.forceManyBody().strength(this.options.nodeGap??-50)),typeof this.options.nodeClickHandler=="function"&&this.graph.onNodeClick(t=>{this.options.nodeClickHandler(t)})}force(t,i){return this.graph.d3Force(t,i)}applyOptions(){this.options.keepDragPosition&&this.graph.onNodeDragEnd(t=>{t.fx=t.x,t.fy=t.y}),this.options.pointerInteraction||this.graph.enablePointerInteraction(!1),this.graph.nodeCanvasObject((t,i,o)=>{t===this.data.nodes[0]&&this.renderGroups(i,o);const e=this.getNodeSize(t)*2,s=typeof this.options.nodeBorderWidth=="function"?this.options.nodeBorderWidth(t):this.options.nodeBorderWidth;i.beginPath(),i.arc(t.x||0,t.y||0,e,0,2*Math.PI),i.fillStyle=this.getNodeColor(t),i.fill(),s&&s>0&&(i.beginPath(),i.arc(t.x||0,t.y||0,e,0,2*Math.PI),i.strokeStyle=this.getNodeBorderColor(t),i.lineWidth=s,i.stroke());const r=this.getNodeLabel(t);if(r&&this.shouldShowLabel(t,o)){const p=typeof this.options.nodeLabelColor=="function"?this.options.nodeLabelColor(t):this.options.nodeLabelColor??"#555",n=(this.options.labelFontSize||14)/o;i.font=`${n}px Arial`,i.fillStyle=p,i.textAlign="center",i.textBaseline="middle",i.fillText(r,t.x||0,(t.y||0)+e+n/2+2)}}),this.applyLinkOptions()}getNodeSize(t){return typeof this.options.nodeSize=="function"?this.options.nodeSize(t)||t?.marker?.radius:this.options.nodeSize||t?.marker?.radius||1}getNodeLabel(t){return typeof this.options.nodeLabel=="function"?this.options.nodeLabel(t):t?.label||t.id}shouldShowLabel(t,i){const o=this.getNodeSize(t)*2,s=(this.options.labelFontSize||14)/i,r=this.options.labelThreshold||1.5;return s<=o*r}updateData(t){const i=new Set(this.data.nodes.map(r=>r.id.toString())),o=t.nodes.filter(r=>!i.has(r.id.toString()));o.forEach(r=>{this.nodesMap.set(r.id.toString(),r)});const e=new Set(this.data.links.map(r=>this.createLinkKey(r.source,r.target))),s=t.links.filter(r=>!e.has(this.createLinkKey(r.source,r.target)));this.data={nodes:[...this.data.nodes,...o],links:[...this.data.links,...s]},this.refreshGraph()}getNodeColor(t){if(typeof this.options.nodeColor=="function"){const i=this.options.nodeColor(t);if(i)return i}return t?.color??""}getNodeBorderColor(t){return typeof this.options.nodeBorderColor=="function"?this.options.nodeBorderColor(t):this.options.nodeBorderColor||"#333"}applyLinkOptions(){this.options.linkWidth!==void 0&&this.graph.linkWidth(t=>this.getLinkProperty(this.options.linkWidth,t)??1),this.options.linkCurvature!==void 0&&this.graph.linkCurvature(this.getLinkCurvature.bind(this)),this.options.linkDirectionalParticles&&this.graph.linkDirectionalParticles(t=>this.getLinkProperty(this.options.linkDirectionalParticles,t)??0),this.options.linkDirectionalParticleSpeed!==void 0&&this.graph.linkDirectionalParticleSpeed(t=>this.getLinkProperty(this.options.linkDirectionalParticleSpeed,t)??0),this.options.linkDirectionalParticleWidth!==void 0&&this.graph.linkDirectionalParticleWidth(t=>this.getLinkProperty(this.options.linkDirectionalParticleWidth,t)??0),this.options.linkDirectionalParticleColor!==void 0&&this.graph.linkDirectionalParticleColor(t=>this.getLinkProperty(this.options.linkDirectionalParticleColor,t)??"#aaa")}getLinkCurvature(t){return typeof this.options.linkCurvature=="function"?this.options.linkCurvature(t):typeof this.options.linkCurvature=="string"?t[this.options.linkCurvature]||0:typeof this.options.linkCurvature=="number"?this.options.linkCurvature:t.curvature||0}getLinkProperty(t,i){return typeof t=="function"?t(i):t}calculateGroupBounds(){if(!this.options.showGroups)return;this.groupBounds.clear();const t=this.options.groupPadding||20,i=new Map;this.data.nodes.forEach(o=>{const e=this.getNodeGroupId(o);e&&(i.has(e)||i.set(e,[]),i.get(e).push(o))}),i.forEach((o,e)=>{if(o.length===0)return;let s=1/0,r=1/0,p=-1/0,u=-1/0;o.forEach(n=>{if(n.x!==void 0&&n.y!==void 0){const h=this.getNodeSize(n);s=Math.min(s,n.x-h),r=Math.min(r,n.y-h),p=Math.max(p,n.x+h),u=Math.max(u,n.y+h)}}),s!==1/0&&this.groupBounds.set(e,{minX:s-t,minY:r-t,maxX:p+t,maxY:u+t,nodes:o})})}getNodeGroupId(t){if(this.options.groupBy){if(typeof this.options.groupBy=="function")return this.options.groupBy(t);if(typeof this.options.groupBy=="string")return t[this.options.groupBy]}}renderGroups(t,i){this.options.showGroups&&(this.calculateGroupBounds(),this.groupBounds.forEach((o,e)=>{const s=this.getGroupBorderColor(e),r=this.options.groupBorderWidth||2,p=this.options.groupBorderOpacity||.3;t.save(),t.globalAlpha=p,t.strokeStyle=s,t.lineWidth=r/i,t.setLineDash([10/i,5/i]),t.strokeRect(o.minX,o.minY,o.maxX-o.minX,o.maxY-o.minY),t.restore();const u=this.options.groupLabelThreshold||.8;if(i<=u){const n=this.getGroupLabelColor(e),h=(this.options.groupLabelSize||16)/i;t.save(),t.font=`bold ${h}px Arial`,t.fillStyle=n,t.textAlign="center",t.textBaseline="middle";const g=(o.minX+o.maxX)/2,f=o.minY-h/2,c=t.measureText(e).width,y=h;t.globalAlpha=.8,t.fillStyle="rgba(255, 255, 255, 0.9)",t.fillRect(g-c/2-4,f-y/2-2,c+8,y+4),t.globalAlpha=1,t.fillStyle=n,t.fillText(e,g,f),t.restore()}}))}getGroupBorderColor(t){return typeof this.options.groupBorderColor=="function"?this.options.groupBorderColor(t):this.options.groupBorderColor||"#666"}getGroupLabelColor(t){return typeof this.options.groupLabelColor=="function"?this.options.groupLabelColor(t):this.options.groupLabelColor||"#333"}getNodeById(t){return this.nodesMap.get(t.toString())}hasNode(t){return this.nodesMap.has(t.toString())}getCooldownTime(){const i=this.data.nodes.length*(125/100);return Math.max(4e3,i)}getDataSize(){const{nodes:t,links:i}=this.graph.graphData();return{nodes:t.length,links:i.length}}getAllNodeIds(){return Array.from(this.nodesMap.keys())}updateNode(t,i){const o=this.nodesMap.get(t.toString());if(o){Object.assign(o,i);const e=this.data.nodes.findIndex(s=>s.id.toString()===t.toString());return e!==-1&&(this.data.nodes[e]=o),this.refreshGraph(),!0}return!1}removeNode(t){const i=t.toString();return this.nodesMap.has(i)?(this.nodesMap.delete(i),this.data.nodes=this.data.nodes.filter(o=>o.id.toString()!==i),this.data.links=this.data.links.filter(o=>{const e=typeof o.source=="object"?o.source.id:o.source,s=typeof o.target=="object"?o.target.id:o.target;return e?.toString()!==i&&s?.toString()!==i}),this.graph.cooldownTime(this.getCooldownTime()),this.refreshGraph(),!0):!1}async addData(t){t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const o=this.createLinkKey(i.source,i.target);this.linkMap.has(o)?console.log("link already exists",i):this.linkMap.set(o,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.graph.cooldownTime(this.getCooldownTime()),this.graph.graphData(this.data)}setLabelThreshold(t){this.options.labelThreshold=t,this.render()}setOptions(t){this.options={...this.options,...t},this.applyOptions()}refreshGraph(){this.graphData(this.data)}reinitialize(){this.initGraph()}reset(){this.graph.pauseAnimation();const t=this.graph.d3Force("simulation");t&&t.stop(),this.data={nodes:[],links:[]},this.nodesMap.clear(),this.graph=new l(this.container),this.initGraph()}getData(){return this.data}getNodesData(){return this.data.nodes}getLinksData(){return this.data.links}createLinkKey(t,i){const o=t===void 0?"undefined":typeof t=="object"?t.id:t,e=i===void 0?"undefined":typeof i=="object"?i.id:i;return`${o}-${e}`}graphData(t){return this.nodesMap.clear(),this.linkMap.clear(),t.nodes.forEach(i=>{this.nodesMap.has(i.id.toString())||this.nodesMap.set(i.id.toString(),i)}),t.links.forEach(i=>{const o=this.createLinkKey(i.source,i.target);this.linkMap.has(o)||this.linkMap.set(o,i)}),this.data={nodes:Array.from(this.nodesMap.values()),links:Array.from(this.linkMap.values())},this.graph.cooldownTime(this.getCooldownTime()),this.graph.graphData(this.data),this}showGroups(t){return this.options.showGroups=t,t&&Object.entries({groupBy:"topic",groupBorderColor:"#666",groupBorderWidth:2,groupBorderOpacity:.3,groupLabelColor:"#333",groupLabelSize:16,groupLabelThreshold:.8,groupPadding:20}).forEach(([o,e])=>{this.options[o]===void 0&&(this.options[o]=e)}),this.applyOptions(),this.refreshGraph(),this}setGroupBy(t){return this.options.groupBy=t,this.applyOptions(),this.refreshGraph(),this}setGroupOptions(t){return t.borderColor!==void 0&&(this.options.groupBorderColor=t.borderColor),t.borderWidth!==void 0&&(this.options.groupBorderWidth=t.borderWidth),t.borderOpacity!==void 0&&(this.options.groupBorderOpacity=t.borderOpacity),t.labelColor!==void 0&&(this.options.groupLabelColor=t.labelColor),t.labelSize!==void 0&&(this.options.groupLabelSize=t.labelSize),t.labelThreshold!==void 0&&(this.options.groupLabelThreshold=t.labelThreshold),t.padding!==void 0&&(this.options.groupPadding=t.padding),this.applyOptions(),this.refreshGraph(),this}getGroups(){const t=new Set;return this.data.nodes.forEach(i=>{const o=this.getNodeGroupId(i);o&&t.add(o)}),Array.from(t)}getNodesInGroup(t){return this.data.nodes.filter(i=>this.getNodeGroupId(i)===t)}getOptions(){return{...this.options}}destroy(){this.worker&&(this.worker.terminate(),this.worker=null),this.graph._destructor()}}a.ForceGraph=C,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
package/dist/index.d.ts CHANGED
@@ -9,7 +9,29 @@ declare interface ForceFn<N extends NodeObject = NodeObject> {
9
9
  [key: string]: any;
10
10
  }
11
11
 
12
- export declare class ForceGraph {
12
+ /**
13
+ * ForceGraph with flexible generic types.
14
+ *
15
+ * - `TNode`: your node shape; must include an `id` (`string | number`).
16
+ * - `TLink`: your link shape; by default `LinkObject<TNode>`.
17
+ *
18
+ * Usage examples:
19
+ *
20
+ * // 1) Use defaults (compatible with NodeData/LinkObject)
21
+ * const graph = new ForceGraph(container, defaultData)
22
+ *
23
+ * // 2) Provide custom shapes
24
+ * type MyNode = NodeObject & { id: string; color?: string; size?: number }
25
+ * type MyLink = LinkObject<MyNode> & { weight?: number }
26
+ * const graph = new ForceGraph<MyNode, MyLink>(container, myData, {
27
+ * nodeSize: (n) => n.size ?? 2,
28
+ * nodeColor: (n) => n.color ?? '#999',
29
+ * linkWidth: (l) => (l.weight ?? 1) * 2,
30
+ * })
31
+ */
32
+ export declare class ForceGraph<TNode extends NodeData & {
33
+ id: string | number;
34
+ } = NodeData, TLink extends LinkData<TNode> = LinkData<TNode>> {
13
35
  private container;
14
36
  private graph;
15
37
  private data;
@@ -18,16 +40,16 @@ export declare class ForceGraph {
18
40
  private options;
19
41
  private worker;
20
42
  private groupBounds;
21
- constructor(container: HTMLElement, initialData?: GraphData, options?: GraphOptions);
43
+ constructor(container: HTMLElement, initialData?: GraphData<TNode, TLink>, options?: GraphOptions<TNode, TLink>);
22
44
  private initGraph;
23
- renderer(): default_2<NodeData, LinkObject<NodeData>>;
45
+ renderer(): default_2<TNode, TLink>;
24
46
  focusPosition(nodeData?: {
25
47
  id?: string;
26
48
  x?: number;
27
49
  y?: number;
28
50
  }): void;
29
51
  render(): void;
30
- force(key: ForceType, func: ForceFn<NodeData>): default_2<NodeData, LinkObject<NodeData>>;
52
+ force(key: ForceType, func: ForceFn<TNode>): default_2<TNode, TLink>;
31
53
  private applyOptions;
32
54
  private getNodeSize;
33
55
  private getNodeLabel;
@@ -36,7 +58,7 @@ export declare class ForceGraph {
36
58
  * The threshold determines when labels become too big relative to nodes
37
59
  */
38
60
  private shouldShowLabel;
39
- updateData(data: GraphData): void;
61
+ updateData(data: GraphData<TNode, TLink>): void;
40
62
  private getNodeColor;
41
63
  private getNodeBorderColor;
42
64
  private applyLinkOptions;
@@ -62,7 +84,7 @@ export declare class ForceGraph {
62
84
  * Get group label color
63
85
  */
64
86
  private getGroupLabelColor;
65
- getNodeById(id: string | number): NodeData | undefined;
87
+ getNodeById(id: string | number): TNode | undefined;
66
88
  hasNode(id: string | number): boolean;
67
89
  /**
68
90
  * Calculate dynamic cooldown time based on node count
@@ -79,11 +101,11 @@ export declare class ForceGraph {
79
101
  links: number;
80
102
  };
81
103
  getAllNodeIds(): string[];
82
- updateNode(id: string | number, updates: Partial<NodeData>): boolean;
104
+ updateNode(id: string | number, updates: Partial<TNode>): boolean;
83
105
  removeNode(id: string | number): boolean;
84
- addData(newData: GraphData): Promise<void>;
106
+ addData(newData: GraphData<TNode, TLink>): Promise<void>;
85
107
  setLabelThreshold(threshold: number): void;
86
- setOptions(options: Partial<GraphOptions>): void;
108
+ setOptions(options: Partial<GraphOptions<TNode, TLink>>): void;
87
109
  /**
88
110
  * Lightweight refresh - only updates graph data
89
111
  */
@@ -93,23 +115,23 @@ export declare class ForceGraph {
93
115
  */
94
116
  reinitialize(): void;
95
117
  reset(): void;
96
- getData(): GraphData;
97
- getNodesData(): GraphData['nodes'];
98
- getLinksData(): GraphData['links'];
118
+ getData(): GraphData<TNode, TLink>;
119
+ getNodesData(): GraphData<TNode, TLink>['nodes'];
120
+ getLinksData(): GraphData<TNode, TLink>['links'];
99
121
  private createLinkKey;
100
122
  /**
101
123
  * Set graph data (chainable method)
102
124
  * @param data - Graph data to set
103
125
  */
104
- graphData(data: GraphData): ForceGraph;
126
+ graphData(data: GraphData<TNode, TLink>): ForceGraph<TNode, TLink>;
105
127
  /**
106
128
  * Enable or disable group visualization
107
129
  */
108
- showGroups(show: boolean): ForceGraph;
130
+ showGroups(show: boolean): ForceGraph<TNode, TLink>;
109
131
  /**
110
132
  * Set the property to group nodes by
111
133
  */
112
- setGroupBy(groupBy: string | ((node: NodeData) => string | undefined)): ForceGraph;
134
+ setGroupBy(groupBy: string | ((node: TNode) => string | undefined)): ForceGraph<TNode, TLink>;
113
135
  /**
114
136
  * Set group visualization options
115
137
  */
@@ -121,7 +143,7 @@ export declare class ForceGraph {
121
143
  labelSize?: number;
122
144
  labelThreshold?: number;
123
145
  padding?: number;
124
- }): ForceGraph;
146
+ }): ForceGraph<TNode, TLink>;
125
147
  /**
126
148
  * Get all available groups
127
149
  */
@@ -129,46 +151,45 @@ export declare class ForceGraph {
129
151
  /**
130
152
  * Get nodes in a specific group
131
153
  */
132
- getNodesInGroup(groupId: string): NodeData[];
154
+ getNodesInGroup(groupId: string): TNode[];
133
155
  /**
134
156
  * Get current options
135
157
  */
136
- getOptions(): GraphOptions;
158
+ getOptions(): GraphOptions<TNode, TLink>;
137
159
  destroy(): void;
138
160
  }
139
161
 
140
162
  declare type ForceType = 'link' | 'charge' | 'center' | 'cluster' | string;
141
163
 
142
- export declare interface GraphData extends GraphData_2<NodeData, LinkData> {
164
+ export declare interface GraphData<N extends NodeData = NodeData, L extends LinkData<N> = LinkData<N>> extends GraphData_2<N, L> {
143
165
  }
144
166
 
145
- export declare interface GraphOptions {
167
+ export declare interface GraphOptions<N extends NodeData = NodeData, L extends LinkData<N> = LinkData<N>> {
146
168
  height?: number;
147
169
  width?: number;
148
170
  labelThreshold?: number;
149
- nodeSize?: number | ((node: NodeData) => number);
150
- linkWidth?: number | ((link: LinkData) => number);
151
- nodeLabel?: string | ((node: NodeData) => string);
152
- nodeLabelColor?: string | ((node: NodeData) => string);
171
+ nodeSize?: number | ((node: N) => number);
172
+ linkWidth?: number | ((link: L) => number);
173
+ nodeLabel?: string | ((node: N) => string);
174
+ nodeLabelColor?: string | ((node: N) => string);
153
175
  labelFontSize?: number;
154
- nodeColor?: string | ((node: NodeData) => string);
155
- nodeBorderColor?: string | ((node: NodeData) => string);
156
- nodeBorderWidth?: number | ((node: NodeData) => number);
176
+ nodeColor?: string | ((node: N) => string);
177
+ nodeBorderColor?: string | ((node: N) => string);
178
+ nodeBorderWidth?: number | ((node: N) => number);
157
179
  nodeGap?: number;
158
- linkLabel?: string | ((link: LinkData) => string);
159
- nodeIcon?: string | ((node: NodeData) => string);
160
- cluster?: (node: NodeData) => boolean | undefined | null;
180
+ linkLabel?: string | ((link: L) => string);
181
+ nodeIcon?: string | ((node: N) => string);
161
182
  loading?: boolean;
162
183
  pointerInteraction?: boolean;
163
184
  keepDragPosition?: boolean;
164
- nodeClickHandler?: (node: NodeData) => void;
165
- linkCurvature?: number | string | ((link: LinkData) => number);
166
- linkDirectionalParticles?: number | ((link: LinkData) => number);
167
- linkDirectionalParticleSpeed?: number | ((link: LinkData) => number);
168
- linkDirectionalParticleWidth?: number | ((link: LinkData) => number);
169
- linkDirectionalParticleColor?: string | ((link: LinkData) => string);
185
+ nodeClickHandler?: (node: N) => void;
186
+ linkCurvature?: number | string | ((link: L) => number);
187
+ linkDirectionalParticles?: number | ((link: L) => number);
188
+ linkDirectionalParticleSpeed?: number | ((link: L) => number);
189
+ linkDirectionalParticleWidth?: number | ((link: L) => number);
190
+ linkDirectionalParticleColor?: string | ((link: L) => string);
170
191
  showGroups?: boolean;
171
- groupBy?: string | ((node: NodeData) => string | undefined);
192
+ groupBy?: string | ((node: N) => string | undefined);
172
193
  groupBorderColor?: string | ((groupId: string) => string);
173
194
  groupBorderWidth?: number;
174
195
  groupBorderOpacity?: number;
@@ -178,9 +199,9 @@ export declare interface GraphOptions {
178
199
  groupPadding?: number;
179
200
  }
180
201
 
181
- export declare interface LinkData extends LinkObject<NodeData> {
182
- source: string | number | NodeData;
183
- target: string | number | NodeData;
202
+ export declare interface LinkData<N extends NodeData = NodeData> extends LinkObject<N> {
203
+ source: string | number | N;
204
+ target: string | number | N;
184
205
  weight?: number;
185
206
  color?: string;
186
207
  curvature?: 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.1.6",
4
+ "version": "0.1.9",
5
5
  "description": "Force-directed graph visualization library.",
6
6
  "author": "Rohmad Kurniadi",
7
7
  "license": "MIT",