3d-force-graph.common.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. 'use strict';
  2. var three$1 = require('three');
  3. var DragControls_js = require('three/examples/jsm/controls/DragControls.js');
  4. var ThreeForceGraph = require('three-forcegraph');
  5. var ThreeRenderObjects = require('three-render-objects');
  6. var accessorFn = require('accessor-fn');
  7. var Kapsule = require('kapsule');
  8. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  9. var ThreeForceGraph__default = /*#__PURE__*/_interopDefaultLegacy(ThreeForceGraph);
  10. var ThreeRenderObjects__default = /*#__PURE__*/_interopDefaultLegacy(ThreeRenderObjects);
  11. var accessorFn__default = /*#__PURE__*/_interopDefaultLegacy(accessorFn);
  12. var Kapsule__default = /*#__PURE__*/_interopDefaultLegacy(Kapsule);
  13. function styleInject(css, ref) {
  14. if (ref === void 0) ref = {};
  15. var insertAt = ref.insertAt;
  16. if (!css || typeof document === 'undefined') {
  17. return;
  18. }
  19. var head = document.head || document.getElementsByTagName('head')[0];
  20. var style = document.createElement('style');
  21. style.type = 'text/css';
  22. if (insertAt === 'top') {
  23. if (head.firstChild) {
  24. head.insertBefore(style, head.firstChild);
  25. } else {
  26. head.appendChild(style);
  27. }
  28. } else {
  29. head.appendChild(style);
  30. }
  31. if (style.styleSheet) {
  32. style.styleSheet.cssText = css;
  33. } else {
  34. style.appendChild(document.createTextNode(css));
  35. }
  36. }
  37. var css_248z = ".graph-info-msg {\n top: 50%;\n width: 100%;\n text-align: center;\n color: lavender;\n opacity: 0.7;\n font-size: 22px;\n position: absolute;\n font-family: Sans-serif;\n}\n\n.scene-container .clickable {\n cursor: pointer;\n}\n\n.scene-container .grabbable {\n cursor: move;\n cursor: grab;\n cursor: -moz-grab;\n cursor: -webkit-grab;\n}\n\n.scene-container .grabbable:active {\n cursor: grabbing;\n cursor: -moz-grabbing;\n cursor: -webkit-grabbing;\n}";
  38. styleInject(css_248z);
  39. function ownKeys(object, enumerableOnly) {
  40. var keys = Object.keys(object);
  41. if (Object.getOwnPropertySymbols) {
  42. var symbols = Object.getOwnPropertySymbols(object);
  43. if (enumerableOnly) {
  44. symbols = symbols.filter(function (sym) {
  45. return Object.getOwnPropertyDescriptor(object, sym).enumerable;
  46. });
  47. }
  48. keys.push.apply(keys, symbols);
  49. }
  50. return keys;
  51. }
  52. function _objectSpread2(target) {
  53. for (var i = 1; i < arguments.length; i++) {
  54. var source = arguments[i] != null ? arguments[i] : {};
  55. if (i % 2) {
  56. ownKeys(Object(source), true).forEach(function (key) {
  57. _defineProperty(target, key, source[key]);
  58. });
  59. } else if (Object.getOwnPropertyDescriptors) {
  60. Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
  61. } else {
  62. ownKeys(Object(source)).forEach(function (key) {
  63. Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
  64. });
  65. }
  66. }
  67. return target;
  68. }
  69. function _defineProperty(obj, key, value) {
  70. if (key in obj) {
  71. Object.defineProperty(obj, key, {
  72. value: value,
  73. enumerable: true,
  74. configurable: true,
  75. writable: true
  76. });
  77. } else {
  78. obj[key] = value;
  79. }
  80. return obj;
  81. }
  82. function _toConsumableArray(arr) {
  83. return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
  84. }
  85. function _arrayWithoutHoles(arr) {
  86. if (Array.isArray(arr)) return _arrayLikeToArray(arr);
  87. }
  88. function _iterableToArray(iter) {
  89. if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
  90. }
  91. function _unsupportedIterableToArray(o, minLen) {
  92. if (!o) return;
  93. if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  94. var n = Object.prototype.toString.call(o).slice(8, -1);
  95. if (n === "Object" && o.constructor) n = o.constructor.name;
  96. if (n === "Map" || n === "Set") return Array.from(o);
  97. if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  98. }
  99. function _arrayLikeToArray(arr, len) {
  100. if (len == null || len > arr.length) len = arr.length;
  101. for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
  102. return arr2;
  103. }
  104. function _nonIterableSpread() {
  105. throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  106. }
  107. function linkKapsule (kapsulePropName, kapsuleType) {
  108. var dummyK = new kapsuleType(); // To extract defaults
  109. return {
  110. linkProp: function linkProp(prop) {
  111. // link property config
  112. return {
  113. "default": dummyK[prop](),
  114. onChange: function onChange(v, state) {
  115. state[kapsulePropName][prop](v);
  116. },
  117. triggerUpdate: false
  118. };
  119. },
  120. linkMethod: function linkMethod(method) {
  121. // link method pass-through
  122. return function (state) {
  123. var kapsuleInstance = state[kapsulePropName];
  124. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  125. args[_key - 1] = arguments[_key];
  126. }
  127. var returnVal = kapsuleInstance[method].apply(kapsuleInstance, args);
  128. return returnVal === kapsuleInstance ? this // chain based on the parent object, not the inner kapsule
  129. : returnVal;
  130. };
  131. }
  132. };
  133. }
  134. var three = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
  135. : {
  136. AmbientLight: three$1.AmbientLight,
  137. DirectionalLight: three$1.DirectionalLight,
  138. Vector3: three$1.Vector3
  139. };
  140. var CAMERA_DISTANCE2NODES_FACTOR = 170; //
  141. // Expose config from forceGraph
  142. var bindFG = linkKapsule('forceGraph', ThreeForceGraph__default['default']);
  143. var linkedFGProps = Object.assign.apply(Object, _toConsumableArray(['jsonUrl', 'graphData', 'numDimensions', 'dagMode', 'dagLevelDistance', 'dagNodeFilter', 'onDagError', 'nodeRelSize', 'nodeId', 'nodeVal', 'nodeResolution', 'nodeColor', 'nodeAutoColorBy', 'nodeOpacity', 'nodeVisibility', 'nodeThreeObject', 'nodeThreeObjectExtend', 'linkSource', 'linkTarget', 'linkVisibility', 'linkColor', 'linkAutoColorBy', 'linkOpacity', 'linkWidth', 'linkResolution', 'linkCurvature', 'linkCurveRotation', 'linkMaterial', 'linkThreeObject', 'linkThreeObjectExtend', 'linkPositionUpdate', 'linkDirectionalArrowLength', 'linkDirectionalArrowColor', 'linkDirectionalArrowRelPos', 'linkDirectionalArrowResolution', 'linkDirectionalParticles', 'linkDirectionalParticleSpeed', 'linkDirectionalParticleWidth', 'linkDirectionalParticleColor', 'linkDirectionalParticleResolution', 'forceEngine', 'd3AlphaDecay', 'd3VelocityDecay', 'd3AlphaMin', 'ngraphPhysics', 'warmupTicks', 'cooldownTicks', 'cooldownTime', 'onEngineTick', 'onEngineStop'].map(function (p) {
  144. return _defineProperty({}, p, bindFG.linkProp(p));
  145. })));
  146. var linkedFGMethods = Object.assign.apply(Object, _toConsumableArray(['refresh', 'getGraphBbox', 'd3Force', 'd3ReheatSimulation', 'emitParticle'].map(function (p) {
  147. return _defineProperty({}, p, bindFG.linkMethod(p));
  148. }))); // Expose config from renderObjs
  149. var bindRenderObjs = linkKapsule('renderObjs', ThreeRenderObjects__default['default']);
  150. var linkedRenderObjsProps = Object.assign.apply(Object, _toConsumableArray(['width', 'height', 'backgroundColor', 'showNavInfo', 'enablePointerInteraction'].map(function (p) {
  151. return _defineProperty({}, p, bindRenderObjs.linkProp(p));
  152. })));
  153. var linkedRenderObjsMethods = Object.assign.apply(Object, _toConsumableArray(['cameraPosition', 'postProcessingComposer'].map(function (p) {
  154. return _defineProperty({}, p, bindRenderObjs.linkMethod(p));
  155. })).concat([{
  156. graph2ScreenCoords: bindRenderObjs.linkMethod('getScreenCoords'),
  157. screen2GraphCoords: bindRenderObjs.linkMethod('getSceneCoords')
  158. }])); //
  159. var _3dForceGraph = Kapsule__default['default']({
  160. props: _objectSpread2(_objectSpread2({
  161. nodeLabel: {
  162. "default": 'name',
  163. triggerUpdate: false
  164. },
  165. linkLabel: {
  166. "default": 'name',
  167. triggerUpdate: false
  168. },
  169. linkHoverPrecision: {
  170. "default": 1,
  171. onChange: function onChange(p, state) {
  172. return state.renderObjs.lineHoverPrecision(p);
  173. },
  174. triggerUpdate: false
  175. },
  176. enableNavigationControls: {
  177. "default": true,
  178. onChange: function onChange(enable, state) {
  179. var controls = state.renderObjs.controls();
  180. if (controls) {
  181. controls.enabled = enable;
  182. }
  183. },
  184. triggerUpdate: false
  185. },
  186. enableNodeDrag: {
  187. "default": true,
  188. triggerUpdate: false
  189. },
  190. onNodeDrag: {
  191. "default": function _default() {},
  192. triggerUpdate: false
  193. },
  194. onNodeDragEnd: {
  195. "default": function _default() {},
  196. triggerUpdate: false
  197. },
  198. onNodeClick: {
  199. triggerUpdate: false
  200. },
  201. onNodeRightClick: {
  202. triggerUpdate: false
  203. },
  204. onNodeHover: {
  205. triggerUpdate: false
  206. },
  207. onLinkClick: {
  208. triggerUpdate: false
  209. },
  210. onLinkRightClick: {
  211. triggerUpdate: false
  212. },
  213. onLinkHover: {
  214. triggerUpdate: false
  215. },
  216. onBackgroundClick: {
  217. triggerUpdate: false
  218. },
  219. onBackgroundRightClick: {
  220. triggerUpdate: false
  221. }
  222. }, linkedFGProps), linkedRenderObjsProps),
  223. methods: _objectSpread2(_objectSpread2({
  224. zoomToFit: function zoomToFit(state, transitionDuration, padding) {
  225. var _state$forceGraph;
  226. for (var _len = arguments.length, bboxArgs = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
  227. bboxArgs[_key - 3] = arguments[_key];
  228. }
  229. state.renderObjs.fitToBbox((_state$forceGraph = state.forceGraph).getGraphBbox.apply(_state$forceGraph, bboxArgs), transitionDuration, padding);
  230. return this;
  231. },
  232. pauseAnimation: function pauseAnimation(state) {
  233. if (state.animationFrameRequestId !== null) {
  234. cancelAnimationFrame(state.animationFrameRequestId);
  235. state.animationFrameRequestId = null;
  236. }
  237. return this;
  238. },
  239. resumeAnimation: function resumeAnimation(state) {
  240. if (state.animationFrameRequestId === null) {
  241. this._animationCycle();
  242. }
  243. return this;
  244. },
  245. _animationCycle: function _animationCycle(state) {
  246. if (state.enablePointerInteraction) {
  247. // reset canvas cursor (override dragControls cursor)
  248. this.renderer().domElement.style.cursor = null;
  249. } // Frame cycle
  250. state.forceGraph.tickFrame();
  251. state.renderObjs.tick();
  252. state.animationFrameRequestId = requestAnimationFrame(this._animationCycle);
  253. },
  254. scene: function scene(state) {
  255. return state.renderObjs.scene();
  256. },
  257. // Expose scene
  258. camera: function camera(state) {
  259. return state.renderObjs.camera();
  260. },
  261. // Expose camera
  262. renderer: function renderer(state) {
  263. return state.renderObjs.renderer();
  264. },
  265. // Expose renderer
  266. controls: function controls(state) {
  267. return state.renderObjs.controls();
  268. },
  269. // Expose controls
  270. tbControls: function tbControls(state) {
  271. return state.renderObjs.tbControls();
  272. },
  273. // To be deprecated
  274. _destructor: function _destructor() {
  275. this.pauseAnimation();
  276. this.graphData({
  277. nodes: [],
  278. links: []
  279. });
  280. }
  281. }, linkedFGMethods), linkedRenderObjsMethods),
  282. stateInit: function stateInit(_ref5) {
  283. var controlType = _ref5.controlType,
  284. rendererConfig = _ref5.rendererConfig,
  285. extraRenderers = _ref5.extraRenderers;
  286. return {
  287. forceGraph: new ThreeForceGraph__default['default'](),
  288. renderObjs: ThreeRenderObjects__default['default']({
  289. controlType: controlType,
  290. rendererConfig: rendererConfig,
  291. extraRenderers: extraRenderers
  292. })
  293. };
  294. },
  295. init: function init(domNode, state) {
  296. // Wipe DOM
  297. domNode.innerHTML = ''; // Add relative container
  298. domNode.appendChild(state.container = document.createElement('div'));
  299. state.container.style.position = 'relative'; // Add renderObjs
  300. var roDomNode = document.createElement('div');
  301. state.container.appendChild(roDomNode);
  302. state.renderObjs(roDomNode);
  303. var camera = state.renderObjs.camera();
  304. var renderer = state.renderObjs.renderer();
  305. var controls = state.renderObjs.controls();
  306. controls.enabled = !!state.enableNavigationControls;
  307. state.lastSetCameraZ = camera.position.z; // Add info space
  308. var infoElem;
  309. state.container.appendChild(infoElem = document.createElement('div'));
  310. infoElem.className = 'graph-info-msg';
  311. infoElem.textContent = ''; // config forcegraph
  312. state.forceGraph.onLoading(function () {
  313. infoElem.textContent = 'Loading...';
  314. }).onFinishLoading(function () {
  315. infoElem.textContent = '';
  316. }).onUpdate(function () {
  317. // sync graph data structures
  318. state.graphData = state.forceGraph.graphData(); // re-aim camera, if still in default position (not user modified)
  319. if (camera.position.x === 0 && camera.position.y === 0 && camera.position.z === state.lastSetCameraZ && state.graphData.nodes.length) {
  320. camera.lookAt(state.forceGraph.position);
  321. state.lastSetCameraZ = camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;
  322. }
  323. }).onFinishUpdate(function () {
  324. // Setup node drag interaction
  325. if (state._dragControls) {
  326. var curNodeDrag = state.graphData.nodes.find(function (node) {
  327. return node.__initialFixedPos && !node.__disposeControlsAfterDrag;
  328. }); // detect if there's a node being dragged using the existing drag controls
  329. if (curNodeDrag) {
  330. curNodeDrag.__disposeControlsAfterDrag = true; // postpone previous controls disposal until drag ends
  331. } else {
  332. state._dragControls.dispose(); // cancel previous drag controls
  333. }
  334. state._dragControls = undefined;
  335. }
  336. if (state.enableNodeDrag && state.enablePointerInteraction && state.forceEngine === 'd3') {
  337. // Can't access node positions programatically in ngraph
  338. var dragControls = state._dragControls = new DragControls_js.DragControls(state.graphData.nodes.map(function (node) {
  339. return node.__threeObj;
  340. }).filter(function (obj) {
  341. return obj;
  342. }), camera, renderer.domElement);
  343. dragControls.addEventListener('dragstart', function (event) {
  344. controls.enabled = false; // Disable controls while dragging
  345. // track drag object movement
  346. event.object.__initialPos = event.object.position.clone();
  347. event.object.__prevPos = event.object.position.clone();
  348. var node = getGraphObj(event.object).__data;
  349. !node.__initialFixedPos && (node.__initialFixedPos = {
  350. fx: node.fx,
  351. fy: node.fy,
  352. fz: node.fz
  353. });
  354. !node.__initialPos && (node.__initialPos = {
  355. x: node.x,
  356. y: node.y,
  357. z: node.z
  358. }); // lock node
  359. ['x', 'y', 'z'].forEach(function (c) {
  360. return node["f".concat(c)] = node[c];
  361. }); // drag cursor
  362. renderer.domElement.classList.add('grabbable');
  363. });
  364. dragControls.addEventListener('drag', function (event) {
  365. var nodeObj = getGraphObj(event.object);
  366. if (!event.object.hasOwnProperty('__graphObjType')) {
  367. // If dragging a child of the node, update the node object instead
  368. var initPos = event.object.__initialPos;
  369. var prevPos = event.object.__prevPos;
  370. var _newPos = event.object.position;
  371. nodeObj.position.add(_newPos.clone().sub(prevPos)); // translate node object by the motion delta
  372. prevPos.copy(_newPos);
  373. _newPos.copy(initPos); // reset child back to its initial position
  374. }
  375. var node = nodeObj.__data;
  376. var newPos = nodeObj.position;
  377. var translate = {
  378. x: newPos.x - node.x,
  379. y: newPos.y - node.y,
  380. z: newPos.z - node.z
  381. }; // Move fx/fy/fz (and x/y/z) of nodes based on object new position
  382. ['x', 'y', 'z'].forEach(function (c) {
  383. return node["f".concat(c)] = node[c] = newPos[c];
  384. });
  385. state.forceGraph.d3AlphaTarget(0.3) // keep engine running at low intensity throughout drag
  386. .resetCountdown(); // prevent freeze while dragging
  387. node.__dragged = true;
  388. state.onNodeDrag(node, translate);
  389. });
  390. dragControls.addEventListener('dragend', function (event) {
  391. delete event.object.__initialPos; // remove tracking attributes
  392. delete event.object.__prevPos;
  393. var node = getGraphObj(event.object).__data; // dispose previous controls if needed
  394. if (node.__disposeControlsAfterDrag) {
  395. dragControls.dispose();
  396. delete node.__disposeControlsAfterDrag;
  397. }
  398. var initFixedPos = node.__initialFixedPos;
  399. var initPos = node.__initialPos;
  400. var translate = {
  401. x: initPos.x - node.x,
  402. y: initPos.y - node.y,
  403. z: initPos.z - node.z
  404. };
  405. if (initFixedPos) {
  406. ['x', 'y', 'z'].forEach(function (c) {
  407. var fc = "f".concat(c);
  408. if (initFixedPos[fc] === undefined) {
  409. delete node[fc];
  410. }
  411. });
  412. delete node.__initialFixedPos;
  413. delete node.__initialPos;
  414. if (node.__dragged) {
  415. delete node.__dragged;
  416. state.onNodeDragEnd(node, translate);
  417. }
  418. }
  419. state.forceGraph.d3AlphaTarget(0) // release engine low intensity
  420. .resetCountdown(); // let the engine readjust after releasing fixed nodes
  421. if (state.enableNavigationControls) {
  422. controls.enabled = true; // Re-enable controls
  423. controls.domElement && controls.domElement.ownerDocument && controls.domElement.ownerDocument.dispatchEvent( // simulate mouseup to ensure the controls don't take over after dragend
  424. new PointerEvent('pointerup', {
  425. pointerType: 'touch'
  426. }));
  427. } // clear cursor
  428. renderer.domElement.classList.remove('grabbable');
  429. });
  430. }
  431. }); // config renderObjs
  432. state.renderObjs.objects([// Populate scene
  433. new three.AmbientLight(0xbbbbbb), new three.DirectionalLight(0xffffff, 0.6), state.forceGraph]).hoverOrderComparator(function (a, b) {
  434. // Prioritize graph objects
  435. var aObj = getGraphObj(a);
  436. if (!aObj) return 1;
  437. var bObj = getGraphObj(b);
  438. if (!bObj) return -1; // Prioritize nodes over links
  439. var isNode = function isNode(o) {
  440. return o.__graphObjType === 'node';
  441. };
  442. return isNode(bObj) - isNode(aObj);
  443. }).tooltipContent(function (obj) {
  444. var graphObj = getGraphObj(obj);
  445. return graphObj ? accessorFn__default['default'](state["".concat(graphObj.__graphObjType, "Label")])(graphObj.__data) || '' : '';
  446. }).hoverDuringDrag(false).onHover(function (obj) {
  447. // Update tooltip and trigger onHover events
  448. var hoverObj = getGraphObj(obj);
  449. if (hoverObj !== state.hoverObj) {
  450. var prevObjType = state.hoverObj ? state.hoverObj.__graphObjType : null;
  451. var prevObjData = state.hoverObj ? state.hoverObj.__data : null;
  452. var objType = hoverObj ? hoverObj.__graphObjType : null;
  453. var objData = hoverObj ? hoverObj.__data : null;
  454. if (prevObjType && prevObjType !== objType) {
  455. // Hover out
  456. var fn = state["on".concat(prevObjType === 'node' ? 'Node' : 'Link', "Hover")];
  457. fn && fn(null, prevObjData);
  458. }
  459. if (objType) {
  460. // Hover in
  461. var _fn = state["on".concat(objType === 'node' ? 'Node' : 'Link', "Hover")];
  462. _fn && _fn(objData, prevObjType === objType ? prevObjData : null);
  463. } // set pointer if hovered object is clickable
  464. renderer.domElement.classList[hoverObj && state["on".concat(objType === 'node' ? 'Node' : 'Link', "Click")] || !hoverObj && state.onBackgroundClick ? 'add' : 'remove']('clickable');
  465. state.hoverObj = hoverObj;
  466. }
  467. }).clickAfterDrag(false).onClick(function (obj, ev) {
  468. var graphObj = getGraphObj(obj);
  469. if (graphObj) {
  470. var fn = state["on".concat(graphObj.__graphObjType === 'node' ? 'Node' : 'Link', "Click")];
  471. fn && fn(graphObj.__data, ev);
  472. } else {
  473. state.onBackgroundClick && state.onBackgroundClick(ev);
  474. }
  475. }).onRightClick(function (obj, ev) {
  476. // Handle right-click events
  477. var graphObj = getGraphObj(obj);
  478. if (graphObj) {
  479. var fn = state["on".concat(graphObj.__graphObjType === 'node' ? 'Node' : 'Link', "RightClick")];
  480. fn && fn(graphObj.__data, ev);
  481. } else {
  482. state.onBackgroundRightClick && state.onBackgroundRightClick(ev);
  483. }
  484. }); //
  485. // Kick-off renderer
  486. this._animationCycle();
  487. }
  488. }); //
  489. function getGraphObj(object) {
  490. var obj = object; // recurse up object chain until finding the graph object
  491. while (obj && !obj.hasOwnProperty('__graphObjType')) {
  492. obj = obj.parent;
  493. }
  494. return obj;
  495. }
  496. module.exports = _3dForceGraph;