force-graph 1.42.3

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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/dist/force-graph.common.js +1754 -0
  4. package/dist/force-graph.d.ts +195 -0
  5. package/dist/force-graph.js +12168 -0
  6. package/dist/force-graph.js.map +1 -0
  7. package/dist/force-graph.min.js +5 -0
  8. package/dist/force-graph.module.js +1743 -0
  9. package/example/auto-colored/index.html +34 -0
  10. package/example/basic/index.html +29 -0
  11. package/example/build-a-graph/index.html +108 -0
  12. package/example/click-to-focus/index.html +28 -0
  13. package/example/collision-detection/index.html +50 -0
  14. package/example/curved-links/index.html +37 -0
  15. package/example/curved-links-computed-curvature/index.html +76 -0
  16. package/example/custom-node-shape/index.html +44 -0
  17. package/example/dag-yarn/index.html +96 -0
  18. package/example/dagre/index.html +119 -0
  19. package/example/dash-odd-links/index.html +47 -0
  20. package/example/datasets/blocks.json +1 -0
  21. package/example/datasets/d3-dependencies.csv +464 -0
  22. package/example/datasets/miserables.json +337 -0
  23. package/example/datasets/mplate.mtx +74090 -0
  24. package/example/directional-links-arrows/index.html +29 -0
  25. package/example/directional-links-particles/index.html +22 -0
  26. package/example/dynamic/index.html +42 -0
  27. package/example/emit-particles/index.html +50 -0
  28. package/example/expandable-nodes/index.html +66 -0
  29. package/example/expandable-tree/index.html +85 -0
  30. package/example/fit-to-canvas/index.html +34 -0
  31. package/example/fix-dragged-nodes/index.html +24 -0
  32. package/example/highlight/index.html +84 -0
  33. package/example/huge-1M/index.html +37 -0
  34. package/example/img-nodes/imgs/cat.jpg +0 -0
  35. package/example/img-nodes/imgs/dog.jpg +0 -0
  36. package/example/img-nodes/imgs/eagle.jpg +0 -0
  37. package/example/img-nodes/imgs/elephant.jpg +0 -0
  38. package/example/img-nodes/imgs/grasshopper.jpg +0 -0
  39. package/example/img-nodes/imgs/octopus.jpg +0 -0
  40. package/example/img-nodes/imgs/owl.jpg +0 -0
  41. package/example/img-nodes/imgs/panda.jpg +0 -0
  42. package/example/img-nodes/imgs/squirrel.jpg +0 -0
  43. package/example/img-nodes/imgs/tiger.jpg +0 -0
  44. package/example/img-nodes/imgs/whale.jpg +0 -0
  45. package/example/img-nodes/index.html +43 -0
  46. package/example/large-graph/index.html +41 -0
  47. package/example/load-json/index.html +24 -0
  48. package/example/medium-graph/index.html +26 -0
  49. package/example/medium-graph/preview.png +0 -0
  50. package/example/move-viewport/index.html +42 -0
  51. package/example/multi-selection/index.html +57 -0
  52. package/example/responsive/index.html +37 -0
  53. package/example/text-links/index.html +69 -0
  54. package/example/text-nodes/index.html +42 -0
  55. package/example/tree/index.html +71 -0
  56. package/package.json +72 -0
  57. package/src/canvas-force-graph.js +544 -0
  58. package/src/color-utils.js +17 -0
  59. package/src/dagDepths.js +51 -0
  60. package/src/force-graph.css +35 -0
  61. package/src/force-graph.js +644 -0
  62. package/src/index.d.ts +195 -0
  63. package/src/index.js +3 -0
  64. package/src/kapsule-link.js +34 -0
