_createClusterEdges(
    childNodesObj,
    childEdgesObj,
    clusterNodeProperties,
    clusterEdgeProperties
  ) {
    let edge, childNodeId, childNode, toId, fromId, otherNodeId;

    // loop over all child nodes and their edges to find edges going out of the cluster
    // these edges will be replaced by clusterEdges.
    const childKeys = Object.keys(childNodesObj);
    const createEdges = [];
    for (let i = 0; i < childKeys.length; i++) {
      childNodeId = childKeys[i];
      childNode = childNodesObj[childNodeId];

      // construct new edges from the cluster to others
      for (let j = 0; j < childNode.edges.length; j++) {
        edge = childNode.edges[j];
        // we only handle edges that are visible to the system, not the disabled ones from the clustering process.
        if (this.clusteredEdges[edge.id] === undefined) {
          // self-referencing edges will be added to the "hidden" list
          if (edge.toId == edge.fromId) {
            childEdgesObj[edge.id] = edge;
          } else {
            // set up the from and to.
            if (edge.toId == childNodeId) {
              // this is a double equals because ints and strings can be interchanged here.
              toId = clusterNodeProperties.id;
              fromId = edge.fromId;
              otherNodeId = fromId;
            } else {
              toId = edge.toId;
              fromId = clusterNodeProperties.id;
              otherNodeId = toId;
            }
          }

          // Only edges from the cluster outwards are being replaced.
          if (childNodesObj[otherNodeId] === undefined) {
            createEdges.push({ edge: edge, fromId: fromId, toId: toId });
          }
        }
      }
    }

    //
    // Here we actually create the replacement edges.
    //
    // We could not do this in the loop above as the creation process
    // would add an edge to the edges array we are iterating over.
    //
    // NOTE: a clustered edge can have multiple base edges!
    //
    const newEdges = [];

    /**
     * Find a cluster edge which matches the given created edge.
     * @param {vis.Edge} createdEdge
     * @returns {vis.Edge}
     */
    const getNewEdge = function (createdEdge) {
      for (let j = 0; j < newEdges.length; j++) {
        const newEdge = newEdges[j];

        // We replace both to and from edges with a single cluster edge
        const matchToDirection =
          createdEdge.fromId === newEdge.fromId &&
          createdEdge.toId === newEdge.toId;
        const matchFromDirection =
          createdEdge.fromId === newEdge.toId &&
          createdEdge.toId === newEdge.fromId;

        if (matchToDirection || matchFromDirection) {
          return newEdge;
        }
      }

      return null;
    };

    for (let j = 0; j < createEdges.length; j++) {
      const createdEdge = createEdges[j];
      const edge = createdEdge.edge;
      let newEdge = getNewEdge(createdEdge);

      if (newEdge === null) {
        // Create a clustered edge for this connection
        newEdge = this._createClusteredEdge(
          createdEdge.fromId,
          createdEdge.toId,
          edge,
          clusterEdgeProperties
        );

        newEdges.push(newEdge);
      } else {
        newEdge.clusteringEdgeReplacingIds.push(edge.id);
      }

      // also reference the new edge in the old edge
      this.body.edges[edge.id].edgeReplacedById = newEdge.id;

      // hide the replaced edge
      this._backupEdgeOptions(edge);
      edge.setOptions({ physics: false });
    }
  }
This function creates the edges that will be attached to the cluster
It looks for edges that are connected to the nodes from the "outside' of the cluster.
@param {{Node.id: vis.Node}} childNodesObj
@param {{vis.Edge.id: vis.Edge}} childEdgesObj
@param {object} clusterNodeProperties
@param {object} clusterEdgeProperties
@private