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