@@ -0,0 +1,1743 @@
1
+ import { select } from 'd3-selection';
2
+ import { zoomTransform, zoom } from 'd3-zoom';
3
+ import { drag } from 'd3-drag';
4
+ import { min, max } from 'd3-array';
5
+ import throttle from 'lodash.throttle';
6
+ import TWEEN from '@tweenjs/tween.js';
7
+ import Kapsule from 'kapsule';
8
+ import accessorFn from 'accessor-fn';
9
+ import ColorTracker from 'canvas-color-tracker';
10
+ import { forceSimulation, forceLink, forceManyBody, forceCenter, forceRadial } from 'd3-force-3d';
11
+ import { Bezier } from 'bezier-js';
12
+ import indexBy from 'index-array-by';
13
+ import { scaleOrdinal } from 'd3-scale';
14
+ import { schemePaired } from 'd3-scale-chromatic';
15
+
16
+ function styleInject(css, ref) {
17
+ if (ref === void 0) ref = {};
18
+ var insertAt = ref.insertAt;
19
+
20
+ if (!css || typeof document === 'undefined') {
21
+ return;
22
+ }
23
+
24
+ var head = document.head || document.getElementsByTagName('head')[0];
25
+ var style = document.createElement('style');
26
+ style.type = 'text/css';
27
+
28
+ if (insertAt === 'top') {
29
+ if (head.firstChild) {
30
+ head.insertBefore(style, head.firstChild);
31
+ } else {
32
+ head.appendChild(style);
33
+ }
34
+ } else {
35
+ head.appendChild(style);
36
+ }
37
+
38
+ if (style.styleSheet) {
39
+ style.styleSheet.cssText = css;
40
+ } else {
41
+ style.appendChild(document.createTextNode(css));
42
+ }
43
+ }
44
+
45
+ var css_248z = ".force-graph-container canvas {\n display: block;\n user-select: none;\n outline: none;\n -webkit-tap-highlight-color: transparent;\n}\n\n.force-graph-container .graph-tooltip {\n position: absolute;\n transform: translate(-50%, 25px);\n font-family: sans-serif;\n font-size: 16px;\n padding: 4px;\n border-radius: 3px;\n color: #eee;\n background: rgba(0,0,0,0.65);\n visibility: hidden; /* by default */\n}\n\n.force-graph-container .clickable {\n cursor: pointer;\n}\n\n.force-graph-container .grabbable {\n cursor: move;\n cursor: grab;\n cursor: -moz-grab;\n cursor: -webkit-grab;\n}\n\n.force-graph-container .grabbable:active {\n cursor: grabbing;\n cursor: -moz-grabbing;\n cursor: -webkit-grabbing;\n}\n";
46
+ styleInject(css_248z);
47
+
48
+ function ownKeys(object, enumerableOnly) {
49
+ var keys = Object.keys(object);
50
+
51
+ if (Object.getOwnPropertySymbols) {
52
+ var symbols = Object.getOwnPropertySymbols(object);
53
+
54
+ if (enumerableOnly) {
55
+ symbols = symbols.filter(function (sym) {
56
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
57
+ });
58
+ }
59
+
60
+ keys.push.apply(keys, symbols);
61
+ }
62
+
63
+ return keys;
64
+ }
65
+
66
+ function _objectSpread2(target) {
67
+ for (var i = 1; i < arguments.length; i++) {
68
+ var source = arguments[i] != null ? arguments[i] : {};
69
+
70
+ if (i % 2) {
71
+ ownKeys(Object(source), true).forEach(function (key) {
72
+ _defineProperty(target, key, source[key]);
73
+ });
74
+ } else if (Object.getOwnPropertyDescriptors) {
75
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
76
+ } else {
77
+ ownKeys(Object(source)).forEach(function (key) {
78
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
79
+ });
80
+ }
81
+ }
82
+
83
+ return target;
84
+ }
85
+
86
+ function _typeof(obj) {
87
+ "@babel/helpers - typeof";
88
+
89
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
90
+ _typeof = function (obj) {
91
+ return typeof obj;
92
+ };
93
+ } else {
94
+ _typeof = function (obj) {
95
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
96
+ };
97
+ }
98
+
99
+ return _typeof(obj);
100
+ }
101
+
102
+ function _defineProperty(obj, key, value) {
103
+ if (key in obj) {
104
+ Object.defineProperty(obj, key, {
105
+ value: value,
106
+ enumerable: true,
107
+ configurable: true,
108
+ writable: true
109
+ });
110
+ } else {
111
+ obj[key] = value;
112
+ }
113
+
114
+ return obj;
115
+ }
116
+
117
+ function _setPrototypeOf(o, p) {
118
+ _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
119
+ o.__proto__ = p;
120
+ return o;
121
+ };
122
+
123
+ return _setPrototypeOf(o, p);
124
+ }
125
+
126
+ function _isNativeReflectConstruct() {
127
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
128
+ if (Reflect.construct.sham) return false;
129
+ if (typeof Proxy === "function") return true;
130
+
131
+ try {
132
+ Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
133
+ return true;
134
+ } catch (e) {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ function _construct(Parent, args, Class) {
140
+ if (_isNativeReflectConstruct()) {
141
+ _construct = Reflect.construct;
142
+ } else {
143
+ _construct = function _construct(Parent, args, Class) {
144
+ var a = [null];
145
+ a.push.apply(a, args);
146
+ var Constructor = Function.bind.apply(Parent, a);
147
+ var instance = new Constructor();
148
+ if (Class) _setPrototypeOf(instance, Class.prototype);
149
+ return instance;
150
+ };
151
+ }
152
+
153
+ return _construct.apply(null, arguments);
154
+ }
155
+
156
+ function _slicedToArray(arr, i) {
157
+ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
158
+ }
159
+
160
+ function _toConsumableArray(arr) {
161
+ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
162
+ }
163
+
164
+ function _arrayWithoutHoles(arr) {
165
+ if (Array.isArray(arr)) return _arrayLikeToArray(arr);
166
+ }
167
+
168
+ function _arrayWithHoles(arr) {
169
+ if (Array.isArray(arr)) return arr;
170
+ }
171
+
172
+ function _iterableToArray(iter) {
173
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
174
+ }
175
+
176
+ function _iterableToArrayLimit(arr, i) {
177
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
178
+
179
+ if (_i == null) return;
180
+ var _arr = [];
181
+ var _n = true;
182
+ var _d = false;
183
+
184
+ var _s, _e;
185
+
186
+ try {
187
+ for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
188
+ _arr.push(_s.value);
189
+
190
+ if (i && _arr.length === i) break;
191
+ }
192
+ } catch (err) {
193
+ _d = true;
194
+ _e = err;
195
+ } finally {
196
+ try {
197
+ if (!_n && _i["return"] != null) _i["return"]();
198
+ } finally {
199
+ if (_d) throw _e;
200
+ }
201
+ }
202
+
203
+ return _arr;
204
+ }
205
+
206
+ function _unsupportedIterableToArray(o, minLen) {
207
+ if (!o) return;
208
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
209
+ var n = Object.prototype.toString.call(o).slice(8, -1);
210
+ if (n === "Object" && o.constructor) n = o.constructor.name;
211
+ if (n === "Map" || n === "Set") return Array.from(o);
212
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
213
+ }
214
+
215
+ function _arrayLikeToArray(arr, len) {
216
+ if (len == null || len > arr.length) len = arr.length;
217
+
218
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
219
+
220
+ return arr2;
221
+ }
222
+
223
+ function _nonIterableSpread() {
224
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
225
+ }
226
+
227
+ function _nonIterableRest() {
228
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
229
+ }
230
+
231
+ var autoColorScale = scaleOrdinal(schemePaired); // Autoset attribute colorField by colorByAccessor property
232
+ // If an object has already a color, don't set it
233
+ // Objects can be nodes or links
234
+
235
+ function autoColorObjects(objects, colorByAccessor, colorField) {
236
+ if (!colorByAccessor || typeof colorField !== 'string') return;
237
+ objects.filter(function (obj) {
238
+ return !obj[colorField];
239
+ }).forEach(function (obj) {
240
+ obj[colorField] = autoColorScale(colorByAccessor(obj));
241
+ });
242
+ }
243
+
244
+ function getDagDepths (_ref, idAccessor) {
245
+ var nodes = _ref.nodes,
246
+ links = _ref.links;
247
+
248
+ var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
249
+ _ref2$nodeFilter = _ref2.nodeFilter,
250
+ nodeFilter = _ref2$nodeFilter === void 0 ? function () {
251
+ return true;
252
+ } : _ref2$nodeFilter,
253
+ _ref2$onLoopError = _ref2.onLoopError,
254
+ onLoopError = _ref2$onLoopError === void 0 ? function (loopIds) {
255
+ throw "Invalid DAG structure! Found cycle in node path: ".concat(loopIds.join(' -> '), ".");
256
+ } : _ref2$onLoopError;
257
+
258
+ // linked graph
259
+ var graph = {};
260
+ nodes.forEach(function (node) {
261
+ return graph[idAccessor(node)] = {
262
+ data: node,
263
+ out: [],
264
+ depth: -1,
265
+ skip: !nodeFilter(node)
266
+ };
267
+ });
268
+ links.forEach(function (_ref3) {
269
+ var source = _ref3.source,
270
+ target = _ref3.target;
271
+ var sourceId = getNodeId(source);
272
+ var targetId = getNodeId(target);
273
+ if (!graph.hasOwnProperty(sourceId)) throw "Missing source node with id: ".concat(sourceId);
274
+ if (!graph.hasOwnProperty(targetId)) throw "Missing target node with id: ".concat(targetId);
275
+ var sourceNode = graph[sourceId];
276
+ var targetNode = graph[targetId];
277
+ sourceNode.out.push(targetNode);
278
+
279
+ function getNodeId(node) {
280
+ return _typeof(node) === 'object' ? idAccessor(node) : node;
281
+ }
282
+ });
283
+ var foundLoops = [];
284
+ traverse(Object.values(graph));
285
+ var nodeDepths = Object.assign.apply(Object, [{}].concat(_toConsumableArray(Object.entries(graph).filter(function (_ref4) {
286
+ var _ref5 = _slicedToArray(_ref4, 2),
287
+ node = _ref5[1];
288
+
289
+ return !node.skip;
290
+ }).map(function (_ref6) {
291
+ var _ref7 = _slicedToArray(_ref6, 2),
292
+ id = _ref7[0],
293
+ node = _ref7[1];
294
+
295
+ return _defineProperty({}, id, node.depth);
296
+ }))));
297
+ return nodeDepths;
298
+
299
+ function traverse(nodes) {
300
+ var nodeStack = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
301
+ var currentDepth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
302
+
303
+ for (var i = 0, l = nodes.length; i < l; i++) {
304
+ var node = nodes[i];
305
+
306
+ if (nodeStack.indexOf(node) !== -1) {
307
+ var _ret = function () {
308
+ var loop = [].concat(_toConsumableArray(nodeStack.slice(nodeStack.indexOf(node))), [node]).map(function (d) {
309
+ return idAccessor(d.data);
310
+ });
311
+
312
+ if (!foundLoops.some(function (foundLoop) {
313
+ return foundLoop.length === loop.length && foundLoop.every(function (id, idx) {
314
+ return id === loop[idx];
315
+ });
316
+ })) {
317
+ foundLoops.push(loop);
318
+ onLoopError(loop);
319
+ }
320
+
321
+ return "continue";
322
+ }();
323
+
324
+ if (_ret === "continue") continue;
325
+ }
326
+
327
+ if (currentDepth > node.depth) {
328
+ // Don't unnecessarily revisit chunks of the graph
329
+ node.depth = currentDepth;
330
+ traverse(node.out, [].concat(_toConsumableArray(nodeStack), [node]), currentDepth + (node.skip ? 0 : 1));
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ var DAG_LEVEL_NODE_RATIO = 2; // whenever styling props are changed that require a canvas redraw
337
+
338
+ var notifyRedraw = function notifyRedraw(_, state) {
339
+ return state.onNeedsRedraw && state.onNeedsRedraw();
340
+ };
341
+
342
+ var CanvasForceGraph = Kapsule({
343
+ props: {
344
+ graphData: {
345
+ "default": {
346
+ nodes: [],
347
+ links: []
348
+ },
349
+ onChange: function onChange(_, state) {
350
+ state.engineRunning = false;
351
+ } // Pause simulation
352
+
353
+ },
354
+ dagMode: {
355
+ onChange: function onChange(dagMode, state) {
356
+ // td, bu, lr, rl, radialin, radialout
357
+ !dagMode && (state.graphData.nodes || []).forEach(function (n) {
358
+ return n.fx = n.fy = undefined;
359
+ }); // unfix nodes when disabling dag mode
360
+ }
361
+ },
362
+ dagLevelDistance: {},
363
+ dagNodeFilter: {
364
+ "default": function _default(node) {
365
+ return true;
366
+ }
367
+ },
368
+ onDagError: {
369
+ triggerUpdate: false
370
+ },
371
+ nodeRelSize: {
372
+ "default": 4,
373
+ triggerUpdate: false,
374
+ onChange: notifyRedraw
375
+ },
376
+ // area per val unit
377
+ nodeId: {
378
+ "default": 'id'
379
+ },
380
+ nodeVal: {
381
+ "default": 'val',
382
+ triggerUpdate: false,
383
+ onChange: notifyRedraw
384
+ },
385
+ nodeColor: {
386
+ "default": 'color',
387
+ triggerUpdate: false,
388
+ onChange: notifyRedraw
389
+ },
390
+ nodeAutoColorBy: {},
391
+ nodeCanvasObject: {
392
+ triggerUpdate: false,
393
+ onChange: notifyRedraw
394
+ },
395
+ nodeCanvasObjectMode: {
396
+ "default": function _default() {
397
+ return 'replace';
398
+ },
399
+ triggerUpdate: false,
400
+ onChange: notifyRedraw
401
+ },
402
+ nodeVisibility: {
403
+ "default": true,
404
+ triggerUpdate: false,
405
+ onChange: notifyRedraw
406
+ },
407
+ linkSource: {
408
+ "default": 'source'
409
+ },
410
+ linkTarget: {
411
+ "default": 'target'
412
+ },
413
+ linkVisibility: {
414
+ "default": true,
415
+ triggerUpdate: false,
416
+ onChange: notifyRedraw
417
+ },
418
+ linkColor: {
419
+ "default": 'color',
420
+ triggerUpdate: false,
421
+ onChange: notifyRedraw
422
+ },
423
+ linkAutoColorBy: {},
424
+ linkLineDash: {
425
+ triggerUpdate: false,
426
+ onChange: notifyRedraw
427
+ },
428
+ linkWidth: {
429
+ "default": 1,
430
+ triggerUpdate: false,
431
+ onChange: notifyRedraw
432
+ },
433
+ linkCurvature: {
434
+ "default": 0,
435
+ triggerUpdate: false,
436
+ onChange: notifyRedraw
437
+ },
438
+ linkCanvasObject: {
439
+ triggerUpdate: false,
440
+ onChange: notifyRedraw
441
+ },
442
+ linkCanvasObjectMode: {
443
+ "default": function _default() {
444
+ return 'replace';
445
+ },
446
+ triggerUpdate: false,
447
+ onChange: notifyRedraw
448
+ },
449
+ linkDirectionalArrowLength: {
450
+ "default": 0,
451
+ triggerUpdate: false,
452
+ onChange: notifyRedraw
453
+ },
454
+ linkDirectionalArrowColor: {
455
+ triggerUpdate: false,
456
+ onChange: notifyRedraw
457
+ },
458
+ linkDirectionalArrowRelPos: {
459
+ "default": 0.5,
460
+ triggerUpdate: false,
461
+ onChange: notifyRedraw
462
+ },
463
+ // value between 0<>1 indicating the relative pos along the (exposed) line
464
+ linkDirectionalParticles: {
465
+ "default": 0
466
+ },
467
+ // animate photons travelling in the link direction
468
+ linkDirectionalParticleSpeed: {
469
+ "default": 0.01,
470
+ triggerUpdate: false
471
+ },
472
+ // in link length ratio per frame
473
+ linkDirectionalParticleWidth: {
474
+ "default": 4,
475
+ triggerUpdate: false
476
+ },
477
+ linkDirectionalParticleColor: {
478
+ triggerUpdate: false
479
+ },
480
+ globalScale: {
481
+ "default": 1,
482
+ triggerUpdate: false
483
+ },
484
+ d3AlphaMin: {
485
+ "default": 0,
486
+ triggerUpdate: false
487
+ },
488
+ d3AlphaDecay: {
489
+ "default": 0.0228,
490
+ triggerUpdate: false,
491
+ onChange: function onChange(alphaDecay, state) {
492
+ state.forceLayout.alphaDecay(alphaDecay);
493
+ }
494
+ },
495
+ d3AlphaTarget: {
496
+ "default": 0,
497
+ triggerUpdate: false,
498
+ onChange: function onChange(alphaTarget, state) {
499
+ state.forceLayout.alphaTarget(alphaTarget);
500
+ }
501
+ },
502
+ d3VelocityDecay: {
503
+ "default": 0.4,
504
+ triggerUpdate: false,
505
+ onChange: function onChange(velocityDecay, state) {
506
+ state.forceLayout.velocityDecay(velocityDecay);
507
+ }
508
+ },
509
+ warmupTicks: {
510
+ "default": 0,
511
+ triggerUpdate: false
512
+ },
513
+ // how many times to tick the force engine at init before starting to render
514
+ cooldownTicks: {
515
+ "default": Infinity,
516
+ triggerUpdate: false
517
+ },
518
+ cooldownTime: {
519
+ "default": 15000,
520
+ triggerUpdate: false
521
+ },
522
+ // ms
523
+ onUpdate: {
524
+ "default": function _default() {},
525
+ triggerUpdate: false
526
+ },
527
+ onFinishUpdate: {
528
+ "default": function _default() {},
529
+ triggerUpdate: false
530
+ },
531
+ onEngineTick: {
532
+ "default": function _default() {},
533
+ triggerUpdate: false
534
+ },
535
+ onEngineStop: {
536
+ "default": function _default() {},
537
+ triggerUpdate: false
538
+ },
539
+ onNeedsRedraw: {
540
+ triggerUpdate: false
541
+ },
542
+ isShadow: {
543
+ "default": false,
544
+ triggerUpdate: false
545
+ }
546
+ },
547
+ methods: {
548
+ // Expose d3 forces for external manipulation
549
+ d3Force: function d3Force(state, forceName, forceFn) {
550
+ if (forceFn === undefined) {
551
+ return state.forceLayout.force(forceName); // Force getter
552
+ }
553
+
554
+ state.forceLayout.force(forceName, forceFn); // Force setter
555
+
556
+ return this;
557
+ },
558
+ d3ReheatSimulation: function d3ReheatSimulation(state) {
559
+ state.forceLayout.alpha(1);
560
+ this.resetCountdown();
561
+ return this;
562
+ },
563
+ // reset cooldown state
564
+ resetCountdown: function resetCountdown(state) {
565
+ state.cntTicks = 0;
566
+ state.startTickTime = new Date();
567
+ state.engineRunning = true;
568
+ return this;
569
+ },
570
+ isEngineRunning: function isEngineRunning(state) {
571
+ return !!state.engineRunning;
572
+ },
573
+ tickFrame: function tickFrame(state) {
574
+ !state.isShadow && layoutTick();
575
+ paintLinks();
576
+ !state.isShadow && paintArrows();
577
+ !state.isShadow && paintPhotons();
578
+ paintNodes();
579
+ return this; //
580
+
581
+ function layoutTick() {
582
+ if (state.engineRunning) {
583
+ if (++state.cntTicks > state.cooldownTicks || new Date() - state.startTickTime > state.cooldownTime || state.d3AlphaMin > 0 && state.forceLayout.alpha() < state.d3AlphaMin) {
584
+ state.engineRunning = false; // Stop ticking graph
585
+
586
+ state.onEngineStop();
587
+ } else {
588
+ state.forceLayout.tick(); // Tick it
589
+
590
+ state.onEngineTick();
591
+ }
592
+ }
593
+ }
594
+
595
+ function paintNodes() {
596
+ var getVisibility = accessorFn(state.nodeVisibility);
597
+ var getVal = accessorFn(state.nodeVal);
598
+ var getColor = accessorFn(state.nodeColor);
599
+ var getNodeCanvasObjectMode = accessorFn(state.nodeCanvasObjectMode);
600
+ var ctx = state.ctx; // Draw wider nodes by 1px on shadow canvas for more precise hovering (due to boundary anti-aliasing)
601
+
602
+ var padAmount = state.isShadow / state.globalScale;
603
+ var visibleNodes = state.graphData.nodes.filter(getVisibility);
604
+ ctx.save();
605
+ visibleNodes.forEach(function (node) {
606
+ var nodeCanvasObjectMode = getNodeCanvasObjectMode(node);
607
+
608
+ if (state.nodeCanvasObject && (nodeCanvasObjectMode === 'before' || nodeCanvasObjectMode === 'replace')) {
609
+ // Custom node before/replace paint
610
+ state.nodeCanvasObject(node, ctx, state.globalScale);
611
+
612
+ if (nodeCanvasObjectMode === 'replace') {
613
+ ctx.restore();
614
+ return;
615
+ }
616
+ } // Draw wider nodes by 1px on shadow canvas for more precise hovering (due to boundary anti-aliasing)
617
+
618
+
619
+ var r = Math.sqrt(Math.max(0, getVal(node) || 1)) * state.nodeRelSize + padAmount;
620
+ ctx.beginPath();
621
+ ctx.arc(node.x, node.y, r, 0, 2 * Math.PI, false);
622
+ ctx.fillStyle = getColor(node) || 'rgba(31, 120, 180, 0.92)';
623
+ ctx.fill();
624
+
625
+ if (state.nodeCanvasObject && nodeCanvasObjectMode === 'after') {
626
+ // Custom node after paint
627
+ state.nodeCanvasObject(node, state.ctx, state.globalScale);
628
+ }
629
+ });
630
+ ctx.restore();
631
+ }
632
+
633
+ function paintLinks() {
634
+ var getVisibility = accessorFn(state.linkVisibility);
635
+ var getColor = accessorFn(state.linkColor);
636
+ var getWidth = accessorFn(state.linkWidth);
637
+ var getLineDash = accessorFn(state.linkLineDash);
638
+ var getCurvature = accessorFn(state.linkCurvature);
639
+ var getLinkCanvasObjectMode = accessorFn(state.linkCanvasObjectMode);
640
+ var ctx = state.ctx; // Draw wider lines by 2px on shadow canvas for more precise hovering (due to boundary anti-aliasing)
641
+
642
+ var padAmount = state.isShadow * 2;
643
+ var visibleLinks = state.graphData.links.filter(getVisibility);
644
+ visibleLinks.forEach(calcLinkControlPoints); // calculate curvature control points for all visible links
645
+
646
+ var beforeCustomLinks = [],
647
+ afterCustomLinks = [],
648
+ defaultPaintLinks = visibleLinks;
649
+
650
+ if (state.linkCanvasObject) {
651
+ var replaceCustomLinks = [],
652
+ otherCustomLinks = [];
653
+ visibleLinks.forEach(function (d) {
654
+ return ({
655
+ before: beforeCustomLinks,
656
+ after: afterCustomLinks,
657
+ replace: replaceCustomLinks
658
+ }[getLinkCanvasObjectMode(d)] || otherCustomLinks).push(d);
659
+ });
660
+ defaultPaintLinks = [].concat(_toConsumableArray(beforeCustomLinks), afterCustomLinks, otherCustomLinks);
661
+ beforeCustomLinks = beforeCustomLinks.concat(replaceCustomLinks);
662
+ } // Custom link before paints
663
+
664
+
665
+ ctx.save();
666
+ beforeCustomLinks.forEach(function (link) {
667
+ return state.linkCanvasObject(link, ctx, state.globalScale);
668
+ });
669
+ ctx.restore(); // Bundle strokes per unique color/width/dash for performance optimization
670
+
671
+ var linksPerColor = indexBy(defaultPaintLinks, [getColor, getWidth, getLineDash]);
672
+ ctx.save();
673
+ Object.entries(linksPerColor).forEach(function (_ref) {
674
+ var _ref2 = _slicedToArray(_ref, 2),
675
+ color = _ref2[0],
676
+ linksPerWidth = _ref2[1];
677
+
678
+ var lineColor = !color || color === 'undefined' ? 'rgba(0,0,0,0.15)' : color;
679
+ Object.entries(linksPerWidth).forEach(function (_ref3) {
680
+ var _ref4 = _slicedToArray(_ref3, 2),
681
+ width = _ref4[0],
682
+ linesPerLineDash = _ref4[1];
683
+
684
+ var lineWidth = (width || 1) / state.globalScale + padAmount;
685
+ Object.entries(linesPerLineDash).forEach(function (_ref5) {
686
+ var _ref6 = _slicedToArray(_ref5, 2);
687
+ _ref6[0];
688
+ var links = _ref6[1];
689
+
690
+ var lineDashSegments = getLineDash(links[0]);
691
+ ctx.beginPath();
692
+ links.forEach(function (link) {
693
+ var start = link.source;
694
+ var end = link.target;
695
+ if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
696
+
697
+ ctx.moveTo(start.x, start.y);
698
+ var controlPoints = link.__controlPoints;
699
+
700
+ if (!controlPoints) {
701
+ // Straight line
702
+ ctx.lineTo(end.x, end.y);
703
+ } else {
704
+ // Use quadratic curves for regular lines and bezier for loops
705
+ ctx[controlPoints.length === 2 ? 'quadraticCurveTo' : 'bezierCurveTo'].apply(ctx, _toConsumableArray(controlPoints).concat([end.x, end.y]));
706
+ }
707
+ });
708
+ ctx.strokeStyle = lineColor;
709
+ ctx.lineWidth = lineWidth;
710
+ ctx.setLineDash(lineDashSegments || []);
711
+ ctx.stroke();
712
+ });
713
+ });
714
+ });
715
+ ctx.restore(); // Custom link after paints
716
+
717
+ ctx.save();
718
+ afterCustomLinks.forEach(function (link) {
719
+ return state.linkCanvasObject(link, ctx, state.globalScale);
720
+ });
721
+ ctx.restore(); //
722
+
723
+ function calcLinkControlPoints(link) {
724
+ var curvature = getCurvature(link);
725
+
726
+ if (!curvature) {
727
+ // straight line
728
+ link.__controlPoints = null;
729
+ return;
730
+ }
731
+
732
+ var start = link.source;
733
+ var end = link.target;
734
+ if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
735
+
736
+ var l = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)); // line length
737
+
738
+ if (l > 0) {
739
+ var a = Math.atan2(end.y - start.y, end.x - start.x); // line angle
740
+
741
+ var d = l * curvature; // control point distance
742
+
743
+ var cp = {
744
+ // control point
745
+ x: (start.x + end.x) / 2 + d * Math.cos(a - Math.PI / 2),
746
+ y: (start.y + end.y) / 2 + d * Math.sin(a - Math.PI / 2)
747
+ };
748
+ link.__controlPoints = [cp.x, cp.y];
749
+ } else {
750
+ // Same point, draw a loop
751
+ var _d = curvature * 70;
752
+
753
+ link.__controlPoints = [end.x, end.y - _d, end.x + _d, end.y];
754
+ }
755
+ }
756
+ }
757
+
758
+ function paintArrows() {
759
+ var ARROW_WH_RATIO = 1.6;
760
+ var ARROW_VLEN_RATIO = 0.2;
761
+ var getLength = accessorFn(state.linkDirectionalArrowLength);
762
+ var getRelPos = accessorFn(state.linkDirectionalArrowRelPos);
763
+ var getVisibility = accessorFn(state.linkVisibility);
764
+ var getColor = accessorFn(state.linkDirectionalArrowColor || state.linkColor);
765
+ var getNodeVal = accessorFn(state.nodeVal);
766
+ var ctx = state.ctx;
767
+ ctx.save();
768
+ state.graphData.links.filter(getVisibility).forEach(function (link) {
769
+ var arrowLength = getLength(link);
770
+ if (!arrowLength || arrowLength < 0) return;
771
+ var start = link.source;
772
+ var end = link.target;
773
+ if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
774
+
775
+ var startR = Math.sqrt(Math.max(0, getNodeVal(start) || 1)) * state.nodeRelSize;
776
+ var endR = Math.sqrt(Math.max(0, getNodeVal(end) || 1)) * state.nodeRelSize;
777
+ var arrowRelPos = Math.min(1, Math.max(0, getRelPos(link)));
778
+ var arrowColor = getColor(link) || 'rgba(0,0,0,0.28)';
779
+ var arrowHalfWidth = arrowLength / ARROW_WH_RATIO / 2; // Construct bezier for curved lines
780
+
781
+ var bzLine = link.__controlPoints && _construct(Bezier, [start.x, start.y].concat(_toConsumableArray(link.__controlPoints), [end.x, end.y]));
782
+
783
+ var getCoordsAlongLine = bzLine ? function (t) {
784
+ return bzLine.get(t);
785
+ } // get position along bezier line
786
+ : function (t) {
787
+ return {
788
+ // straight line: interpolate linearly
789
+ x: start.x + (end.x - start.x) * t || 0,
790
+ y: start.y + (end.y - start.y) * t || 0
791
+ };
792
+ };
793
+ var lineLen = bzLine ? bzLine.length() : Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
794
+ var posAlongLine = startR + arrowLength + (lineLen - startR - endR - arrowLength) * arrowRelPos;
795
+ var arrowHead = getCoordsAlongLine(posAlongLine / lineLen);
796
+ var arrowTail = getCoordsAlongLine((posAlongLine - arrowLength) / lineLen);
797
+ var arrowTailVertex = getCoordsAlongLine((posAlongLine - arrowLength * (1 - ARROW_VLEN_RATIO)) / lineLen);
798
+ var arrowTailAngle = Math.atan2(arrowHead.y - arrowTail.y, arrowHead.x - arrowTail.x) - Math.PI / 2;
799
+ ctx.beginPath();
800
+ ctx.moveTo(arrowHead.x, arrowHead.y);
801
+ ctx.lineTo(arrowTail.x + arrowHalfWidth * Math.cos(arrowTailAngle), arrowTail.y + arrowHalfWidth * Math.sin(arrowTailAngle));
802
+ ctx.lineTo(arrowTailVertex.x, arrowTailVertex.y);
803
+ ctx.lineTo(arrowTail.x - arrowHalfWidth * Math.cos(arrowTailAngle), arrowTail.y - arrowHalfWidth * Math.sin(arrowTailAngle));
804
+ ctx.fillStyle = arrowColor;
805
+ ctx.fill();
806
+ });
807
+ ctx.restore();
808
+ }
809
+
810
+ function paintPhotons() {
811
+ var getNumPhotons = accessorFn(state.linkDirectionalParticles);
812
+ var getSpeed = accessorFn(state.linkDirectionalParticleSpeed);
813
+ var getDiameter = accessorFn(state.linkDirectionalParticleWidth);
814
+ var getVisibility = accessorFn(state.linkVisibility);
815
+ var getColor = accessorFn(state.linkDirectionalParticleColor || state.linkColor);
816
+ var ctx = state.ctx;
817
+ ctx.save();
818
+ state.graphData.links.filter(getVisibility).forEach(function (link) {
819
+ var numCyclePhotons = getNumPhotons(link);
820
+ if (!link.hasOwnProperty('__photons') || !link.__photons.length) return;
821
+ var start = link.source;
822
+ var end = link.target;
823
+ if (!start || !end || !start.hasOwnProperty('x') || !end.hasOwnProperty('x')) return; // skip invalid link
824
+
825
+ var particleSpeed = getSpeed(link);
826
+ var photons = link.__photons || [];
827
+ var photonR = Math.max(0, getDiameter(link) / 2) / Math.sqrt(state.globalScale);
828
+ var photonColor = getColor(link) || 'rgba(0,0,0,0.28)';
829
+ ctx.fillStyle = photonColor; // Construct bezier for curved lines
830
+
831
+ var bzLine = link.__controlPoints ? _construct(Bezier, [start.x, start.y].concat(_toConsumableArray(link.__controlPoints), [end.x, end.y])) : null;
832
+ var cyclePhotonIdx = 0;
833
+ var needsCleanup = false; // whether some photons need to be removed from list
834
+
835
+ photons.forEach(function (photon) {
836
+ var singleHop = !!photon.__singleHop;
837
+
838
+ if (!photon.hasOwnProperty('__progressRatio')) {
839
+ photon.__progressRatio = singleHop ? 0 : cyclePhotonIdx / numCyclePhotons;
840
+ }
841
+
842
+ !singleHop && cyclePhotonIdx++; // increase regular photon index
843
+
844
+ photon.__progressRatio += particleSpeed;
845
+
846
+ if (photon.__progressRatio >= 1) {
847
+ if (!singleHop) {
848
+ photon.__progressRatio = photon.__progressRatio % 1;
849
+ } else {
850
+ needsCleanup = true;
851
+ return;
852
+ }
853
+ }
854
+
855
+ var photonPosRatio = photon.__progressRatio;
856
+ var coords = bzLine ? bzLine.get(photonPosRatio) // get position along bezier line
857
+ : {
858
+ // straight line: interpolate linearly
859
+ x: start.x + (end.x - start.x) * photonPosRatio || 0,
860
+ y: start.y + (end.y - start.y) * photonPosRatio || 0
861
+ };
862
+ ctx.beginPath();
863
+ ctx.arc(coords.x, coords.y, photonR, 0, 2 * Math.PI, false);
864
+ ctx.fill();
865
+ });
866
+
867
+ if (needsCleanup) {
868
+ // remove expired single hop photons
869
+ link.__photons = link.__photons.filter(function (photon) {
870
+ return !photon.__singleHop || photon.__progressRatio <= 1;
871
+ });
872
+ }
873
+ });
874
+ ctx.restore();
875
+ }
876
+ },
877
+ emitParticle: function emitParticle(state, link) {
878
+ if (link) {
879
+ !link.__photons && (link.__photons = []);
880
+
881
+ link.__photons.push({
882
+ __singleHop: true
883
+ }); // add a single hop particle
884
+
885
+ }
886
+
887
+ return this;
888
+ }
889
+ },
890
+ stateInit: function stateInit() {
891
+ return {
892
+ forceLayout: forceSimulation().force('link', forceLink()).force('charge', forceManyBody()).force('center', forceCenter()).force('dagRadial', null).stop(),
893
+ engineRunning: false
894
+ };
895
+ },
896
+ init: function init(canvasCtx, state) {
897
+ // Main canvas object to manipulate
898
+ state.ctx = canvasCtx;
899
+ },
900
+ update: function update(state) {
901
+ state.engineRunning = false; // Pause simulation
902
+
903
+ state.onUpdate();
904
+
905
+ if (state.nodeAutoColorBy !== null) {
906
+ // Auto add color to uncolored nodes
907
+ autoColorObjects(state.graphData.nodes, accessorFn(state.nodeAutoColorBy), state.nodeColor);
908
+ }
909
+
910
+ if (state.linkAutoColorBy !== null) {
911
+ // Auto add color to uncolored links
912
+ autoColorObjects(state.graphData.links, accessorFn(state.linkAutoColorBy), state.linkColor);
913
+ } // parse links
914
+
915
+
916
+ state.graphData.links.forEach(function (link) {
917
+ link.source = link[state.linkSource];
918
+ link.target = link[state.linkTarget];
919
+ });
920
+
921
+ if (!state.isShadow) {
922
+ // Add photon particles
923
+ var linkParticlesAccessor = accessorFn(state.linkDirectionalParticles);
924
+ state.graphData.links.forEach(function (link) {
925
+ var numPhotons = Math.round(Math.abs(linkParticlesAccessor(link)));
926
+
927
+ if (numPhotons) {
928
+ link.__photons = _toConsumableArray(Array(numPhotons)).map(function () {
929
+ return {};
930
+ });
931
+ } else {
932
+ delete link.__photons;
933
+ }
934
+ });
935
+ } // Feed data to force-directed layout
936
+
937
+
938
+ state.forceLayout.stop().alpha(1) // re-heat the simulation
939
+ .nodes(state.graphData.nodes); // add links (if link force is still active)
940
+
941
+ var linkForce = state.forceLayout.force('link');
942
+
943
+ if (linkForce) {
944
+ linkForce.id(function (d) {
945
+ return d[state.nodeId];
946
+ }).links(state.graphData.links);
947
+ } // setup dag force constraints
948
+
949
+
950
+ var nodeDepths = state.dagMode && getDagDepths(state.graphData, function (node) {
951
+ return node[state.nodeId];
952
+ }, {
953
+ nodeFilter: state.dagNodeFilter,
954
+ onLoopError: state.onDagError || undefined
955
+ });
956
+ var maxDepth = Math.max.apply(Math, _toConsumableArray(Object.values(nodeDepths || [])));
957
+ var dagLevelDistance = state.dagLevelDistance || state.graphData.nodes.length / (maxDepth || 1) * DAG_LEVEL_NODE_RATIO * (['radialin', 'radialout'].indexOf(state.dagMode) !== -1 ? 0.7 : 1); // Fix nodes to x,y for dag mode
958
+
959
+ if (state.dagMode) {
960
+ var getFFn = function getFFn(fix, invert) {
961
+ return function (node) {
962
+ return !fix ? undefined : (nodeDepths[node[state.nodeId]] - maxDepth / 2) * dagLevelDistance * (invert ? -1 : 1);
963
+ };
964
+ };
965
+
966
+ var fxFn = getFFn(['lr', 'rl'].indexOf(state.dagMode) !== -1, state.dagMode === 'rl');
967
+ var fyFn = getFFn(['td', 'bu'].indexOf(state.dagMode) !== -1, state.dagMode === 'bu');
968
+ state.graphData.nodes.filter(state.dagNodeFilter).forEach(function (node) {
969
+ node.fx = fxFn(node);
970
+ node.fy = fyFn(node);
971
+ });
972
+ } // Use radial force for radial dags
973
+
974
+
975
+ state.forceLayout.force('dagRadial', ['radialin', 'radialout'].indexOf(state.dagMode) !== -1 ? forceRadial(function (node) {
976
+ var nodeDepth = nodeDepths[node[state.nodeId]] || -1;
977
+ return (state.dagMode === 'radialin' ? maxDepth - nodeDepth : nodeDepth) * dagLevelDistance;
978
+ }).strength(function (node) {
979
+ return state.dagNodeFilter(node) ? 1 : 0;
980
+ }) : null);
981
+
982
+ for (var i = 0; i < state.warmupTicks && !(state.d3AlphaMin > 0 && state.forceLayout.alpha() < state.d3AlphaMin); i++) {
983
+ state.forceLayout.tick();
984
+ } // Initial ticks before starting to render
985
+
986
+
987
+ this.resetCountdown();
988
+ state.onFinishUpdate();
989
+ }
990
+ });
991
+
992
+ function linkKapsule (kapsulePropNames, kapsuleType) {
993
+ var propNames = kapsulePropNames instanceof Array ? kapsulePropNames : [kapsulePropNames];
994
+ var dummyK = new kapsuleType(); // To extract defaults
995
+
996
+ return {
997
+ linkProp: function linkProp(prop) {
998
+ // link property config
999
+ return {
1000
+ "default": dummyK[prop](),
1001
+ onChange: function onChange(v, state) {
1002
+ propNames.forEach(function (propName) {
1003
+ return state[propName][prop](v);
1004
+ });
1005
+ },
1006
+ triggerUpdate: false
1007
+ };
1008
+ },
1009
+ linkMethod: function linkMethod(method) {
1010
+ // link method pass-through
1011
+ return function (state) {
1012
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
1013
+ args[_key - 1] = arguments[_key];
1014
+ }
1015
+
1016
+ var returnVals = [];
1017
+ propNames.forEach(function (propName) {
1018
+ var kapsuleInstance = state[propName];
1019
+ var returnVal = kapsuleInstance[method].apply(kapsuleInstance, args);
1020
+
1021
+ if (returnVal !== kapsuleInstance) {
1022
+ returnVals.push(returnVal);
1023
+ }
1024
+ });
1025
+ return returnVals.length ? returnVals[0] : this; // chain based on the parent object, not the inner kapsule
1026
+ };
1027
+ }
1028
+ };
1029
+ }
1030
+
1031
+ var HOVER_CANVAS_THROTTLE_DELAY = 800; // ms to throttle shadow canvas updates for perf improvement
1032
+
1033
+ var ZOOM2NODES_FACTOR = 4; // Expose config from forceGraph
1034
+
1035
+ var bindFG = linkKapsule('forceGraph', CanvasForceGraph);
1036
+ var bindBoth = linkKapsule(['forceGraph', 'shadowGraph'], CanvasForceGraph);
1037
+ 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) {
1038
+ return _defineProperty({}, p, bindFG.linkProp(p));
1039
+ })).concat(_toConsumableArray(['nodeRelSize', 'nodeId', 'nodeVal', 'nodeVisibility', 'linkSource', 'linkTarget', 'linkVisibility', 'linkCurvature'].map(function (p) {
1040
+ return _defineProperty({}, p, bindBoth.linkProp(p));
1041
+ }))));
1042
+ var linkedMethods = Object.assign.apply(Object, _toConsumableArray(['d3Force', 'd3ReheatSimulation', 'emitParticle'].map(function (p) {
1043
+ return _defineProperty({}, p, bindFG.linkMethod(p));
1044
+ })));
1045
+
1046
+ function adjustCanvasSize(state) {
1047
+ if (state.canvas) {
1048
+ var curWidth = state.canvas.width;
1049
+ var curHeight = state.canvas.height;
1050
+
1051
+ if (curWidth === 300 && curHeight === 150) {
1052
+ // Default canvas dimensions
1053
+ curWidth = curHeight = 0;
1054
+ }
1055
+
1056
+ var pxScale = window.devicePixelRatio; // 2 on retina displays
1057
+
1058
+ curWidth /= pxScale;
1059
+ curHeight /= pxScale; // Resize canvases
1060
+
1061
+ [state.canvas, state.shadowCanvas].forEach(function (canvas) {
1062
+ // Element size
1063
+ canvas.style.width = "".concat(state.width, "px");
1064
+ canvas.style.height = "".concat(state.height, "px"); // Memory size (scaled to avoid blurriness)
1065
+
1066
+ canvas.width = state.width * pxScale;
1067
+ canvas.height = state.height * pxScale; // Normalize coordinate system to use css pixels (on init only)
1068
+
1069
+ if (!curWidth && !curHeight) {
1070
+ canvas.getContext('2d').scale(pxScale, pxScale);
1071
+ }
1072
+ }); // Relative center panning based on 0,0
1073
+
1074
+ var k = zoomTransform(state.canvas).k;
1075
+ state.zoom.translateBy(state.zoom.__baseElem, (state.width - curWidth) / 2 / k, (state.height - curHeight) / 2 / k);
1076
+ state.needsRedraw = true;
1077
+ }
1078
+ }
1079
+
1080
+ function resetTransform(ctx) {
1081
+ var pxRatio = window.devicePixelRatio;
1082
+ ctx.setTransform(pxRatio, 0, 0, pxRatio, 0, 0);
1083
+ }
1084
+
1085
+ function clearCanvas(ctx, width, height) {
1086
+ ctx.save();
1087
+ resetTransform(ctx); // reset transform
1088
+
1089
+ ctx.clearRect(0, 0, width, height);
1090
+ ctx.restore(); //restore transforms
1091
+ } //
1092
+
1093
+
1094
+ var forceGraph = Kapsule({
1095
+ props: _objectSpread2({
1096
+ width: {
1097
+ "default": window.innerWidth,
1098
+ onChange: function onChange(_, state) {
1099
+ return adjustCanvasSize(state);
1100
+ },
1101
+ triggerUpdate: false
1102
+ },
1103
+ height: {
1104
+ "default": window.innerHeight,
1105
+ onChange: function onChange(_, state) {
1106
+ return adjustCanvasSize(state);
1107
+ },
1108
+ triggerUpdate: false
1109
+ },
1110
+ graphData: {
1111
+ "default": {
1112
+ nodes: [],
1113
+ links: []
1114
+ },
1115
+ onChange: function onChange(d, state) {
1116
+ [{
1117
+ type: 'Node',
1118
+ objs: d.nodes
1119
+ }, {
1120
+ type: 'Link',
1121
+ objs: d.links
1122
+ }].forEach(hexIndex);
1123
+ state.forceGraph.graphData(d);
1124
+ state.shadowGraph.graphData(d);
1125
+
1126
+ function hexIndex(_ref4) {
1127
+ var type = _ref4.type,
1128
+ objs = _ref4.objs;
1129
+ objs.filter(function (d) {
1130
+ if (!d.hasOwnProperty('__indexColor')) return true;
1131
+ var cur = state.colorTracker.lookup(d.__indexColor);
1132
+ return !cur || !cur.hasOwnProperty('d') || cur.d !== d;
1133
+ }).forEach(function (d) {
1134
+ // store object lookup color
1135
+ d.__indexColor = state.colorTracker.register({
1136
+ type: type,
1137
+ d: d
1138
+ });
1139
+ });
1140
+ }
1141
+ },
1142
+ triggerUpdate: false
1143
+ },
1144
+ backgroundColor: {
1145
+ onChange: function onChange(color, state) {
1146
+ state.canvas && color && (state.canvas.style.background = color);
1147
+ },
1148
+ triggerUpdate: false
1149
+ },
1150
+ nodeLabel: {
1151
+ "default": 'name',
1152
+ triggerUpdate: false
1153
+ },
1154
+ nodePointerAreaPaint: {
1155
+ onChange: function onChange(paintFn, state) {
1156
+ state.shadowGraph.nodeCanvasObject(!paintFn ? null : function (node, ctx, globalScale) {
1157
+ return paintFn(node, node.__indexColor, ctx, globalScale);
1158
+ });
1159
+ state.flushShadowCanvas && state.flushShadowCanvas();
1160
+ },
1161
+ triggerUpdate: false
1162
+ },
1163
+ linkPointerAreaPaint: {
1164
+ onChange: function onChange(paintFn, state) {
1165
+ state.shadowGraph.linkCanvasObject(!paintFn ? null : function (link, ctx, globalScale) {
1166
+ return paintFn(link, link.__indexColor, ctx, globalScale);
1167
+ });
1168
+ state.flushShadowCanvas && state.flushShadowCanvas();
1169
+ },
1170
+ triggerUpdate: false
1171
+ },
1172
+ linkLabel: {
1173
+ "default": 'name',
1174
+ triggerUpdate: false
1175
+ },
1176
+ linkHoverPrecision: {
1177
+ "default": 4,
1178
+ triggerUpdate: false
1179
+ },
1180
+ minZoom: {
1181
+ "default": 0.01,
1182
+ onChange: function onChange(minZoom, state) {
1183
+ state.zoom.scaleExtent([minZoom, state.zoom.scaleExtent()[1]]);
1184
+ },
1185
+ triggerUpdate: false
1186
+ },
1187
+ maxZoom: {
1188
+ "default": 1000,
1189
+ onChange: function onChange(maxZoom, state) {
1190
+ state.zoom.scaleExtent([state.zoom.scaleExtent()[0], maxZoom]);
1191
+ },
1192
+ triggerUpdate: false
1193
+ },
1194
+ enableNodeDrag: {
1195
+ "default": true,
1196
+ triggerUpdate: false
1197
+ },
1198
+ enableZoomInteraction: {
1199
+ "default": true,
1200
+ triggerUpdate: false
1201
+ },
1202
+ enablePanInteraction: {
1203
+ "default": true,
1204
+ triggerUpdate: false
1205
+ },
1206
+ enableZoomPanInteraction: {
1207
+ "default": true,
1208
+ triggerUpdate: false
1209
+ },
1210
+ // to be deprecated
1211
+ enablePointerInteraction: {
1212
+ "default": true,
1213
+ onChange: function onChange(_, state) {
1214
+ state.hoverObj = null;
1215
+ },
1216
+ triggerUpdate: false
1217
+ },
1218
+ autoPauseRedraw: {
1219
+ "default": true,
1220
+ triggerUpdate: false
1221
+ },
1222
+ onNodeDrag: {
1223
+ "default": function _default() {},
1224
+ triggerUpdate: false
1225
+ },
1226
+ onNodeDragEnd: {
1227
+ "default": function _default() {},
1228
+ triggerUpdate: false
1229
+ },
1230
+ onNodeClick: {
1231
+ triggerUpdate: false
1232
+ },
1233
+ onNodeRightClick: {
1234
+ triggerUpdate: false
1235
+ },
1236
+ onNodeHover: {
1237
+ triggerUpdate: false
1238
+ },
1239
+ onLinkClick: {
1240
+ triggerUpdate: false
1241
+ },
1242
+ onLinkRightClick: {
1243
+ triggerUpdate: false
1244
+ },
1245
+ onLinkHover: {
1246
+ triggerUpdate: false
1247
+ },
1248
+ onBackgroundClick: {
1249
+ triggerUpdate: false
1250
+ },
1251
+ onBackgroundRightClick: {
1252
+ triggerUpdate: false
1253
+ },
1254
+ onZoom: {
1255
+ "default": function _default() {},
1256
+ triggerUpdate: false
1257
+ },
1258
+ onZoomEnd: {
1259
+ "default": function _default() {},
1260
+ triggerUpdate: false
1261
+ },
1262
+ onRenderFramePre: {
1263
+ triggerUpdate: false
1264
+ },
1265
+ onRenderFramePost: {
1266
+ triggerUpdate: false
1267
+ }
1268
+ }, linkedProps),
1269
+ aliases: {
1270
+ // Prop names supported for backwards compatibility
1271
+ stopAnimation: 'pauseAnimation'
1272
+ },
1273
+ methods: _objectSpread2({
1274
+ graph2ScreenCoords: function graph2ScreenCoords(state, x, y) {
1275
+ var t = zoomTransform(state.canvas);
1276
+ return {
1277
+ x: x * t.k + t.x,
1278
+ y: y * t.k + t.y
1279
+ };
1280
+ },
1281
+ screen2GraphCoords: function screen2GraphCoords(state, x, y) {
1282
+ var t = zoomTransform(state.canvas);
1283
+ return {
1284
+ x: (x - t.x) / t.k,
1285
+ y: (y - t.y) / t.k
1286
+ };
1287
+ },
1288
+ centerAt: function centerAt(state, x, y, transitionDuration) {
1289
+ if (!state.canvas) return null; // no canvas yet
1290
+ // setter
1291
+
1292
+ if (x !== undefined || y !== undefined) {
1293
+ var finalPos = Object.assign({}, x !== undefined ? {
1294
+ x: x
1295
+ } : {}, y !== undefined ? {
1296
+ y: y
1297
+ } : {});
1298
+
1299
+ if (!transitionDuration) {
1300
+ // no animation
1301
+ setCenter(finalPos);
1302
+ } else {
1303
+ new TWEEN.Tween(getCenter()).to(finalPos, transitionDuration).easing(TWEEN.Easing.Quadratic.Out).onUpdate(setCenter).start();
1304
+ }
1305
+
1306
+ return this;
1307
+ } // getter
1308
+
1309
+
1310
+ return getCenter(); //
1311
+
1312
+ function getCenter() {
1313
+ var t = zoomTransform(state.canvas);
1314
+ return {
1315
+ x: (state.width / 2 - t.x) / t.k,
1316
+ y: (state.height / 2 - t.y) / t.k
1317
+ };
1318
+ }
1319
+
1320
+ function setCenter(_ref5) {
1321
+ var x = _ref5.x,
1322
+ y = _ref5.y;
1323
+ state.zoom.translateTo(state.zoom.__baseElem, x === undefined ? getCenter().x : x, y === undefined ? getCenter().y : y);
1324
+ state.needsRedraw = true;
1325
+ }
1326
+ },
1327
+ zoom: function zoom(state, k, transitionDuration) {
1328
+ if (!state.canvas) return null; // no canvas yet
1329
+ // setter
1330
+
1331
+ if (k !== undefined) {
1332
+ if (!transitionDuration) {
1333
+ // no animation
1334
+ setZoom(k);
1335
+ } else {
1336
+ new TWEEN.Tween({
1337
+ k: getZoom()
1338
+ }).to({
1339
+ k: k
1340
+ }, transitionDuration).easing(TWEEN.Easing.Quadratic.Out).onUpdate(function (_ref6) {
1341
+ var k = _ref6.k;
1342
+ return setZoom(k);
1343
+ }).start();
1344
+ }
1345
+
1346
+ return this;
1347
+ } // getter
1348
+
1349
+
1350
+ return getZoom(); //
1351
+
1352
+ function getZoom() {
1353
+ return zoomTransform(state.canvas).k;
1354
+ }
1355
+
1356
+ function setZoom(k) {
1357
+ state.zoom.scaleTo(state.zoom.__baseElem, k);
1358
+ state.needsRedraw = true;
1359
+ }
1360
+ },
1361
+ zoomToFit: function zoomToFit(state) {
1362
+ var transitionDuration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1363
+ var padding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 10;
1364
+
1365
+ for (var _len = arguments.length, bboxArgs = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
1366
+ bboxArgs[_key - 3] = arguments[_key];
1367
+ }
1368
+
1369
+ var bbox = this.getGraphBbox.apply(this, bboxArgs);
1370
+
1371
+ if (bbox) {
1372
+ var center = {
1373
+ x: (bbox.x[0] + bbox.x[1]) / 2,
1374
+ y: (bbox.y[0] + bbox.y[1]) / 2
1375
+ };
1376
+ var zoomK = Math.max(1e-12, Math.min(1e12, (state.width - padding * 2) / (bbox.x[1] - bbox.x[0]), (state.height - padding * 2) / (bbox.y[1] - bbox.y[0])));
1377
+ this.centerAt(center.x, center.y, transitionDuration);
1378
+ this.zoom(zoomK, transitionDuration);
1379
+ }
1380
+
1381
+ return this;
1382
+ },
1383
+ getGraphBbox: function getGraphBbox(state) {
1384
+ var nodeFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
1385
+ return true;
1386
+ };
1387
+ var getVal = accessorFn(state.nodeVal);
1388
+
1389
+ var getR = function getR(node) {
1390
+ return Math.sqrt(Math.max(0, getVal(node) || 1)) * state.nodeRelSize;
1391
+ };
1392
+
1393
+ var nodesPos = state.graphData.nodes.filter(nodeFilter).map(function (node) {
1394
+ return {
1395
+ x: node.x,
1396
+ y: node.y,
1397
+ r: getR(node)
1398
+ };
1399
+ });
1400
+ return !nodesPos.length ? null : {
1401
+ x: [min(nodesPos, function (node) {
1402
+ return node.x - node.r;
1403
+ }), max(nodesPos, function (node) {
1404
+ return node.x + node.r;
1405
+ })],
1406
+ y: [min(nodesPos, function (node) {
1407
+ return node.y - node.r;
1408
+ }), max(nodesPos, function (node) {
1409
+ return node.y + node.r;
1410
+ })]
1411
+ };
1412
+ },
1413
+ pauseAnimation: function pauseAnimation(state) {
1414
+ if (state.animationFrameRequestId) {
1415
+ cancelAnimationFrame(state.animationFrameRequestId);
1416
+ state.animationFrameRequestId = null;
1417
+ }
1418
+
1419
+ return this;
1420
+ },
1421
+ resumeAnimation: function resumeAnimation(state) {
1422
+ if (!state.animationFrameRequestId) {
1423
+ this._animationCycle();
1424
+ }
1425
+
1426
+ return this;
1427
+ },
1428
+ _destructor: function _destructor() {
1429
+ this.pauseAnimation();
1430
+ this.graphData({
1431
+ nodes: [],
1432
+ links: []
1433
+ });
1434
+ }
1435
+ }, linkedMethods),
1436
+ stateInit: function stateInit() {
1437
+ return {
1438
+ lastSetZoom: 1,
1439
+ zoom: zoom(),
1440
+ forceGraph: new CanvasForceGraph(),
1441
+ shadowGraph: new CanvasForceGraph().cooldownTicks(0).nodeColor('__indexColor').linkColor('__indexColor').isShadow(true),
1442
+ colorTracker: new ColorTracker() // indexed objects for rgb lookup
1443
+
1444
+ };
1445
+ },
1446
+ init: function init(domNode, state) {
1447
+ // Wipe DOM
1448
+ domNode.innerHTML = ''; // Container anchor for canvas and tooltip
1449
+
1450
+ var container = document.createElement('div');
1451
+ container.classList.add('force-graph-container');
1452
+ container.style.position = 'relative';
1453
+ domNode.appendChild(container);
1454
+ state.canvas = document.createElement('canvas');
1455
+ if (state.backgroundColor) state.canvas.style.background = state.backgroundColor;
1456
+ container.appendChild(state.canvas);
1457
+ state.shadowCanvas = document.createElement('canvas'); // Show shadow canvas
1458
+ //state.shadowCanvas.style.position = 'absolute';
1459
+ //state.shadowCanvas.style.top = '0';
1460
+ //state.shadowCanvas.style.left = '0';
1461
+ //container.appendChild(state.shadowCanvas);
1462
+
1463
+ var ctx = state.canvas.getContext('2d');
1464
+ var shadowCtx = state.shadowCanvas.getContext('2d');
1465
+ var pointerPos = {
1466
+ x: -1e12,
1467
+ y: -1e12
1468
+ };
1469
+
1470
+ var getObjUnderPointer = function getObjUnderPointer() {
1471
+ var obj = null;
1472
+ var pxScale = window.devicePixelRatio;
1473
+ var px = pointerPos.x > 0 && pointerPos.y > 0 ? shadowCtx.getImageData(pointerPos.x * pxScale, pointerPos.y * pxScale, 1, 1) : null; // Lookup object per pixel color
1474
+
1475
+ px && (obj = state.colorTracker.lookup(px.data));
1476
+ return obj;
1477
+ }; // Setup node drag interaction
1478
+
1479
+
1480
+ select(state.canvas).call(drag().subject(function () {
1481
+ if (!state.enableNodeDrag) {
1482
+ return null;
1483
+ }
1484
+
1485
+ var obj = getObjUnderPointer();
1486
+ return obj && obj.type === 'Node' ? obj.d : null; // Only drag nodes
1487
+ }).on('start', function (ev) {
1488
+ var obj = ev.subject;
1489
+ obj.__initialDragPos = {
1490
+ x: obj.x,
1491
+ y: obj.y,
1492
+ fx: obj.fx,
1493
+ fy: obj.fy
1494
+ }; // keep engine running at low intensity throughout drag
1495
+
1496
+ if (!ev.active) {
1497
+ obj.fx = obj.x;
1498
+ obj.fy = obj.y; // Fix points
1499
+ } // drag cursor
1500
+
1501
+
1502
+ state.canvas.classList.add('grabbable');
1503
+ }).on('drag', function (ev) {
1504
+ var obj = ev.subject;
1505
+ var initPos = obj.__initialDragPos;
1506
+ var dragPos = ev;
1507
+ var k = zoomTransform(state.canvas).k;
1508
+ var translate = {
1509
+ x: initPos.x + (dragPos.x - initPos.x) / k - obj.x,
1510
+ y: initPos.y + (dragPos.y - initPos.y) / k - obj.y
1511
+ }; // Move fx/fy (and x/y) of nodes based on the scaled drag distance since the drag start
1512
+
1513
+ ['x', 'y'].forEach(function (c) {
1514
+ return obj["f".concat(c)] = obj[c] = initPos[c] + (dragPos[c] - initPos[c]) / k;
1515
+ }); // prevent freeze while dragging
1516
+
1517
+ state.forceGraph.d3AlphaTarget(0.3) // keep engine running at low intensity throughout drag
1518
+ .resetCountdown(); // prevent freeze while dragging
1519
+
1520
+ state.isPointerDragging = true;
1521
+ obj.__dragged = true;
1522
+ state.onNodeDrag(obj, translate);
1523
+ }).on('end', function (ev) {
1524
+ var obj = ev.subject;
1525
+ var initPos = obj.__initialDragPos;
1526
+ var translate = {
1527
+ x: obj.x - initPos.x,
1528
+ y: obj.y - initPos.y
1529
+ };
1530
+
1531
+ if (initPos.fx === undefined) {
1532
+ obj.fx = undefined;
1533
+ }
1534
+
1535
+ if (initPos.fy === undefined) {
1536
+ obj.fy = undefined;
1537
+ }
1538
+
1539
+ delete obj.__initialDragPos;
1540
+
1541
+ if (state.forceGraph.d3AlphaTarget()) {
1542
+ state.forceGraph.d3AlphaTarget(0) // release engine low intensity
1543
+ .resetCountdown(); // let the engine readjust after releasing fixed nodes
1544
+ } // drag cursor
1545
+
1546
+
1547
+ state.canvas.classList.remove('grabbable');
1548
+ state.isPointerDragging = false;
1549
+
1550
+ if (obj.__dragged) {
1551
+ delete obj.__dragged;
1552
+ state.onNodeDragEnd(obj, translate);
1553
+ }
1554
+ })); // Setup zoom / pan interaction
1555
+
1556
+ state.zoom(state.zoom.__baseElem = select(state.canvas)); // Attach controlling elem for easy access
1557
+
1558
+ state.zoom.__baseElem.on('dblclick.zoom', null); // Disable double-click to zoom
1559
+
1560
+
1561
+ state.zoom.filter(function (ev) {
1562
+ return (// disable zoom interaction
1563
+ !ev.button && state.enableZoomPanInteraction && (state.enableZoomInteraction || ev.type !== 'wheel') && (state.enablePanInteraction || ev.type === 'wheel')
1564
+ );
1565
+ }).on('zoom', function (ev) {
1566
+ var t = ev.transform;
1567
+ [ctx, shadowCtx].forEach(function (c) {
1568
+ resetTransform(c);
1569
+ c.translate(t.x, t.y);
1570
+ c.scale(t.k, t.k);
1571
+ });
1572
+ state.onZoom(_objectSpread2({}, t));
1573
+ state.needsRedraw = true;
1574
+ }).on('end', function (ev) {
1575
+ return state.onZoomEnd(_objectSpread2({}, ev.transform));
1576
+ });
1577
+ adjustCanvasSize(state);
1578
+ state.forceGraph.onNeedsRedraw(function () {
1579
+ return state.needsRedraw = true;
1580
+ }).onFinishUpdate(function () {
1581
+ // re-zoom, if still in default position (not user modified)
1582
+ if (zoomTransform(state.canvas).k === state.lastSetZoom && state.graphData.nodes.length) {
1583
+ state.zoom.scaleTo(state.zoom.__baseElem, state.lastSetZoom = ZOOM2NODES_FACTOR / Math.cbrt(state.graphData.nodes.length));
1584
+ state.needsRedraw = true;
1585
+ }
1586
+ }); // Setup tooltip
1587
+
1588
+ var toolTipElem = document.createElement('div');
1589
+ toolTipElem.classList.add('graph-tooltip');
1590
+ container.appendChild(toolTipElem); // Capture pointer coords on move or touchstart
1591
+
1592
+ ['pointermove', 'pointerdown'].forEach(function (evType) {
1593
+ return container.addEventListener(evType, function (ev) {
1594
+ if (evType === 'pointerdown') {
1595
+ state.isPointerPressed = true; // track click state
1596
+
1597
+ state.pointerDownEvent = ev;
1598
+ } // detect pointer drag on canvas pan
1599
+
1600
+
1601
+ !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)
1602
+ && (ev.pressure > 0 || state.isPointerPressed) // ev.pressure always 0 on Safari, so we use the isPointerPressed tracker
1603
+ && (ev.pointerType !== 'touch' || ev.movementX === undefined || [ev.movementX, ev.movementY].some(function (m) {
1604
+ return Math.abs(m) > 1;
1605
+ })) // relax drag trigger sensitivity on touch events
1606
+ && (state.isPointerDragging = true); // update the pointer pos
1607
+
1608
+ var offset = getOffset(container);
1609
+ pointerPos.x = ev.pageX - offset.left;
1610
+ pointerPos.y = ev.pageY - offset.top; // Move tooltip
1611
+
1612
+ toolTipElem.style.top = "".concat(pointerPos.y, "px");
1613
+ toolTipElem.style.left = "".concat(pointerPos.x, "px"); //
1614
+
1615
+ function getOffset(el) {
1616
+ var rect = el.getBoundingClientRect(),
1617
+ scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
1618
+ scrollTop = window.pageYOffset || document.documentElement.scrollTop;
1619
+ return {
1620
+ top: rect.top + scrollTop,
1621
+ left: rect.left + scrollLeft
1622
+ };
1623
+ }
1624
+ }, {
1625
+ passive: true
1626
+ });
1627
+ }); // Handle click/touch events on nodes/links
1628
+
1629
+ container.addEventListener('pointerup', function (ev) {
1630
+ state.isPointerPressed = false;
1631
+
1632
+ if (state.isPointerDragging) {
1633
+ state.isPointerDragging = false;
1634
+ return; // don't trigger click events after pointer drag (pan / node drag functionality)
1635
+ }
1636
+
1637
+ var cbEvents = [ev, state.pointerDownEvent];
1638
+ requestAnimationFrame(function () {
1639
+ // trigger click events asynchronously, to allow hoverObj to be set (on frame)
1640
+ if (ev.button === 0) {
1641
+ // mouse left-click or touch
1642
+ if (state.hoverObj) {
1643
+ var fn = state["on".concat(state.hoverObj.type, "Click")];
1644
+ fn && fn.apply(void 0, [state.hoverObj.d].concat(cbEvents));
1645
+ } else {
1646
+ state.onBackgroundClick && state.onBackgroundClick.apply(state, cbEvents);
1647
+ }
1648
+ }
1649
+
1650
+ if (ev.button === 2) {
1651
+ // mouse right-click
1652
+ if (state.hoverObj) {
1653
+ var _fn = state["on".concat(state.hoverObj.type, "RightClick")];
1654
+ _fn && _fn.apply(void 0, [state.hoverObj.d].concat(cbEvents));
1655
+ } else {
1656
+ state.onBackgroundRightClick && state.onBackgroundRightClick.apply(state, cbEvents);
1657
+ }
1658
+ }
1659
+ });
1660
+ }, {
1661
+ passive: true
1662
+ });
1663
+ container.addEventListener('contextmenu', function (ev) {
1664
+ if (!state.onBackgroundRightClick && !state.onNodeRightClick && !state.onLinkRightClick) return true; // default contextmenu behavior
1665
+
1666
+ ev.preventDefault();
1667
+ return false;
1668
+ });
1669
+ state.forceGraph(ctx);
1670
+ state.shadowGraph(shadowCtx); //
1671
+
1672
+ var refreshShadowCanvas = throttle(function () {
1673
+ // wipe canvas
1674
+ clearCanvas(shadowCtx, state.width, state.height); // Adjust link hover area
1675
+
1676
+ state.shadowGraph.linkWidth(function (l) {
1677
+ return accessorFn(state.linkWidth)(l) + state.linkHoverPrecision;
1678
+ }); // redraw
1679
+
1680
+ var t = zoomTransform(state.canvas);
1681
+ state.shadowGraph.globalScale(t.k).tickFrame();
1682
+ }, HOVER_CANVAS_THROTTLE_DELAY);
1683
+ state.flushShadowCanvas = refreshShadowCanvas.flush; // hook to immediately invoke shadow canvas paint
1684
+ // Kick-off renderer
1685
+
1686
+ (this._animationCycle = function animate() {
1687
+ // IIFE
1688
+ var doRedraw = !state.autoPauseRedraw || !!state.needsRedraw || state.forceGraph.isEngineRunning() || state.graphData.links.some(function (d) {
1689
+ return d.__photons && d.__photons.length;
1690
+ });
1691
+ state.needsRedraw = false;
1692
+
1693
+ if (state.enablePointerInteraction) {
1694
+ // Update tooltip and trigger onHover events
1695
+ var obj = !state.isPointerDragging ? getObjUnderPointer() : null; // don't hover during drag
1696
+
1697
+ if (obj !== state.hoverObj) {
1698
+ var prevObj = state.hoverObj;
1699
+ var prevObjType = prevObj ? prevObj.type : null;
1700
+ var objType = obj ? obj.type : null;
1701
+
1702
+ if (prevObjType && prevObjType !== objType) {
1703
+ // Hover out
1704
+ var fn = state["on".concat(prevObjType, "Hover")];
1705
+ fn && fn(null, prevObj.d);
1706
+ }
1707
+
1708
+ if (objType) {
1709
+ // Hover in
1710
+ var _fn2 = state["on".concat(objType, "Hover")];
1711
+ _fn2 && _fn2(obj.d, prevObjType === objType ? prevObj.d : null);
1712
+ }
1713
+
1714
+ var tooltipContent = obj ? accessorFn(state["".concat(obj.type.toLowerCase(), "Label")])(obj.d) || '' : '';
1715
+ toolTipElem.style.visibility = tooltipContent ? 'visible' : 'hidden';
1716
+ toolTipElem.innerHTML = tooltipContent; // set pointer if hovered object is clickable
1717
+
1718
+ state.canvas.classList[obj && state["on".concat(objType, "Click")] || !obj && state.onBackgroundClick ? 'add' : 'remove']('clickable');
1719
+ state.hoverObj = obj;
1720
+ }
1721
+
1722
+ doRedraw && refreshShadowCanvas();
1723
+ }
1724
+
1725
+ if (doRedraw) {
1726
+ // Wipe canvas
1727
+ clearCanvas(ctx, state.width, state.height); // Frame cycle
1728
+
1729
+ var globalScale = zoomTransform(state.canvas).k;
1730
+ state.onRenderFramePre && state.onRenderFramePre(ctx, globalScale);
1731
+ state.forceGraph.globalScale(globalScale).tickFrame();
1732
+ state.onRenderFramePost && state.onRenderFramePost(ctx, globalScale);
1733
+ }
1734
+
1735
+ TWEEN.update(); // update canvas animation tweens
1736
+
1737
+ state.animationFrameRequestId = requestAnimationFrame(animate);
1738
+ })();
1739
+ },
1740
+ update: function updateFn(state) {}
1741
+ });
1742
+
1743
+ export { forceGraph as default };