Skip to content
Snippets Groups Projects
3d-force-graph.js 1.6 MiB
Newer Older
Amira Abdel-Rahman's avatar
Amira Abdel-Rahman committed
53001 53002 53003 53004 53005 53006 53007 53008 53009 53010 53011 53012 53013 53014 53015 53016 53017 53018 53019 53020 53021 53022 53023 53024 53025 53026 53027 53028 53029 53030 53031 53032 53033 53034 53035 53036 53037 53038 53039 53040 53041 53042 53043 53044 53045 53046 53047 53048 53049 53050 53051 53052 53053 53054 53055 53056 53057 53058 53059 53060 53061 53062 53063 53064 53065 53066 53067 53068 53069 53070 53071 53072 53073 53074 53075 53076 53077 53078 53079 53080 53081 53082 53083 53084 53085 53086 53087 53088 53089 53090 53091 53092 53093 53094 53095 53096 53097 53098 53099 53100 53101 53102 53103 53104 53105 53106 53107 53108 53109 53110 53111 53112 53113 53114 53115 53116 53117 53118 53119 53120 53121 53122 53123 53124 53125 53126 53127 53128 53129 53130 53131 53132 53133 53134 53135 53136 53137 53138 53139 53140 53141 53142 53143 53144 53145 53146 53147 53148 53149 53150 53151 53152 53153 53154 53155 53156 53157 53158 53159 53160 53161 53162 53163 53164 53165 53166 53167 53168 53169 53170 53171 53172 53173 53174 53175 53176 53177 53178 53179 53180 53181 53182 53183 53184 53185 53186 53187 53188 53189 53190 53191 53192 53193 53194 53195 53196 53197 53198 53199 53200 53201 53202 53203 53204 53205 53206 53207 53208 53209 53210 53211 53212 53213 53214 53215 53216 53217 53218 53219 53220 53221 53222 53223 53224 53225 53226 53227 53228 53229 53230 53231 53232 53233 53234 53235 53236 53237 53238 53239 53240 53241 53242 53243 53244 53245 53246 53247 53248 53249 53250 53251 53252 53253 53254 53255 53256 53257 53258 53259 53260 53261 53262 53263 53264 53265 53266 53267 53268 53269 53270 53271 53272 53273 53274 53275 53276 53277 53278 53279 53280 53281 53282 53283 53284 53285 53286 53287 53288 53289 53290 53291 53292 53293 53294 53295 53296 53297 53298 53299 53300 53301 53302 53303 53304 53305 53306 53307 53308 53309 53310 53311 53312 53313 53314 53315 53316 53317 53318 53319 53320 53321 53322 53323 53324 53325 53326 53327 53328 53329 53330 53331 53332 53333 53334 53335 53336 53337 53338 53339 53340 53341 53342 53343 53344 53345 53346 53347 53348 53349 53350 53351 53352 53353 53354 53355 53356 53357 53358 53359 53360 53361 53362 53363 53364 53365 53366 53367 53368 53369 53370 53371 53372 53373 53374 53375 53376 53377 53378 53379 53380 53381 53382 53383 53384 53385 53386 53387 53388 53389 53390 53391 53392 53393 53394 53395 53396 53397 53398 53399 53400 53401 53402 53403 53404 53405 53406 53407 53408 53409 53410 53411 53412 53413 53414 53415 53416 53417 53418 53419 53420 53421 53422 53423 53424 53425 53426 53427 53428 53429 53430 53431 53432 53433 53434 53435 53436 53437 53438 53439 53440 53441 53442 53443 53444 53445 53446 53447 53448 53449 53450 53451 53452 53453 53454 53455 53456 53457 53458 53459 53460 53461 53462 53463 53464 53465 53466 53467 53468 53469 53470 53471 53472 53473 53474 53475 53476 53477 53478 53479 53480 53481 53482 53483 53484 53485 53486 53487 53488 53489 53490 53491 53492 53493 53494 53495 53496 53497 53498 53499 53500 53501 53502 53503 53504 53505 53506 53507 53508 53509 53510 53511 53512 53513 53514 53515 53516 53517 53518 53519 53520 53521 53522 53523 53524 53525 53526 53527 53528 53529 53530 53531 53532 53533 53534 53535 53536 53537 53538 53539 53540 53541 53542 53543 53544 53545 53546 53547 53548 53549 53550 53551 53552 53553 53554 53555 53556 53557 53558 53559 53560 53561 53562 53563 53564 53565 53566 53567 53568 53569 53570 53571 53572 53573 53574 53575 53576 53577 53578 53579 53580 53581 53582 53583 53584 53585 53586 53587 53588 53589 53590 53591 53592 53593 53594 53595 53596 53597 53598 53599 53600 53601 53602 53603 53604 53605 53606 53607 53608 53609 53610 53611 53612 53613 53614 53615 53616 53617 53618 53619 53620 53621 53622 53623 53624 53625 53626 53627 53628 53629 53630 53631 53632 53633 53634 53635 53636 53637 53638 53639 53640 53641 53642 53643 53644 53645 53646 53647 53648 53649 53650 53651 53652 53653 53654 53655 53656 53657 53658 53659 53660 53661 53662 53663 53664 53665 53666 53667 53668 53669 53670 53671 53672 53673 53674 53675 53676 53677 53678 53679 53680 53681 53682 53683 53684 53685 53686 53687 53688 53689 53690 53691 53692 53693 53694 53695 53696 53697 53698 53699 53700 53701 53702 53703 53704 53705 53706 53707 53708 53709 53710 53711 53712 53713 53714 53715 53716 53717 53718 53719 53720 53721 53722 53723 53724 53725 53726 53727 53728 53729 53730 53731 53732 53733 53734 53735 53736 53737 53738 53739 53740 53741 53742 53743 53744 53745 53746 53747 53748 53749 53750 53751 53752 53753 53754 53755 53756 53757 53758 53759 53760 53761 53762 53763 53764 53765 53766 53767 53768 53769 53770 53771 53772 53773 53774 53775 53776 53777 53778 53779 53780 53781 53782 53783 53784 53785 53786 53787 53788 53789 53790 53791 53792 53793 53794 53795 53796 53797 53798 53799 53800 53801 53802 53803 53804 53805 53806 53807 53808 53809 53810 53811 53812 53813 53814 53815 53816 53817 53818 53819 53820 53821 53822 53823 53824 53825 53826 53827 53828 53829 53830 53831 53832 53833 53834 53835 53836 53837 53838 53839 53840 53841 53842 53843 53844 53845 53846 53847 53848 53849 53850 53851 53852 53853 53854 53855 53856 53857 53858 53859 53860 53861 53862 53863 53864 53865 53866 53867 53868 53869 53870 53871 53872 53873 53874 53875 53876 53877 53878 53879 53880 53881 53882 53883 53884 53885 53886 53887 53888 53889 53890 53891 53892 53893 53894 53895 53896 53897 53898 53899 53900 53901 53902 53903 53904 53905 53906 53907 53908 53909 53910 53911 53912 53913 53914 53915 53916 53917 53918 53919 53920 53921 53922 53923 53924 53925 53926 53927 53928 53929 53930 53931 53932 53933 53934 53935 53936 53937 53938 53939 53940 53941 53942 53943 53944 53945 53946 53947 53948 53949 53950 53951 53952 53953 53954 53955 53956 53957 53958 53959 53960 53961 53962 53963 53964 53965 53966 53967 53968 53969 53970 53971 53972 53973 53974 53975 53976 53977 53978 53979 53980 53981 53982 53983 53984 53985 53986 53987 53988 53989 53990 53991 53992 53993 53994 53995 53996 53997 53998 53999 54000
   *  var graph = require('ngraph.graph')();
   *  graph.addNode(1);     // graph has one node.
   *  graph.addLink(2, 3);  // now graph contains three nodes and one link.
   *
   */
  var ngraph_graph = createGraph;



  /**
   * Creates a new graph
   */
  function createGraph(options) {
    // Graph structure is maintained as dictionary of nodes
    // and array of links. Each node has 'links' property which
    // hold all links related to that node. And general links
    // array is used to speed up all links enumeration. This is inefficient
    // in terms of memory, but simplifies coding.
    options = options || {};
    if ('uniqueLinkId' in options) {
      console.warn(
        'ngraph.graph: Starting from version 0.14 `uniqueLinkId` is deprecated.\n' +
        'Use `multigraph` option instead\n',
        '\n',
        'Note: there is also change in default behavior: From now on each graph\n'+
        'is considered to be not a multigraph by default (each edge is unique).'
      );

      options.multigraph = options.uniqueLinkId;
    }

    // Dear reader, the non-multigraphs do not guarantee that there is only
    // one link for a given pair of node. When this option is set to false
    // we can save some memory and CPU (18% faster for non-multigraph);
    if (options.multigraph === undefined) options.multigraph = false;

    if (typeof Map !== 'function') {
      // TODO: Should we polyfill it ourselves? We don't use much operations there..
      throw new Error('ngraph.graph requires `Map` to be defined. Please polyfill it before using ngraph');
    } 

    var nodes = new Map();
    var links = [],
      // Hash of multi-edges. Used to track ids of edges between same nodes
      multiEdges = {},
      suspendEvents = 0,

      createLink = options.multigraph ? createUniqueLink : createSingleLink,

      // Our graph API provides means to listen to graph changes. Users can subscribe
      // to be notified about changes in the graph by using `on` method. However
      // in some cases they don't use it. To avoid unnecessary memory consumption
      // we will not record graph changes until we have at least one subscriber.
      // Code below supports this optimization.
      //
      // Accumulates all changes made during graph updates.
      // Each change element contains:
      //  changeType - one of the strings: 'add', 'remove' or 'update';
      //  node - if change is related to node this property is set to changed graph's node;
      //  link - if change is related to link this property is set to changed graph's link;
      changes = [],
      recordLinkChange = noop,
      recordNodeChange = noop,
      enterModification = noop,
      exitModification = noop;

    // this is our public API:
    var graphPart = {
      /**
       * Adds node to the graph. If node with given id already exists in the graph
       * its data is extended with whatever comes in 'data' argument.
       *
       * @param nodeId the node's identifier. A string or number is preferred.
       * @param [data] additional data for the node being added. If node already
       *   exists its data object is augmented with the new one.
       *
       * @return {node} The newly added node or node with given id if it already exists.
       */
      addNode: addNode,

      /**
       * Adds a link to the graph. The function always create a new
       * link between two nodes. If one of the nodes does not exists
       * a new node is created.
       *
       * @param fromId link start node id;
       * @param toId link end node id;
       * @param [data] additional data to be set on the new link;
       *
       * @return {link} The newly created link
       */
      addLink: addLink,

      /**
       * Removes link from the graph. If link does not exist does nothing.
       *
       * @param link - object returned by addLink() or getLinks() methods.
       *
       * @returns true if link was removed; false otherwise.
       */
      removeLink: removeLink,

      /**
       * Removes node with given id from the graph. If node does not exist in the graph
       * does nothing.
       *
       * @param nodeId node's identifier passed to addNode() function.
       *
       * @returns true if node was removed; false otherwise.
       */
      removeNode: removeNode,

      /**
       * Gets node with given identifier. If node does not exist undefined value is returned.
       *
       * @param nodeId requested node identifier;
       *
       * @return {node} in with requested identifier or undefined if no such node exists.
       */
      getNode: getNode,

      /**
       * Gets number of nodes in this graph.
       *
       * @return number of nodes in the graph.
       */
      getNodeCount: getNodeCount,

      /**
       * Gets total number of links in the graph.
       */
      getLinkCount: getLinkCount,

      /**
       * Synonym for `getLinkCount()`
       */
      getLinksCount: getLinkCount,
      
      /**
       * Synonym for `getNodeCount()`
       */
      getNodesCount: getNodeCount,

      /**
       * Gets all links (inbound and outbound) from the node with given id.
       * If node with given id is not found null is returned.
       *
       * @param nodeId requested node identifier.
       *
       * @return Array of links from and to requested node if such node exists;
       *   otherwise null is returned.
       */
      getLinks: getLinks,

      /**
       * Invokes callback on each node of the graph.
       *
       * @param {Function(node)} callback Function to be invoked. The function
       *   is passed one argument: visited node.
       */
      forEachNode: forEachNode,

      /**
       * Invokes callback on every linked (adjacent) node to the given one.
       *
       * @param nodeId Identifier of the requested node.
       * @param {Function(node, link)} callback Function to be called on all linked nodes.
       *   The function is passed two parameters: adjacent node and link object itself.
       * @param oriented if true graph treated as oriented.
       */
      forEachLinkedNode: forEachLinkedNode,

      /**
       * Enumerates all links in the graph
       *
       * @param {Function(link)} callback Function to be called on all links in the graph.
       *   The function is passed one parameter: graph's link object.
       *
       * Link object contains at least the following fields:
       *  fromId - node id where link starts;
       *  toId - node id where link ends,
       *  data - additional data passed to graph.addLink() method.
       */
      forEachLink: forEachLink,

      /**
       * Suspend all notifications about graph changes until
       * endUpdate is called.
       */
      beginUpdate: enterModification,

      /**
       * Resumes all notifications about graph changes and fires
       * graph 'changed' event in case there are any pending changes.
       */
      endUpdate: exitModification,

      /**
       * Removes all nodes and links from the graph.
       */
      clear: clear,

      /**
       * Detects whether there is a link between two nodes.
       * Operation complexity is O(n) where n - number of links of a node.
       * NOTE: this function is synonim for getLink()
       *
       * @returns link if there is one. null otherwise.
       */
      hasLink: getLink,

      /**
       * Detects whether there is a node with given id
       * 
       * Operation complexity is O(1)
       * NOTE: this function is synonim for getNode()
       *
       * @returns node if there is one; Falsy value otherwise.
       */
      hasNode: getNode,

      /**
       * Gets an edge between two nodes.
       * Operation complexity is O(n) where n - number of links of a node.
       *
       * @param {string} fromId link start identifier
       * @param {string} toId link end identifier
       *
       * @returns link if there is one. null otherwise.
       */
      getLink: getLink
    };

    // this will add `on()` and `fire()` methods.
    ngraph_events(graphPart);

    monitorSubscribers();

    return graphPart;

    function monitorSubscribers() {
      var realOn = graphPart.on;

      // replace real `on` with our temporary on, which will trigger change
      // modification monitoring:
      graphPart.on = on;

      function on() {
        // now it's time to start tracking stuff:
        graphPart.beginUpdate = enterModification = enterModificationReal;
        graphPart.endUpdate = exitModification = exitModificationReal;
        recordLinkChange = recordLinkChangeReal;
        recordNodeChange = recordNodeChangeReal;

        // this will replace current `on` method with real pub/sub from `eventify`.
        graphPart.on = realOn;
        // delegate to real `on` handler:
        return realOn.apply(graphPart, arguments);
      }
    }

    function recordLinkChangeReal(link, changeType) {
      changes.push({
        link: link,
        changeType: changeType
      });
    }

    function recordNodeChangeReal(node, changeType) {
      changes.push({
        node: node,
        changeType: changeType
      });
    }

    function addNode(nodeId, data) {
      if (nodeId === undefined) {
        throw new Error('Invalid node identifier');
      }

      enterModification();

      var node = getNode(nodeId);
      if (!node) {
        node = new Node$1(nodeId, data);
        recordNodeChange(node, 'add');
      } else {
        node.data = data;
        recordNodeChange(node, 'update');
      }

      nodes.set(nodeId, node);

      exitModification();
      return node;
    }

    function getNode(nodeId) {
      return nodes.get(nodeId);
    }

    function removeNode(nodeId) {
      var node = getNode(nodeId);
      if (!node) {
        return false;
      }

      enterModification();

      var prevLinks = node.links;
      if (prevLinks) {
        node.links = null;
        for(var i = 0; i < prevLinks.length; ++i) {
          removeLink(prevLinks[i]);
        }
      }

      nodes.delete(nodeId);

      recordNodeChange(node, 'remove');

      exitModification();

      return true;
    }


    function addLink(fromId, toId, data) {
      enterModification();

      var fromNode = getNode(fromId) || addNode(fromId);
      var toNode = getNode(toId) || addNode(toId);

      var link = createLink(fromId, toId, data);

      links.push(link);

      // TODO: this is not cool. On large graphs potentially would consume more memory.
      addLinkToNode(fromNode, link);
      if (fromId !== toId) {
        // make sure we are not duplicating links for self-loops
        addLinkToNode(toNode, link);
      }

      recordLinkChange(link, 'add');

      exitModification();

      return link;
    }

    function createSingleLink(fromId, toId, data) {
      var linkId = makeLinkId(fromId, toId);
      return new Link(fromId, toId, data, linkId);
    }

    function createUniqueLink(fromId, toId, data) {
      // TODO: Get rid of this method.
      var linkId = makeLinkId(fromId, toId);
      var isMultiEdge = multiEdges.hasOwnProperty(linkId);
      if (isMultiEdge || getLink(fromId, toId)) {
        if (!isMultiEdge) {
          multiEdges[linkId] = 0;
        }
        var suffix = '@' + (++multiEdges[linkId]);
        linkId = makeLinkId(fromId + suffix, toId + suffix);
      }

      return new Link(fromId, toId, data, linkId);
    }

    function getNodeCount() {
      return nodes.size;
    }

    function getLinkCount() {
      return links.length;
    }

    function getLinks(nodeId) {
      var node = getNode(nodeId);
      return node ? node.links : null;
    }

    function removeLink(link) {
      if (!link) {
        return false;
      }
      var idx = indexOfElementInArray(link, links);
      if (idx < 0) {
        return false;
      }

      enterModification();

      links.splice(idx, 1);

      var fromNode = getNode(link.fromId);
      var toNode = getNode(link.toId);

      if (fromNode) {
        idx = indexOfElementInArray(link, fromNode.links);
        if (idx >= 0) {
          fromNode.links.splice(idx, 1);
        }
      }

      if (toNode) {
        idx = indexOfElementInArray(link, toNode.links);
        if (idx >= 0) {
          toNode.links.splice(idx, 1);
        }
      }

      recordLinkChange(link, 'remove');

      exitModification();

      return true;
    }

    function getLink(fromNodeId, toNodeId) {
      // TODO: Use sorted links to speed this up
      var node = getNode(fromNodeId),
        i;
      if (!node || !node.links) {
        return null;
      }

      for (i = 0; i < node.links.length; ++i) {
        var link = node.links[i];
        if (link.fromId === fromNodeId && link.toId === toNodeId) {
          return link;
        }
      }

      return null; // no link.
    }

    function clear() {
      enterModification();
      forEachNode(function(node) {
        removeNode(node.id);
      });
      exitModification();
    }

    function forEachLink(callback) {
      var i, length;
      if (typeof callback === 'function') {
        for (i = 0, length = links.length; i < length; ++i) {
          callback(links[i]);
        }
      }
    }

    function forEachLinkedNode(nodeId, callback, oriented) {
      var node = getNode(nodeId);

      if (node && node.links && typeof callback === 'function') {
        if (oriented) {
          return forEachOrientedLink(node.links, nodeId, callback);
        } else {
          return forEachNonOrientedLink(node.links, nodeId, callback);
        }
      }
    }

    function forEachNonOrientedLink(links, nodeId, callback) {
      var quitFast;
      for (var i = 0; i < links.length; ++i) {
        var link = links[i];
        var linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId;

        quitFast = callback(nodes.get(linkedNodeId), link);
        if (quitFast) {
          return true; // Client does not need more iterations. Break now.
        }
      }
    }

    function forEachOrientedLink(links, nodeId, callback) {
      var quitFast;
      for (var i = 0; i < links.length; ++i) {
        var link = links[i];
        if (link.fromId === nodeId) {
          quitFast = callback(nodes.get(link.toId), link);
          if (quitFast) {
            return true; // Client does not need more iterations. Break now.
          }
        }
      }
    }

    // we will not fire anything until users of this library explicitly call `on()`
    // method.
    function noop() {}

    // Enter, Exit modification allows bulk graph updates without firing events.
    function enterModificationReal() {
      suspendEvents += 1;
    }

    function exitModificationReal() {
      suspendEvents -= 1;
      if (suspendEvents === 0 && changes.length > 0) {
        graphPart.fire('changed', changes);
        changes.length = 0;
      }
    }

    function forEachNode(callback) {
      if (typeof callback !== 'function') {
        throw new Error('Function is expected to iterate over graph nodes. You passed ' + callback);
      }

      var valuesIterator = nodes.values();
      var nextValue = valuesIterator.next();
      while (!nextValue.done) {
        if (callback(nextValue.value)) {
          return true; // client doesn't want to proceed. Return.
        }
        nextValue = valuesIterator.next();
      }
    }
  }

  // need this for old browsers. Should this be a separate module?
  function indexOfElementInArray(element, array) {
    if (!array) return -1;

    if (array.indexOf) {
      return array.indexOf(element);
    }

    var len = array.length,
      i;

    for (i = 0; i < len; i += 1) {
      if (array[i] === element) {
        return i;
      }
    }

    return -1;
  }

  /**
   * Internal structure to represent node;
   */
  function Node$1(id, data) {
    this.id = id;
    this.links = null;
    this.data = data;
  }

  function addLinkToNode(node, link) {
    if (node.links) {
      node.links.push(link);
    } else {
      node.links = [link];
    }
  }

  /**
   * Internal structure to represent links;
   */
  function Link(fromId, toId, data, id) {
    this.fromId = fromId;
    this.toId = toId;
    this.data = data;
    this.id = id;
  }

  function makeLinkId(fromId, toId) {
    return fromId.toString() + '👉 ' + toId.toString();
  }

  var spring = Spring;

  /**
   * Represents a physical spring. Spring connects two bodies, has rest length
   * stiffness coefficient and optional weight
   */
  function Spring(fromBody, toBody, length, coeff) {
      this.from = fromBody;
      this.to = toBody;
      this.length = length;
      this.coeff = coeff;
  }

  var ngraph_expose = exposeProperties;

  /**
   * Augments `target` object with getter/setter functions, which modify settings
   *
   * @example
   *  var target = {};
   *  exposeProperties({ age: 42}, target);
   *  target.age(); // returns 42
   *  target.age(24); // make age 24;
   *
   *  var filteredTarget = {};
   *  exposeProperties({ age: 42, name: 'John'}, filteredTarget, ['name']);
   *  filteredTarget.name(); // returns 'John'
   *  filteredTarget.age === undefined; // true
   */
  function exposeProperties(settings, target, filter) {
    var needsFilter = Object.prototype.toString.call(filter) === '[object Array]';
    if (needsFilter) {
      for (var i = 0; i < filter.length; ++i) {
        augment(settings, target, filter[i]);
      }
    } else {
      for (var key in settings) {
        augment(settings, target, key);
      }
    }
  }

  function augment(source, target, key) {
    if (source.hasOwnProperty(key)) {
      if (typeof target[key] === 'function') {
        // this accessor is already defined. Ignore it
        return;
      }
      target[key] = function (value) {
        if (value !== undefined) {
          source[key] = value;
          return target;
        }
        return source[key];
      };
    }
  }

  var ngraph_merge = merge;

  /**
   * Augments `target` with properties in `options`. Does not override
   * target's properties if they are defined and matches expected type in 
   * options
   *
   * @returns {Object} merged object
   */
  function merge(target, options) {
    var key;
    if (!target) { target = {}; }
    if (options) {
      for (key in options) {
        if (options.hasOwnProperty(key)) {
          var targetHasIt = target.hasOwnProperty(key),
              optionsValueType = typeof options[key],
              shouldReplace = !targetHasIt || (typeof target[key] !== optionsValueType);

          if (shouldReplace) {
            target[key] = options[key];
          } else if (optionsValueType === 'object') {
            // go deep, don't care about loops here, we are simple API!:
            target[key] = merge(target[key], options[key]);
          }
        }
      }
    }

    return target;
  }

  function createCommonjsModule(fn, basedir, module) {
  	return module = {
  	  path: basedir,
  	  exports: {},
  	  require: function (path, base) {
        return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
      }
  	}, fn(module, module.exports), module.exports;
  }

  function commonjsRequire () {
  	throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
  }

  var ngraph_random = createCommonjsModule(function (module) {
  module.exports = random;

  // TODO: Deprecate?
  module.exports.random = random,
  module.exports.randomIterator = randomIterator;

  /**
   * Creates seeded PRNG with two methods:
   *   next() and nextDouble()
   */
  function random(inputSeed) {
    var seed = typeof inputSeed === 'number' ? inputSeed : (+new Date());
    return new Generator(seed)
  }

  function Generator(seed) {
    this.seed = seed;
  }

  /**
    * Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive)
    *
    * @param maxValue Number REQUIRED. Omitting this number will result in NaN values from PRNG.
    */
  Generator.prototype.next = next;

  /**
    * Generates random double number in the range from 0 (inclusive) to 1 (exclusive)
    * This function is the same as Math.random() (except that it could be seeded)
    */
  Generator.prototype.nextDouble = nextDouble;

  /**
   * Returns a random real number uniformly in [0, 1)
   */
  Generator.prototype.uniform = nextDouble;

  Generator.prototype.gaussian = gaussian;

  function gaussian() {
    // use the polar form of the Box-Muller transform
    // based on https://introcs.cs.princeton.edu/java/23recursion/StdRandom.java
    var r, x, y;
    do {
      x = this.nextDouble() * 2 - 1;
      y = this.nextDouble() * 2 - 1;
      r = x * x + y * y;
    } while (r >= 1 || r === 0);

    return x * Math.sqrt(-2 * Math.log(r)/r);
  }

  function nextDouble() {
    var seed = this.seed;
    // Robert Jenkins' 32 bit integer hash function.
    seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
    seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
    seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff;
    seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff;
    seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
    seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
    this.seed = seed;
    return (seed & 0xfffffff) / 0x10000000;
  }

  function next(maxValue) {
    return Math.floor(this.nextDouble() * maxValue);
  }

  /*
   * Creates iterator over array, which returns items of array in random order
   * Time complexity is guaranteed to be O(n);
   */
  function randomIterator(array, customRandom) {
    var localRandom = customRandom || random();
    if (typeof localRandom.next !== 'function') {
      throw new Error('customRandom does not match expected API: next() function is missing');
    }

    return {
      forEach: forEach,

      /**
       * Shuffles array randomly, in place.
       */
      shuffle: shuffle
    };

    function shuffle() {
      var i, j, t;
      for (i = array.length - 1; i > 0; --i) {
        j = localRandom.next(i + 1); // i inclusive
        t = array[j];
        array[j] = array[i];
        array[i] = t;
      }

      return array;
    }

    function forEach(callback) {
      var i, j, t;
      for (i = array.length - 1; i > 0; --i) {
        j = localRandom.next(i + 1); // i inclusive
        t = array[j];
        array[j] = array[i];
        array[i] = t;

        callback(t);
      }

      if (array.length) {
        callback(array[0]);
      }
    }
  }
  });

  /**
   * Internal data structure to represent 2D QuadTree node
   */
  var node = function Node() {
    // body stored inside this node. In quad tree only leaf nodes (by construction)
    // contain boides:
    this.body = null;

    // Child nodes are stored in quads. Each quad is presented by number:
    // 0 | 1
    // -----
    // 2 | 3
    this.quad0 = null;
    this.quad1 = null;
    this.quad2 = null;
    this.quad3 = null;

    // Total mass of current node
    this.mass = 0;

    // Center of mass coordinates
    this.massX = 0;
    this.massY = 0;

    // bounding box coordinates
    this.left = 0;
    this.top = 0;
    this.bottom = 0;
    this.right = 0;
  };

  var insertStack = InsertStack;

  /**
   * Our implmentation of QuadTree is non-recursive to avoid GC hit
   * This data structure represent stack of elements
   * which we are trying to insert into quad tree.
   */
  function InsertStack () {
      this.stack = [];
      this.popIdx = 0;
  }

  InsertStack.prototype = {
      isEmpty: function() {
          return this.popIdx === 0;
      },
      push: function (node, body) {
          var item = this.stack[this.popIdx];
          if (!item) {
              // we are trying to avoid memory pressue: create new element
              // only when absolutely necessary
              this.stack[this.popIdx] = new InsertStackElement(node, body);
          } else {
              item.node = node;
              item.body = body;
          }
          ++this.popIdx;
      },
      pop: function () {
          if (this.popIdx > 0) {
              return this.stack[--this.popIdx];
          }
      },
      reset: function () {
          this.popIdx = 0;
      }
  };

  function InsertStackElement(node, body) {
      this.node = node; // QuadTree node
      this.body = body; // physical body which needs to be inserted to node
  }

  var isSamePosition = function isSamePosition(point1, point2) {
      var dx = Math.abs(point1.x - point2.x);
      var dy = Math.abs(point1.y - point2.y);

      return (dx < 1e-8 && dy < 1e-8);
  };

  /**
   * This is Barnes Hut simulation algorithm for 2d case. Implementation
   * is highly optimized (avoids recusion and gc pressure)
   *
   * http://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/barnes-hut.html
   */

  var ngraph_quadtreebh = function(options) {
    options = options || {};
    options.gravity = typeof options.gravity === 'number' ? options.gravity : -1;
    options.theta = typeof options.theta === 'number' ? options.theta : 0.8;

    // we require deterministic randomness here
    var random = ngraph_random.random(1984),
      Node = node,
      InsertStack = insertStack,
      isSamePosition$1 = isSamePosition;

    var gravity = options.gravity,
      updateQueue = [],
      insertStack$1 = new InsertStack(),
      theta = options.theta,

      nodesCache = [],
      currentInCache = 0,
      root = newNode();

    return {
      insertBodies: insertBodies,
      /**
       * Gets root node if its present
       */
      getRoot: function() {
        return root;
      },
      updateBodyForce: update,
      options: function(newOptions) {
        if (newOptions) {
          if (typeof newOptions.gravity === 'number') {
            gravity = newOptions.gravity;
          }
          if (typeof newOptions.theta === 'number') {
            theta = newOptions.theta;
          }

          return this;
        }

        return {
          gravity: gravity,
          theta: theta
        };
      }
    };

    function newNode() {
      // To avoid pressure on GC we reuse nodes.
      var node = nodesCache[currentInCache];
      if (node) {
        node.quad0 = null;
        node.quad1 = null;
        node.quad2 = null;
        node.quad3 = null;
        node.body = null;
        node.mass = node.massX = node.massY = 0;
        node.left = node.right = node.top = node.bottom = 0;
      } else {
        node = new Node();
        nodesCache[currentInCache] = node;
      }

      ++currentInCache;
      return node;
    }

    function update(sourceBody) {
      var queue = updateQueue,
        v,
        dx,
        dy,
        r, fx = 0,
        fy = 0,
        queueLength = 1,
        shiftIdx = 0,
        pushIdx = 1;

      queue[0] = root;

      while (queueLength) {
        var node = queue[shiftIdx],
          body = node.body;

        queueLength -= 1;
        shiftIdx += 1;
        var differentBody = (body !== sourceBody);
        if (body && differentBody) {
          // If the current node is a leaf node (and it is not source body),
          // calculate the force exerted by the current node on body, and add this
          // amount to body's net force.
          dx = body.pos.x - sourceBody.pos.x;
          dy = body.pos.y - sourceBody.pos.y;
          r = Math.sqrt(dx * dx + dy * dy);

          if (r === 0) {
            // Poor man's protection against zero distance.
            dx = (random.nextDouble() - 0.5) / 50;
            dy = (random.nextDouble() - 0.5) / 50;
            r = Math.sqrt(dx * dx + dy * dy);
          }

          // This is standard gravition force calculation but we divide
          // by r^3 to save two operations when normalizing force vector.
          v = gravity * body.mass * sourceBody.mass / (r * r * r);
          fx += v * dx;
          fy += v * dy;
        } else if (differentBody) {
          // Otherwise, calculate the ratio s / r,  where s is the width of the region
          // represented by the internal node, and r is the distance between the body