force-graph 1.49.6 → 1.50.1

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.
@@ -418,6 +418,11 @@ var CanvasForceGraph = Kapsule({
418
418
  triggerUpdate: false
419
419
  },
420
420
  // in link length ratio per frame
421
+ linkDirectionalParticleOffset: {
422
+ "default": 0,
423
+ triggerUpdate: false
424
+ },
425
+ // starting position offset along the link's length, like a pre-delay. Values between [0, 1]
421
426
  linkDirectionalParticleWidth: {
422
427
  "default": 4,
423
428
  triggerUpdate: false
@@ -425,6 +430,9 @@ var CanvasForceGraph = Kapsule({
425
430
  linkDirectionalParticleColor: {
426
431
  triggerUpdate: false
427
432
  },
433
+ linkDirectionalParticleCanvasObject: {
434
+ triggerUpdate: false
435
+ },
428
436
  globalScale: {
429
437
  "default": 1,
430
438
  triggerUpdate: false
@@ -746,6 +754,7 @@ var CanvasForceGraph = Kapsule({
746
754
  function paintPhotons() {
747
755
  var getNumPhotons = accessorFn(state.linkDirectionalParticles);
748
756
  var getSpeed = accessorFn(state.linkDirectionalParticleSpeed);
757
+ var getOffset = accessorFn(state.linkDirectionalParticleOffset);
749
758
  var getDiameter = accessorFn(state.linkDirectionalParticleWidth);
750
759
  var getVisibility = accessorFn(state.linkVisibility);
751
760
  var getColor = accessorFn(state.linkDirectionalParticleColor || state.linkColor);
@@ -759,6 +768,7 @@ var CanvasForceGraph = Kapsule({
759
768
  if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
760
769
 
761
770
  var particleSpeed = getSpeed(link);
771
+ var particleOffset = Math.abs(getOffset(link));
762
772
  var photons = link.__photons || [];
763
773
  var photonR = Math.max(0, getDiameter(link) / 2) / Math.sqrt(state.globalScale);
764
774
  var photonColor = getColor(link) || 'rgba(0,0,0,0.28)';
@@ -771,7 +781,7 @@ var CanvasForceGraph = Kapsule({
771
781
  photons.forEach(function (photon) {
772
782
  var singleHop = !!photon.__singleHop;
773
783
  if (!photon.hasOwnProperty('__progressRatio')) {
774
- photon.__progressRatio = singleHop ? 0 : cyclePhotonIdx / numCyclePhotons;
784
+ photon.__progressRatio = singleHop ? 0 : (cyclePhotonIdx + particleOffset) / numCyclePhotons;
775
785
  }
776
786
  !singleHop && cyclePhotonIdx++; // increase regular photon index
777
787
 
@@ -791,9 +801,13 @@ var CanvasForceGraph = Kapsule({
791
801
  x: start.x + (end.x - start.x) * photonPosRatio || 0,
792
802
  y: start.y + (end.y - start.y) * photonPosRatio || 0
793
803
  };
794
- ctx.beginPath();
795
- ctx.arc(coords.x, coords.y, photonR, 0, 2 * Math.PI, false);
796
- ctx.fill();
804
+ if (state.linkDirectionalParticleCanvasObject) {
805
+ state.linkDirectionalParticleCanvasObject(coords.x, coords.y, link, ctx, state.globalScale);
806
+ } else {
807
+ ctx.beginPath();
808
+ ctx.arc(coords.x, coords.y, photonR, 0, 2 * Math.PI, false);
809
+ ctx.fill();
810
+ }
797
811
  });
798
812
  if (needsCleanup) {
799
813
  // remove expired single hop photons
@@ -945,7 +959,7 @@ var DRAG_CLICK_TOLERANCE_PX = 5; // How many px can a node be accidentally dragg
945
959
  // Expose config from forceGraph
946
960
  var bindFG = linkKapsule('forceGraph', CanvasForceGraph);
947
961
  var bindBoth = linkKapsule(['forceGraph', 'shadowGraph'], CanvasForceGraph);
948
- var linkedProps = Object.assign.apply(Object, _toConsumableArray(['nodeColor', 'nodeAutoColorBy', 'nodeCanvasObject', 'nodeCanvasObjectMode', 'linkColor', 'linkAutoColorBy', 'linkLineDash', 'linkWidth', 'linkCanvasObject', 'linkCanvasObjectMode', 'linkDirectionalArrowLength', 'linkDirectionalArrowColor', 'linkDirectionalArrowRelPos', 'linkDirectionalParticles', 'linkDirectionalParticleSpeed', 'linkDirectionalParticleWidth', 'linkDirectionalParticleColor', 'dagMode', 'dagLevelDistance', 'dagNodeFilter', 'onDagError', 'd3AlphaMin', 'd3AlphaDecay', 'd3VelocityDecay', 'warmupTicks', 'cooldownTicks', 'cooldownTime', 'onEngineTick', 'onEngineStop'].map(function (p) {
962
+ var linkedProps = Object.assign.apply(Object, _toConsumableArray(['nodeColor', 'nodeAutoColorBy', 'nodeCanvasObject', 'nodeCanvasObjectMode', 'linkColor', 'linkAutoColorBy', 'linkLineDash', 'linkWidth', 'linkCanvasObject', 'linkCanvasObjectMode', 'linkDirectionalArrowLength', 'linkDirectionalArrowColor', 'linkDirectionalArrowRelPos', 'linkDirectionalParticles', 'linkDirectionalParticleSpeed', 'linkDirectionalParticleOffset', 'linkDirectionalParticleWidth', 'linkDirectionalParticleColor', 'linkDirectionalParticleCanvasObject', 'dagMode', 'dagLevelDistance', 'dagNodeFilter', 'onDagError', 'd3AlphaMin', 'd3AlphaDecay', 'd3VelocityDecay', 'warmupTicks', 'cooldownTicks', 'cooldownTime', 'onEngineTick', 'onEngineStop'].map(function (p) {
949
963
  return _defineProperty({}, p, bindFG.linkProp(p));
950
964
  })).concat(_toConsumableArray(['nodeRelSize', 'nodeId', 'nodeVal', 'nodeVisibility', 'linkSource', 'linkTarget', 'linkVisibility', 'linkCurvature'].map(function (p) {
951
965
  return _defineProperty({}, p, bindBoth.linkProp(p));
@@ -1511,9 +1525,9 @@ var forceGraph = Kapsule({
1511
1525
  // detect pointer drag on canvas pan
1512
1526
  !state.isPointerDragging && ev.type === 'pointermove' && state.onBackgroundClick // only bother detecting drags this way if background clicks are enabled (so they don't trigger accidentally on canvas panning)
1513
1527
  && (ev.pressure > 0 || state.isPointerPressed) // ev.pressure always 0 on Safari, so we use the isPointerPressed tracker
1514
- && (ev.pointerType !== 'touch' || ev.movementX === undefined || [ev.movementX, ev.movementY].some(function (m) {
1528
+ && (ev.pointerType === 'mouse' || ev.movementX === undefined || [ev.movementX, ev.movementY].some(function (m) {
1515
1529
  return Math.abs(m) > 1;
1516
- })) // relax drag trigger sensitivity on touch events
1530
+ })) // relax drag trigger sensitivity on non-mouse (touch/pen) events
1517
1531
  && (state.isPointerDragging = true);
1518
1532
 
1519
1533
  // update the pointer pos
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "force-graph",
3
- "version": "1.49.6",
3
+ "version": "1.50.1",
4
4
  "description": "2D force-directed graph rendered on HTML5 canvas",
5
5
  "type": "module",
6
6
  "unpkg": "dist/force-graph.min.js",
@@ -65,15 +65,15 @@
65
65
  "lodash-es": "4"
66
66
  },
67
67
  "devDependencies": {
68
- "@babel/core": "^7.27.1",
69
- "@babel/preset-env": "^7.27.2",
68
+ "@babel/core": "^7.28.0",
69
+ "@babel/preset-env": "^7.28.0",
70
70
  "@rollup/plugin-babel": "^6.0.4",
71
- "@rollup/plugin-commonjs": "^28.0.3",
71
+ "@rollup/plugin-commonjs": "^28.0.6",
72
72
  "@rollup/plugin-node-resolve": "^16.0.1",
73
73
  "@rollup/plugin-terser": "^0.4.4",
74
- "postcss": "^8.5.3",
74
+ "postcss": "^8.5.6",
75
75
  "rimraf": "^6.0.1",
76
- "rollup": "^4.40.2",
76
+ "rollup": "^4.44.1",
77
77
  "rollup-plugin-dts": "^6.2.1",
78
78
  "rollup-plugin-postcss": "^4.0.2",
79
79
  "typescript": "^5.8.3"
@@ -78,8 +78,10 @@ export default Kapsule({
78
78
  linkDirectionalArrowRelPos: { default: 0.5, triggerUpdate: false, onChange: notifyRedraw }, // value between 0<>1 indicating the relative pos along the (exposed) line
79
79
  linkDirectionalParticles: { default: 0, triggerUpdate: false, onChange: updDataPhotons }, // animate photons travelling in the link direction
80
80
  linkDirectionalParticleSpeed: { default: 0.01, triggerUpdate: false }, // in link length ratio per frame
81
+ linkDirectionalParticleOffset: { default: 0, triggerUpdate: false }, // starting position offset along the link's length, like a pre-delay. Values between [0, 1]
81
82
  linkDirectionalParticleWidth: { default: 4, triggerUpdate: false },
82
83
  linkDirectionalParticleColor: { triggerUpdate: false },
84
+ linkDirectionalParticleCanvasObject: { triggerUpdate: false },
83
85
  globalScale: { default: 1, triggerUpdate: false },
84
86
  d3AlphaMin: { default: 0, triggerUpdate: false},
85
87
  d3AlphaDecay: { default: 0.0228, triggerUpdate: false, onChange(alphaDecay, state) { state.forceLayout.alphaDecay(alphaDecay) }},
@@ -365,6 +367,7 @@ export default Kapsule({
365
367
  function paintPhotons() {
366
368
  const getNumPhotons = accessorFn(state.linkDirectionalParticles);
367
369
  const getSpeed = accessorFn(state.linkDirectionalParticleSpeed);
370
+ const getOffset = accessorFn(state.linkDirectionalParticleOffset);
368
371
  const getDiameter = accessorFn(state.linkDirectionalParticleWidth);
369
372
  const getVisibility = accessorFn(state.linkVisibility);
370
373
  const getColor = accessorFn(state.linkDirectionalParticleColor || state.linkColor);
@@ -382,6 +385,7 @@ export default Kapsule({
382
385
  if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
383
386
 
384
387
  const particleSpeed = getSpeed(link);
388
+ const particleOffset = Math.abs(getOffset(link));
385
389
  const photons = link.__photons || [];
386
390
  const photonR = Math.max(0, getDiameter(link) / 2) / Math.sqrt(state.globalScale);
387
391
  const photonColor = getColor(link) || 'rgba(0,0,0,0.28)';
@@ -399,7 +403,7 @@ export default Kapsule({
399
403
  const singleHop = !!photon.__singleHop;
400
404
 
401
405
  if (!photon.hasOwnProperty('__progressRatio')) {
402
- photon.__progressRatio = singleHop ? 0 : cyclePhotonIdx / numCyclePhotons;
406
+ photon.__progressRatio = singleHop ? 0 : (cyclePhotonIdx + particleOffset) / numCyclePhotons;
403
407
  }
404
408
 
405
409
  !singleHop && cyclePhotonIdx++; // increase regular photon index
@@ -424,9 +428,13 @@ export default Kapsule({
424
428
  y: start.y + (end.y - start.y) * photonPosRatio || 0
425
429
  };
426
430
 
427
- ctx.beginPath();
428
- ctx.arc(coords.x, coords.y, photonR, 0, 2 * Math.PI, false);
429
- ctx.fill();
431
+ if(state.linkDirectionalParticleCanvasObject) {
432
+ state.linkDirectionalParticleCanvasObject(coords.x, coords.y, link, ctx, state.globalScale);
433
+ } else {
434
+ ctx.beginPath();
435
+ ctx.arc(coords.x, coords.y, photonR, 0, 2 * Math.PI, false);
436
+ ctx.fill();
437
+ }
430
438
  });
431
439
 
432
440
  if (needsCleanup) {
@@ -36,8 +36,10 @@ const linkedProps = Object.assign(
36
36
  'linkDirectionalArrowRelPos',
37
37
  'linkDirectionalParticles',
38
38
  'linkDirectionalParticleSpeed',
39
+ 'linkDirectionalParticleOffset',
39
40
  'linkDirectionalParticleWidth',
40
41
  'linkDirectionalParticleColor',
42
+ 'linkDirectionalParticleCanvasObject',
41
43
  'dagMode',
42
44
  'dagLevelDistance',
43
45
  'dagNodeFilter',
@@ -526,7 +528,7 @@ export default Kapsule({
526
528
  !state.isPointerDragging && ev.type === 'pointermove'
527
529
  && (state.onBackgroundClick) // only bother detecting drags this way if background clicks are enabled (so they don't trigger accidentally on canvas panning)
528
530
  && (ev.pressure > 0 || state.isPointerPressed) // ev.pressure always 0 on Safari, so we use the isPointerPressed tracker
529
- && (ev.pointerType !== 'touch' || ev.movementX === undefined || [ev.movementX, ev.movementY].some(m => Math.abs(m) > 1)) // relax drag trigger sensitivity on touch events
531
+ && (ev.pointerType === 'mouse' || ev.movementX === undefined || [ev.movementX, ev.movementY].some(m => Math.abs(m) > 1)) // relax drag trigger sensitivity on non-mouse (touch/pen) events
530
532
  && (state.isPointerDragging = true);
531
533
 
532
534
  // update the pointer pos
package/src/index.d.ts CHANGED
@@ -29,6 +29,7 @@ type CanvasCustomRenderMode = 'replace' | 'before' | 'after';
29
29
  export type CanvasCustomRenderModeFn<T> = (obj: T) => CanvasCustomRenderMode | any;
30
30
  export type CanvasCustomRenderFn<T> = (obj: T, canvasContext: CanvasRenderingContext2D, globalScale: number) => void;
31
31
  export type CanvasPointerAreaPaintFn<T> = (obj: T, paintColor: string, canvasContext: CanvasRenderingContext2D, globalScale: number) => void;
32
+ export type CanvasLinkParticleRenderFn<L> = (x: number, y: number, link: L, canvasContext: CanvasRenderingContext2D, globalScale: number) => void;
32
33
 
33
34
  type DagMode = 'td' | 'bu' | 'lr' | 'rl' | 'radialout' | 'radialin';
34
35
 
@@ -110,10 +111,14 @@ export declare class ForceGraphGeneric<ChainableInstance, N extends NodeObject =
110
111
  linkDirectionalParticles(numParticlesAccessor: LinkAccessor<number, N, L>): ChainableInstance;
111
112
  linkDirectionalParticleSpeed(): LinkAccessor<number, N, L>;
112
113
  linkDirectionalParticleSpeed(relDistancePerFrameAccessor: LinkAccessor<number, N, L>): ChainableInstance;
114
+ linkDirectionalParticleOffset(): LinkAccessor<number, N, L>;
115
+ linkDirectionalParticleOffset(relOffset: LinkAccessor<number, N, L>): ChainableInstance;
113
116
  linkDirectionalParticleWidth(): LinkAccessor<number, N, L>;
114
117
  linkDirectionalParticleWidth(widthAccessor: LinkAccessor<number, N, L>): ChainableInstance;
115
118
  linkDirectionalParticleColor(): LinkAccessor<string, N, L>;
116
119
  linkDirectionalParticleColor(colorAccessor: LinkAccessor<string, N, L>): ChainableInstance;
120
+ linkDirectionalParticleCanvasObject(): CanvasLinkParticleRenderFn<L>;
121
+ linkDirectionalParticleCanvasObject(renderFn: CanvasLinkParticleRenderFn<L>): ChainableInstance;
117
122
  emitParticle(link: L): ChainableInstance;
118
123
  linkPointerAreaPaint(): CanvasPointerAreaPaintFn<L>;
119
124
  linkPointerAreaPaint(renderFn: CanvasPointerAreaPaintFn<L>): ChainableInstance;