UNPKG

@copyfactory/alpine-flow

Version:

Alpine Flow makes creating directed step based flowcharts and node based workflow UIs (DAG) in AlpineJS an easier task.

2,047 lines (1,780 loc) 264 kB
/** * Converts an element to a Node to be used in the diagram. Attaches state properties. * @param {Object} currentNodeData - The data representing the current node. * @param {string} [currentNodeData.id=null] - The ID of the node. * @param {Object} [currentNodeData.data={}] - Additional data associated with the node. * @param {Object} [currentNodeData.position={x: 0, y: 0}] - The position of the node. * @param {string} [currentNodeData.type=''] - The type of the node. * @param {boolean} [currentNodeData.selected=false] - Indicates whether the node is selected. * @param {boolean} [currentNodeData.selectable=true] - Indicates whether the node is selectable. * @param {boolean} [currentNodeData.deletable=true] - Indicates whether the node is deletable. * @param {boolean} [currentNodeData.allowBranching=true] - Indicates whether the node allows branching. * @param {boolean} [currentNodeData.allowChildren=true] - Indicates whether the node allows children nodes. * @param {string} [currentNodeData.className=''] - The CSS class name of the node. * @param {number} [currentNodeData.width=0] - The width of the node. * @param {number} [currentNodeData.height=0] - The height of the node. * @returns {Object} - The complete node object with attached state properties. */ function getCompleteNode(currentNodeData) { let newNode = Alpine.reactive({ id: null, data: {}, position: { x: 0, y: 0 }, type: '', positionComputed: false, selected: false, selectable: true, deletable: true, allowBranching: false, allowChildren: true, width: 0, height: 0, ...currentNodeData, /** * Sets the computed width and height of the node. * @param {HTMLElement} ele - The element representing the node. */ setComputedWidthHeight(ele) { let styles = window.getComputedStyle(ele); this.width = parseFloat(styles.width); this.height = parseFloat(styles.height); }, toObject() { return { id: this.id, data: this.data, type: this.type, position: this.position, selected: this.selected, selectable: this.selectable, deletable: this.deletable, allowChildren: this.allowChildren, allowBranching: this.allowBranching, width: this.width, height: this.height }; } }); newNode.id = newNode.id.toString(); newNode.positionComputed = false; return newNode; } const nodeRegistry = { default: {} }; const node = Alpine => { Alpine.directive('node', (el, { value, expression, modifiers }, { evaluate }) => { if (!expression) { console.warn('Node not registered due to missing config. Modify to `x-node="{type: myNodeName}"`'); return; } let nodeConfig = evaluate(expression); if (!'type' in nodeConfig) { console.warn('Node not registered due to missing name. Modify to `x-node="{type: myNodeName}"`'); return; } el.setAttribute('x-ignore', true); el.removeAttribute('x-node'); if (!modifiers.length) { modifiers.push('default'); } modifiers.forEach(registryName => { if (!nodeRegistry.hasOwnProperty(registryName)) { nodeRegistry[registryName] = {}; } nodeRegistry[registryName][nodeConfig.type] = { ele: el, nodeConfig: nodeConfig }; }); el.remove(); }).before('ignore'); Alpine.magic('nodes', (el, { Alpine }) => { return nodeRegistry; }); }; /** * Clamps a value between a minimum and maximum. * @param {number} val - The value to clamp. * @param {number} [min=0] - The minimum value. * @param {number} [max=1] - The maximum value. * @returns {number} - The clamped value. */ const clamp = (val, min = 0, max = 1) => Math.min(Math.max(val, min), max); /** * Gets the first item of an array or returns null if the array is empty. * @param {Array} array - The input array. * @returns {*} - The first item of the array or null if the array is empty. */ function getFirstItemOrNull(array) { if (!array) { return null; } return array[0]; } /** * Calculates the center and offsets of an edge. * @param {Object} options - The options for calculating edge center. * @param {number} options.sourceX - The x-coordinate of the source node. * @param {number} options.sourceY - The y-coordinate of the source node. * @param {number} options.targetX - The x-coordinate of the target node. * @param {number} options.targetY - The y-coordinate of the target node. * @returns {Array} - The center x and y coordinates, and the offsets in x and y directions. */ function getEdgeCenter({ sourceX, sourceY, targetX, targetY }) { const xOffset = Math.abs(targetX - sourceX) / 2; const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset; const yOffset = Math.abs(targetY - sourceY) / 2; const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset; return [centerX, centerY, xOffset, yOffset]; } var DEFAULT_EDGE_NAME = "\x00"; var GRAPH_NODE = "\x00"; var EDGE_KEY_DELIM = "\x01"; // Implementation notes: // // * Node id query functions should return string ids for the nodes // * Edge id query functions should return an "edgeObj", edge object, that is // composed of enough information to uniquely identify an edge: {v, w, name}. // * Internally we use an "edgeId", a stringified form of the edgeObj, to // reference edges. This is because we need a performant way to look these // edges up and, object properties, which have string keys, are the closest // we're going to get to a performant hashtable in JavaScript. class Graph$a { _isDirected = true; _isMultigraph = false; _isCompound = false; // Label for the graph itself _label; // Defaults to be set when creating a new node _defaultNodeLabelFn = () => undefined; // Defaults to be set when creating a new edge _defaultEdgeLabelFn = () => undefined; // v -> label _nodes = {}; // v -> edgeObj _in = {}; // u -> v -> Number _preds = {}; // v -> edgeObj _out = {}; // v -> w -> Number _sucs = {}; // e -> edgeObj _edgeObjs = {}; // e -> label _edgeLabels = {}; /* Number of nodes in the graph. Should only be changed by the implementation. */ _nodeCount = 0; /* Number of edges in the graph. Should only be changed by the implementation. */ _edgeCount = 0; _parent; _children; constructor(opts) { if (opts) { this._isDirected = opts.hasOwnProperty("directed") ? opts.directed : true; this._isMultigraph = opts.hasOwnProperty("multigraph") ? opts.multigraph : false; this._isCompound = opts.hasOwnProperty("compound") ? opts.compound : false; } if (this._isCompound) { // v -> parent this._parent = {}; // v -> children this._children = {}; this._children[GRAPH_NODE] = {}; } } /* === Graph functions ========= */ /** * Whether graph was created with 'directed' flag set to true or not. */ isDirected() { return this._isDirected; } /** * Whether graph was created with 'multigraph' flag set to true or not. */ isMultigraph() { return this._isMultigraph; } /** * Whether graph was created with 'compound' flag set to true or not. */ isCompound() { return this._isCompound; } /** * Sets the label of the graph. */ setGraph(label) { this._label = label; return this; } /** * Gets the graph label. */ graph() { return this._label; } /* === Node functions ========== */ /** * Sets the default node label. If newDefault is a function, it will be * invoked ach time when setting a label for a node. Otherwise, this label * will be assigned as default label in case if no label was specified while * setting a node. * Complexity: O(1). */ setDefaultNodeLabel(newDefault) { this._defaultNodeLabelFn = newDefault; if (typeof newDefault !== 'function') { this._defaultNodeLabelFn = () => newDefault; } return this; } /** * Gets the number of nodes in the graph. * Complexity: O(1). */ nodeCount() { return this._nodeCount; } /** * Gets all nodes of the graph. Note, the in case of compound graph subnodes are * not included in list. * Complexity: O(1). */ nodes() { return Object.keys(this._nodes); } /** * Gets list of nodes without in-edges. * Complexity: O(|V|). */ sources() { var self = this; return this.nodes().filter(v => Object.keys(self._in[v]).length === 0); } /** * Gets list of nodes without out-edges. * Complexity: O(|V|). */ sinks() { var self = this; return this.nodes().filter(v => Object.keys(self._out[v]).length === 0); } /** * Invokes setNode method for each node in names list. * Complexity: O(|names|). */ setNodes(vs, value) { var args = arguments; var self = this; vs.forEach(function(v) { if (args.length > 1) { self.setNode(v, value); } else { self.setNode(v); } }); return this; } /** * Creates or updates the value for the node v in the graph. If label is supplied * it is set as the value for the node. If label is not supplied and the node was * created by this call then the default node label will be assigned. * Complexity: O(1). */ setNode(v, value) { if (this._nodes.hasOwnProperty(v)) { if (arguments.length > 1) { this._nodes[v] = value; } return this; } this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v); if (this._isCompound) { this._parent[v] = GRAPH_NODE; this._children[v] = {}; this._children[GRAPH_NODE][v] = true; } this._in[v] = {}; this._preds[v] = {}; this._out[v] = {}; this._sucs[v] = {}; ++this._nodeCount; return this; } /** * Gets the label of node with specified name. * Complexity: O(|V|). */ node(v) { return this._nodes[v]; } /** * Detects whether graph has a node with specified name or not. */ hasNode(v) { return this._nodes.hasOwnProperty(v); } /** * Remove the node with the name from the graph or do nothing if the node is not in * the graph. If the node was removed this function also removes any incident * edges. * Complexity: O(1). */ removeNode(v) { var self = this; if (this._nodes.hasOwnProperty(v)) { var removeEdge = e => self.removeEdge(self._edgeObjs[e]); delete this._nodes[v]; if (this._isCompound) { this._removeFromParentsChildList(v); delete this._parent[v]; this.children(v).forEach(function(child) { self.setParent(child); }); delete this._children[v]; } Object.keys(this._in[v]).forEach(removeEdge); delete this._in[v]; delete this._preds[v]; Object.keys(this._out[v]).forEach(removeEdge); delete this._out[v]; delete this._sucs[v]; --this._nodeCount; } return this; } /** * Sets node p as a parent for node v if it is defined, or removes the * parent for v if p is undefined. Method throws an exception in case of * invoking it in context of noncompound graph. * Average-case complexity: O(1). */ setParent(v, parent) { if (!this._isCompound) { throw new Error("Cannot set parent in a non-compound graph"); } if (parent === undefined) { parent = GRAPH_NODE; } else { // Coerce parent to string parent += ""; for (var ancestor = parent; ancestor !== undefined; ancestor = this.parent(ancestor)) { if (ancestor === v) { throw new Error("Setting " + parent+ " as parent of " + v + " would create a cycle"); } } this.setNode(parent); } this.setNode(v); this._removeFromParentsChildList(v); this._parent[v] = parent; this._children[parent][v] = true; return this; } _removeFromParentsChildList(v) { delete this._children[this._parent[v]][v]; } /** * Gets parent node for node v. * Complexity: O(1). */ parent(v) { if (this._isCompound) { var parent = this._parent[v]; if (parent !== GRAPH_NODE) { return parent; } } } /** * Gets list of direct children of node v. * Complexity: O(1). */ children(v = GRAPH_NODE) { if (this._isCompound) { var children = this._children[v]; if (children) { return Object.keys(children); } } else if (v === GRAPH_NODE) { return this.nodes(); } else if (this.hasNode(v)) { return []; } } /** * Return all nodes that are predecessors of the specified node or undefined if node v is not in * the graph. Behavior is undefined for undirected graphs - use neighbors instead. * Complexity: O(|V|). */ predecessors(v) { var predsV = this._preds[v]; if (predsV) { return Object.keys(predsV); } } /** * Return all nodes that are successors of the specified node or undefined if node v is not in * the graph. Behavior is undefined for undirected graphs - use neighbors instead. * Complexity: O(|V|). */ successors(v) { var sucsV = this._sucs[v]; if (sucsV) { return Object.keys(sucsV); } } /** * Return all nodes that are predecessors or successors of the specified node or undefined if * node v is not in the graph. * Complexity: O(|V|). */ neighbors(v) { var preds = this.predecessors(v); if (preds) { const union = new Set(preds); for (var succ of this.successors(v)) { union.add(succ); } return Array.from(union.values()); } } isLeaf(v) { var neighbors; if (this.isDirected()) { neighbors = this.successors(v); } else { neighbors = this.neighbors(v); } return neighbors.length === 0; } /** * Creates new graph with nodes filtered via filter. Edges incident to rejected node * are also removed. In case of compound graph, if parent is rejected by filter, * than all its children are rejected too. * Average-case complexity: O(|E|+|V|). */ filterNodes(filter) { var copy = new this.constructor({ directed: this._isDirected, multigraph: this._isMultigraph, compound: this._isCompound }); copy.setGraph(this.graph()); var self = this; Object.entries(this._nodes).forEach(function([v, value]) { if (filter(v)) { copy.setNode(v, value); } }); Object.values(this._edgeObjs).forEach(function(e) { if (copy.hasNode(e.v) && copy.hasNode(e.w)) { copy.setEdge(e, self.edge(e)); } }); var parents = {}; function findParent(v) { var parent = self.parent(v); if (parent === undefined || copy.hasNode(parent)) { parents[v] = parent; return parent; } else if (parent in parents) { return parents[parent]; } else { return findParent(parent); } } if (this._isCompound) { copy.nodes().forEach(v => copy.setParent(v, findParent(v))); } return copy; } /* === Edge functions ========== */ /** * Sets the default edge label or factory function. This label will be * assigned as default label in case if no label was specified while setting * an edge or this function will be invoked each time when setting an edge * with no label specified and returned value * will be used as a label for edge. * Complexity: O(1). */ setDefaultEdgeLabel(newDefault) { this._defaultEdgeLabelFn = newDefault; if (typeof newDefault !== 'function') { this._defaultEdgeLabelFn = () => newDefault; } return this; } /** * Gets the number of edges in the graph. * Complexity: O(1). */ edgeCount() { return this._edgeCount; } /** * Gets edges of the graph. In case of compound graph subgraphs are not considered. * Complexity: O(|E|). */ edges() { return Object.values(this._edgeObjs); } /** * Establish an edges path over the nodes in nodes list. If some edge is already * exists, it will update its label, otherwise it will create an edge between pair * of nodes with label provided or default label if no label provided. * Complexity: O(|nodes|). */ setPath(vs, value) { var self = this; var args = arguments; vs.reduce(function(v, w) { if (args.length > 1) { self.setEdge(v, w, value); } else { self.setEdge(v, w); } return w; }); return this; } /** * Creates or updates the label for the edge (v, w) with the optionally supplied * name. If label is supplied it is set as the value for the edge. If label is not * supplied and the edge was created by this call then the default edge label will * be assigned. The name parameter is only useful with multigraphs. */ setEdge() { var v, w, name, value; var valueSpecified = false; var arg0 = arguments[0]; if (typeof arg0 === "object" && arg0 !== null && "v" in arg0) { v = arg0.v; w = arg0.w; name = arg0.name; if (arguments.length === 2) { value = arguments[1]; valueSpecified = true; } } else { v = arg0; w = arguments[1]; name = arguments[3]; if (arguments.length > 2) { value = arguments[2]; valueSpecified = true; } } v = "" + v; w = "" + w; if (name !== undefined) { name = "" + name; } var e = edgeArgsToId(this._isDirected, v, w, name); if (this._edgeLabels.hasOwnProperty(e)) { if (valueSpecified) { this._edgeLabels[e] = value; } return this; } if (name !== undefined && !this._isMultigraph) { throw new Error("Cannot set a named edge when isMultigraph = false"); } // It didn't exist, so we need to create it. // First ensure the nodes exist. this.setNode(v); this.setNode(w); this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name); var edgeObj = edgeArgsToObj(this._isDirected, v, w, name); // Ensure we add undirected edges in a consistent way. v = edgeObj.v; w = edgeObj.w; Object.freeze(edgeObj); this._edgeObjs[e] = edgeObj; incrementOrInitEntry(this._preds[w], v); incrementOrInitEntry(this._sucs[v], w); this._in[w][e] = edgeObj; this._out[v][e] = edgeObj; this._edgeCount++; return this; } /** * Gets the label for the specified edge. * Complexity: O(1). */ edge(v, w, name) { var e = (arguments.length === 1 ? edgeObjToId(this._isDirected, arguments[0]) : edgeArgsToId(this._isDirected, v, w, name)); return this._edgeLabels[e]; } /** * Gets the label for the specified edge and converts it to an object. * Complexity: O(1) */ edgeAsObj() { const edge = this.edge(...arguments); if (typeof edge !== "object") { return {label: edge}; } return edge; } /** * Detects whether the graph contains specified edge or not. No subgraphs are considered. * Complexity: O(1). */ hasEdge(v, w, name) { var e = (arguments.length === 1 ? edgeObjToId(this._isDirected, arguments[0]) : edgeArgsToId(this._isDirected, v, w, name)); return this._edgeLabels.hasOwnProperty(e); } /** * Removes the specified edge from the graph. No subgraphs are considered. * Complexity: O(1). */ removeEdge(v, w, name) { var e = (arguments.length === 1 ? edgeObjToId(this._isDirected, arguments[0]) : edgeArgsToId(this._isDirected, v, w, name)); var edge = this._edgeObjs[e]; if (edge) { v = edge.v; w = edge.w; delete this._edgeLabels[e]; delete this._edgeObjs[e]; decrementOrRemoveEntry(this._preds[w], v); decrementOrRemoveEntry(this._sucs[v], w); delete this._in[w][e]; delete this._out[v][e]; this._edgeCount--; } return this; } /** * Return all edges that point to the node v. Optionally filters those edges down to just those * coming from node u. Behavior is undefined for undirected graphs - use nodeEdges instead. * Complexity: O(|E|). */ inEdges(v, u) { var inV = this._in[v]; if (inV) { var edges = Object.values(inV); if (!u) { return edges; } return edges.filter(edge => edge.v === u); } } /** * Return all edges that are pointed at by node v. Optionally filters those edges down to just * those point to w. Behavior is undefined for undirected graphs - use nodeEdges instead. * Complexity: O(|E|). */ outEdges(v, w) { var outV = this._out[v]; if (outV) { var edges = Object.values(outV); if (!w) { return edges; } return edges.filter(edge => edge.w === w); } } /** * Returns all edges to or from node v regardless of direction. Optionally filters those edges * down to just those between nodes v and w regardless of direction. * Complexity: O(|E|). */ nodeEdges(v, w) { var inEdges = this.inEdges(v, w); if (inEdges) { return inEdges.concat(this.outEdges(v, w)); } } } function incrementOrInitEntry(map, k) { if (map[k]) { map[k]++; } else { map[k] = 1; } } function decrementOrRemoveEntry(map, k) { if (!--map[k]) { delete map[k]; } } function edgeArgsToId(isDirected, v_, w_, name) { var v = "" + v_; var w = "" + w_; if (!isDirected && v > w) { var tmp = v; v = w; w = tmp; } return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM + (name === undefined ? DEFAULT_EDGE_NAME : name); } function edgeArgsToObj(isDirected, v_, w_, name) { var v = "" + v_; var w = "" + w_; if (!isDirected && v > w) { var tmp = v; v = w; w = tmp; } var edgeObj = { v: v, w: w }; if (name) { edgeObj.name = name; } return edgeObj; } function edgeObjToId(isDirected, edgeObj) { return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name); } var graph = Graph$a; var version$1 = '2.2.2'; // Includes only the "core" of graphlib var lib$1 = { Graph: graph, version: version$1 }; var Graph$9 = graph; var json = { write: write, read: read }; /** * Creates a JSON representation of the graph that can be serialized to a string with * JSON.stringify. The graph can later be restored using json.read. */ function write(g) { var json = { options: { directed: g.isDirected(), multigraph: g.isMultigraph(), compound: g.isCompound() }, nodes: writeNodes(g), edges: writeEdges(g) }; if (g.graph() !== undefined) { json.value = structuredClone(g.graph()); } return json; } function writeNodes(g) { return g.nodes().map(function(v) { var nodeValue = g.node(v); var parent = g.parent(v); var node = { v: v }; if (nodeValue !== undefined) { node.value = nodeValue; } if (parent !== undefined) { node.parent = parent; } return node; }); } function writeEdges(g) { return g.edges().map(function(e) { var edgeValue = g.edge(e); var edge = { v: e.v, w: e.w }; if (e.name !== undefined) { edge.name = e.name; } if (edgeValue !== undefined) { edge.value = edgeValue; } return edge; }); } /** * Takes JSON as input and returns the graph representation. * * @example * var g2 = graphlib.json.read(JSON.parse(str)); * g2.nodes(); * // ['a', 'b'] * g2.edges() * // [ { v: 'a', w: 'b' } ] */ function read(json) { var g = new Graph$9(json.options).setGraph(json.value); json.nodes.forEach(function(entry) { g.setNode(entry.v, entry.value); if (entry.parent) { g.setParent(entry.v, entry.parent); } }); json.edges.forEach(function(entry) { g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value); }); return g; } var components_1 = components; function components(g) { var visited = {}; var cmpts = []; var cmpt; function dfs(v) { if (visited.hasOwnProperty(v)) return; visited[v] = true; cmpt.push(v); g.successors(v).forEach(dfs); g.predecessors(v).forEach(dfs); } g.nodes().forEach(function(v) { cmpt = []; dfs(v); if (cmpt.length) { cmpts.push(cmpt); } }); return cmpts; } /** * A min-priority queue data structure. This algorithm is derived from Cormen, * et al., "Introduction to Algorithms". The basic idea of a min-priority * queue is that you can efficiently (in O(1) time) get the smallest key in * the queue. Adding and removing elements takes O(log n) time. A key can * have its priority decreased in O(log n) time. */ class PriorityQueue$2 { _arr = []; _keyIndices = {}; /** * Returns the number of elements in the queue. Takes `O(1)` time. */ size() { return this._arr.length; } /** * Returns the keys that are in the queue. Takes `O(n)` time. */ keys() { return this._arr.map(function(x) { return x.key; }); } /** * Returns `true` if **key** is in the queue and `false` if not. */ has(key) { return this._keyIndices.hasOwnProperty(key); } /** * Returns the priority for **key**. If **key** is not present in the queue * then this function returns `undefined`. Takes `O(1)` time. * * @param {Object} key */ priority(key) { var index = this._keyIndices[key]; if (index !== undefined) { return this._arr[index].priority; } } /** * Returns the key for the minimum element in this queue. If the queue is * empty this function throws an Error. Takes `O(1)` time. */ min() { if (this.size() === 0) { throw new Error("Queue underflow"); } return this._arr[0].key; } /** * Inserts a new key into the priority queue. If the key already exists in * the queue this function returns `false`; otherwise it will return `true`. * Takes `O(n)` time. * * @param {Object} key the key to add * @param {Number} priority the initial priority for the key */ add(key, priority) { var keyIndices = this._keyIndices; key = String(key); if (!keyIndices.hasOwnProperty(key)) { var arr = this._arr; var index = arr.length; keyIndices[key] = index; arr.push({key: key, priority: priority}); this._decrease(index); return true; } return false; } /** * Removes and returns the smallest key in the queue. Takes `O(log n)` time. */ removeMin() { this._swap(0, this._arr.length - 1); var min = this._arr.pop(); delete this._keyIndices[min.key]; this._heapify(0); return min.key; } /** * Decreases the priority for **key** to **priority**. If the new priority is * greater than the previous priority, this function will throw an Error. * * @param {Object} key the key for which to raise priority * @param {Number} priority the new priority for the key */ decrease(key, priority) { var index = this._keyIndices[key]; if (priority > this._arr[index].priority) { throw new Error("New priority is greater than current priority. " + "Key: " + key + " Old: " + this._arr[index].priority + " New: " + priority); } this._arr[index].priority = priority; this._decrease(index); } _heapify(i) { var arr = this._arr; var l = 2 * i; var r = l + 1; var largest = i; if (l < arr.length) { largest = arr[l].priority < arr[largest].priority ? l : largest; if (r < arr.length) { largest = arr[r].priority < arr[largest].priority ? r : largest; } if (largest !== i) { this._swap(i, largest); this._heapify(largest); } } } _decrease(index) { var arr = this._arr; var priority = arr[index].priority; var parent; while (index !== 0) { parent = index >> 1; if (arr[parent].priority < priority) { break; } this._swap(index, parent); index = parent; } } _swap(i, j) { var arr = this._arr; var keyIndices = this._keyIndices; var origArrI = arr[i]; var origArrJ = arr[j]; arr[i] = origArrJ; arr[j] = origArrI; keyIndices[origArrJ.key] = i; keyIndices[origArrI.key] = j; } } var priorityQueue = PriorityQueue$2; var PriorityQueue$1 = priorityQueue; var dijkstra_1 = dijkstra$1; var DEFAULT_WEIGHT_FUNC$1 = () => 1; function dijkstra$1(g, source, weightFn, edgeFn) { return runDijkstra(g, String(source), weightFn || DEFAULT_WEIGHT_FUNC$1, edgeFn || function(v) { return g.outEdges(v); }); } function runDijkstra(g, source, weightFn, edgeFn) { var results = {}; var pq = new PriorityQueue$1(); var v, vEntry; var updateNeighbors = function(edge) { var w = edge.v !== v ? edge.v : edge.w; var wEntry = results[w]; var weight = weightFn(edge); var distance = vEntry.distance + weight; if (weight < 0) { throw new Error("dijkstra does not allow negative edge weights. " + "Bad edge: " + edge + " Weight: " + weight); } if (distance < wEntry.distance) { wEntry.distance = distance; wEntry.predecessor = v; pq.decrease(w, distance); } }; g.nodes().forEach(function(v) { var distance = v === source ? 0 : Number.POSITIVE_INFINITY; results[v] = { distance: distance }; pq.add(v, distance); }); while (pq.size() > 0) { v = pq.removeMin(); vEntry = results[v]; if (vEntry.distance === Number.POSITIVE_INFINITY) { break; } edgeFn(v).forEach(updateNeighbors); } return results; } var dijkstra = dijkstra_1; var dijkstraAll_1 = dijkstraAll; function dijkstraAll(g, weightFunc, edgeFunc) { return g.nodes().reduce(function(acc, v) { acc[v] = dijkstra(g, v, weightFunc, edgeFunc); return acc; }, {}); } var tarjan_1 = tarjan$1; function tarjan$1(g) { var index = 0; var stack = []; var visited = {}; // node id -> { onStack, lowlink, index } var results = []; function dfs(v) { var entry = visited[v] = { onStack: true, lowlink: index, index: index++ }; stack.push(v); g.successors(v).forEach(function(w) { if (!visited.hasOwnProperty(w)) { dfs(w); entry.lowlink = Math.min(entry.lowlink, visited[w].lowlink); } else if (visited[w].onStack) { entry.lowlink = Math.min(entry.lowlink, visited[w].index); } }); if (entry.lowlink === entry.index) { var cmpt = []; var w; do { w = stack.pop(); visited[w].onStack = false; cmpt.push(w); } while (v !== w); results.push(cmpt); } } g.nodes().forEach(function(v) { if (!visited.hasOwnProperty(v)) { dfs(v); } }); return results; } var tarjan = tarjan_1; var findCycles_1 = findCycles; function findCycles(g) { return tarjan(g).filter(function(cmpt) { return cmpt.length > 1 || (cmpt.length === 1 && g.hasEdge(cmpt[0], cmpt[0])); }); } var floydWarshall_1 = floydWarshall; var DEFAULT_WEIGHT_FUNC = () => 1; function floydWarshall(g, weightFn, edgeFn) { return runFloydWarshall(g, weightFn || DEFAULT_WEIGHT_FUNC, edgeFn || function(v) { return g.outEdges(v); }); } function runFloydWarshall(g, weightFn, edgeFn) { var results = {}; var nodes = g.nodes(); nodes.forEach(function(v) { results[v] = {}; results[v][v] = { distance: 0 }; nodes.forEach(function(w) { if (v !== w) { results[v][w] = { distance: Number.POSITIVE_INFINITY }; } }); edgeFn(v).forEach(function(edge) { var w = edge.v === v ? edge.w : edge.v; var d = weightFn(edge); results[v][w] = { distance: d, predecessor: v }; }); }); nodes.forEach(function(k) { var rowK = results[k]; nodes.forEach(function(i) { var rowI = results[i]; nodes.forEach(function(j) { var ik = rowI[k]; var kj = rowK[j]; var ij = rowI[j]; var altDistance = ik.distance + kj.distance; if (altDistance < ij.distance) { ij.distance = altDistance; ij.predecessor = kj.predecessor; } }); }); }); return results; } function topsort$1(g) { var visited = {}; var stack = {}; var results = []; function visit(node) { if (stack.hasOwnProperty(node)) { throw new CycleException(); } if (!visited.hasOwnProperty(node)) { stack[node] = true; visited[node] = true; g.predecessors(node).forEach(visit); delete stack[node]; results.push(node); } } g.sinks().forEach(visit); if (Object.keys(visited).length !== g.nodeCount()) { throw new CycleException(); } return results; } class CycleException extends Error { constructor() { super(...arguments); } } var topsort_1 = topsort$1; topsort$1.CycleException = CycleException; var topsort = topsort_1; var isAcyclic_1 = isAcyclic; function isAcyclic(g) { try { topsort(g); } catch (e) { if (e instanceof topsort.CycleException) { return false; } throw e; } return true; } var dfs_1 = dfs$3; /* * A helper that preforms a pre- or post-order traversal on the input graph * and returns the nodes in the order they were visited. If the graph is * undirected then this algorithm will navigate using neighbors. If the graph * is directed then this algorithm will navigate using successors. * * If the order is not "post", it will be treated as "pre". */ function dfs$3(g, vs, order) { if (!Array.isArray(vs)) { vs = [vs]; } var navigation = g.isDirected() ? v => g.successors(v) : v => g.neighbors(v); var orderFunc = order === "post" ? postOrderDfs : preOrderDfs; var acc = []; var visited = {}; vs.forEach(v => { if (!g.hasNode(v)) { throw new Error("Graph does not have node: " + v); } orderFunc(v, navigation, visited, acc); }); return acc; } function postOrderDfs(v, navigation, visited, acc) { var stack = [[v, false]]; while (stack.length > 0) { var curr = stack.pop(); if (curr[1]) { acc.push(curr[0]); } else { if (!visited.hasOwnProperty(curr[0])) { visited[curr[0]] = true; stack.push([curr[0], true]); forEachRight(navigation(curr[0]), w => stack.push([w, false])); } } } } function preOrderDfs(v, navigation, visited, acc) { var stack = [v]; while (stack.length > 0) { var curr = stack.pop(); if (!visited.hasOwnProperty(curr)) { visited[curr] = true; acc.push(curr); forEachRight(navigation(curr), w => stack.push(w)); } } } function forEachRight(array, iteratee) { var length = array.length; while (length--) { iteratee(array[length], length, array); } return array; } var dfs$2 = dfs_1; var postorder_1 = postorder$2; function postorder$2(g, vs) { return dfs$2(g, vs, "post"); } var dfs$1 = dfs_1; var preorder_1 = preorder$1; function preorder$1(g, vs) { return dfs$1(g, vs, "pre"); } var Graph$8 = graph; var PriorityQueue = priorityQueue; var prim_1 = prim; function prim(g, weightFunc) { var result = new Graph$8(); var parents = {}; var pq = new PriorityQueue(); var v; function updateNeighbors(edge) { var w = edge.v === v ? edge.w : edge.v; var pri = pq.priority(w); if (pri !== undefined) { var edgeWeight = weightFunc(edge); if (edgeWeight < pri) { parents[w] = v; pq.decrease(w, edgeWeight); } } } if (g.nodeCount() === 0) { return result; } g.nodes().forEach(function(v) { pq.add(v, Number.POSITIVE_INFINITY); result.setNode(v); }); // Start from an arbitrary node pq.decrease(g.nodes()[0], 0); var init = false; while (pq.size() > 0) { v = pq.removeMin(); if (parents.hasOwnProperty(v)) { result.setEdge(v, parents[v]); } else if (init) { throw new Error("Input graph is not connected: " + g); } else { init = true; } g.nodeEdges(v).forEach(updateNeighbors); } return result; } var alg = { components: components_1, dijkstra: dijkstra_1, dijkstraAll: dijkstraAll_1, findCycles: findCycles_1, floydWarshall: floydWarshall_1, isAcyclic: isAcyclic_1, postorder: postorder_1, preorder: preorder_1, prim: prim_1, tarjan: tarjan_1, topsort: topsort_1 }; /** * Copyright (c) 2014, Chris Pettitt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ var lib = lib$1; var graphlib = { Graph: lib.Graph, json: json, alg: alg, version: lib.version }; /* * Simple doubly linked list implementation derived from Cormen, et al., * "Introduction to Algorithms". */ class List$1 { constructor() { let sentinel = {}; sentinel._next = sentinel._prev = sentinel; this._sentinel = sentinel; } dequeue() { let sentinel = this._sentinel; let entry = sentinel._prev; if (entry !== sentinel) { unlink(entry); return entry; } } enqueue(entry) { let sentinel = this._sentinel; if (entry._prev && entry._next) { unlink(entry); } entry._next = sentinel._next; sentinel._next._prev = entry; sentinel._next = entry; entry._prev = sentinel; } toString() { let strs = []; let sentinel = this._sentinel; let curr = sentinel._prev; while (curr !== sentinel) { strs.push(JSON.stringify(curr, filterOutLinks)); curr = curr._prev; } return "[" + strs.join(", ") + "]"; } } function unlink(entry) { entry._prev._next = entry._next; entry._next._prev = entry._prev; delete entry._next; delete entry._prev; } function filterOutLinks(k, v) { if (k !== "_next" && k !== "_prev") { return v; } } var list = List$1; let Graph$7 = graphlib.Graph; let List = list; /* * A greedy heuristic for finding a feedback arc set for a graph. A feedback * arc set is a set of edges that can be removed to make a graph acyclic. * The algorithm comes from: P. Eades, X. Lin, and W. F. Smyth, "A fast and * effective heuristic for the feedback arc set problem." This implementation * adjusts that from the paper to allow for weighted edges. */ var greedyFas = greedyFAS$1; let DEFAULT_WEIGHT_FN = () => 1; function greedyFAS$1(g, weightFn) { if (g.nodeCount() <= 1) { return []; } let state = buildState(g, weightFn || DEFAULT_WEIGHT_FN); let results = doGreedyFAS(state.graph, state.buckets, state.zeroIdx); // Expand multi-edges return results.flatMap(e => g.outEdges(e.v, e.w)); } function doGreedyFAS(g, buckets, zeroIdx) { let results = []; let sources = buckets[buckets.length - 1]; let sinks = buckets[0]; let entry; while (g.nodeCount()) { while ((entry = sinks.dequeue())) { removeNode(g, buckets, zeroIdx, entry); } while ((entry = sources.dequeue())) { removeNode(g, buckets, zeroIdx, entry); } if (g.nodeCount()) { for (let i = buckets.length - 2; i > 0; --i) { entry = buckets[i].dequeue(); if (entry) { results = results.concat(removeNode(g, buckets, zeroIdx, entry, true)); break; } } } } return results; } function removeNode(g, buckets, zeroIdx, entry, collectPredecessors) { let results = collectPredecessors ? [] : undefined; g.inEdges(entry.v).forEach(edge => { let weight = g.edge(edge); let uEntry = g.node(edge.v); if (collectPredecessors) { results.push({ v: edge.v, w: edge.w }); } uEntry.out -= weight; assignBucket(buckets, zeroIdx, uEntry); }); g.outEdges(entry.v).forEach(edge => { let weight = g.edge(edge); let w = edge.w; let wEntry = g.node(w); wEntry["in"] -= weight; assignBucket(buckets, zeroIdx, wEntry); }); g.removeNode(entry.v); return results; } function buildState(g, weightFn) { let fasGraph = new Graph$7(); let maxIn = 0; let maxOut = 0; g.nodes().forEach(v => { fasGraph.setNode(v, { v: v, "in": 0, out: 0 }); }); // Aggregate weights on nodes, but also sum the weights across multi-edges // into a single edge for the fasGraph. g.edges().forEach(e => { let prevWeight = fasGraph.edge(e.v, e.w) || 0; let weight = weightFn(e); let edgeWeight = prevWeight + weight; fasGraph.setEdge(e.v, e.w, edgeWeight); maxOut = Math.max(maxOut, fasGraph.node(e.v).out += weight); maxIn = Math.max(maxIn, fasGraph.node(e.w)["in"] += weight); }); let buckets = range$1(maxOut + maxIn + 3).map(() => new List()); let zeroIdx = maxIn + 1; fasGraph.nodes().forEach(v => { assignBucket(buckets, zeroIdx, fasGraph.node(v)); }); return { graph: fasGraph, buckets: buckets, zeroIdx: zeroIdx }; } function assignBucket(buckets, zeroIdx, entry) { if (!entry.out) { buckets[0].enqueue(entry); } else if (!entry["in"]) { buckets[buckets.length - 1].enqueue(entry); } else { buckets[entry.out - entry["in"] + zeroIdx].enqueue(entry); } } function range$1(limit) { const range = []; for (let i = 0; i < limit; i++) { range.push(i); } return range; } /* eslint "no-console": off */ let Graph$6 = graphlib.Graph; var util$d = { addBorderNode: addBorderNode$1, addDummyNode, asNonCompoundGraph, buildLayerMatrix, intersectRect, mapValues, maxRank, normalizeRanks: normalizeRanks$1, notime, partition, pick, predecessorWeights, range, removeEmptyRanks: removeEmptyRanks$1, simplify: simplify$1, successorWeights, time, uniqueId: uniqueId$1, zipObject: zipObject$1, }; /* * Adds a dummy node to the graph and return v. */ function addDummyNode(g, type, attrs, name) { let v; do { v = uniqueId$1(name); } while (g.hasNode(v)); attrs.dummy = type; g.setNode(v, attrs); return v; } /* * Returns a new graph with only simple edges. Handles aggregation of data * associated with multi-edges. */ function simplify$1(g) { let simplified = new Graph$6().setGraph(g.graph()); g.nodes().forEach(v => simplified.setNode(v, g.node(v))); g.edges().forEach(e => { let simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 }; let label = g.edge(e); simplified.setEdge(e.v, e.w, { weight: simpleLabel.weight + label.weight, minlen: Math.max(simpleLabel.minlen, label.minlen) }); }); return simplified; } function asNonCompoundGraph(g) { let simplified = new Graph$6({ multigraph: g.isMultigraph() }).setGraph(g.graph()); g.nodes().forEach(v => { if (!g.children(v).length) { simplified.setNode(v, g.node(v)); } }); g.edges().forEach(e => { simplified.setEdge(e, g.edge(e)); }); return simplified; } function successorWeights(g) { let weightMap = g.nodes().map(v => { let sucs = {}; g.outEdges(v).forEach(e => { sucs[e.w] = (sucs[e.w] || 0) + g.edge(e).weight; }); return sucs; }); return zipObject$1(g.nodes(), weightMap); } function predecessorWeights(g) { let weightMap = g.nodes().map(v => { let preds = {}; g.inEdges(v).forEach(e => { preds[e.v] = (preds[e.v] || 0) + g.edge(e).weight; }); return preds; }); return zipObject$1(g.nodes(), weightMap); } /* * Finds where a line starting at point ({x, y}) would intersect a rectangle * ({x, y, width, height}) if it were pointing at the rectangle's center. */ function intersectRect(rect, point) { let x = rect.x; let y = rect.y; // Rectangle intersection algorithm from: // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes let dx = point.x - x; let dy = point.y - y; let w = rect.width / 2; let h = rect.height / 2; if (!dx && !dy) { throw new Error("Not possible to find intersection inside of the rectangle"); } let sx, sy; if (Math.abs(dy) * w > Math.abs(dx) * h) { // Intersection is top or bottom of rect. if (dy < 0) { h = -h; } sx = h * dx / dy; sy = h; } else { // Intersection is left or right of rect. if (dx < 0) { w = -w; } sx = w; sy = w * dy / dx; } return { x: x + sx, y: y + sy }; } /* * Given a DAG with each node assigned "rank" and "order" properties, this * function will produce a matrix with the ids of each node. */ function buildLayerMatrix(g) { let layering = range(maxRank(g) + 1).map(() => []); g.nodes().forEach(v => { let node = g.node(v); let rank = node.rank; if (rank !== undefined) { layering[rank][node.order] = v; } }); return layering; } /* * Adjusts the ranks for all nodes in the graph such that all nodes v have * rank(v) >= 0 and at least one node w has rank(w) = 0. */ function normalizeRanks$1(g) { let min = Math.min(...g.nodes().map(v => { let rank = g.node(v).rank; if (rank === undefined) { return Number.MAX_VALUE; } return rank; })); g.nodes().forEach(v => { let node = g.node(v); if (node.hasOwnProperty("rank")) { node.rank -= min; } }); } function removeEmptyRanks$1(g) { // Ranks may not start at 0, so we need to offset them let offset = Math.min(...g.nodes().map(v => g.node(v).rank)); let layers = []; g.nodes().forEach(v => { let rank = g.node(v).rank - offset; if (!layers[rank]) { layers[rank] = []; } layers[rank].push(v); }); let delta = 0; let nodeRankFactor = g.graph().nodeRankFactor; Array.from(layers).forEach((vs, i) => { if (vs === undefined && i % nodeRankFactor !== 0) { --delta; } else if (vs !== undefined && delta) { vs.forEach(v => g.node(v).rank += delta); } }); } function addBorderNode$1(g, prefix, rank, order) { let node = { width: 0, height: 0 }; if (arguments.length >= 4) { node.rank = rank; node.order = order; } return addDummyNode(g, "border", node, prefix); } function maxRank(g) { return Math.max(...g.nodes().map(v => { let rank = g.node(v).rank; if (rank === undefined) { return Number.MIN_VALUE; } return rank; })); } /* * Partition a collection into two groups: `lhs` and `rhs`. If the supplied * function returns true for an entry it goes into `lhs`. Otherwise it goes * into `rhs. */ function partition(collection, fn) { let result = { lhs: [], rhs: [] }; collection.forEach(value => { if (fn(value)) { result.lhs.push(value); } else { result.rhs.push(value); } }); return result; } /* * Returns a new function that wraps `fn` with a timer. The wrapper logs the * time it takes to execute the function. */ function time(name, fn) { let start = Date.now(); try { return fn(); } finally { console.log(name + " time: " + (Date.now() - start) + "ms"); } } function notime(name, fn) { return fn(); } let idCounter = 0; function uniqueId$1(prefix) { var id = ++idCounter; return toString(prefix) + id; } function range(start, limit, step = 1) { if (limit == null) { limit = start; start = 0; } let endCon = (i) => i < limit; if (step < 0) { endCon = (i) => limit < i; } const range = []; for (let i = start; endCon(i); i += step) { range.push(i); } return range; } function pick(source, keys) { const dest = {}; for (const key of keys) { if (source[key] !== undefined) { dest[key] = source[key]; } } return dest; } function mapValues(obj, funcOrProp) { let func = funcOrProp; if (typeof funcOrProp === 'string') { func = (val) => val[funcOrProp]; } return Object.entries(obj).reduce((acc, [k, v]) => { acc[k] = func(v, k); return acc; }, {}); } function zipObject$1(props, values) { return props.reduce((acc, key, i) => { acc[key] = values[i]; return acc; }, {}); } let greedyFAS = greedyFas; let uniqueId = util$d.uniqueId; var acyclic$1 = { run: run$2, undo: undo$2 }; function run$2(g) { let fas = (g.graph().acyclicer === "greedy" ? greedyFAS(g, weightFn(g)) : dfsFAS(g)); fas.forEach(e => { let label = g.edge(e); g.removeEdge(e); label.forwardName = e.name; label.reversed = true; g.setEdge(e.w, e.v, label, uniqueId("rev")); }); function weightFn(g) { return e => { return g.edge(e).weight; }; } } function dfsFAS(g) { let fas = []; let stack = {}; let visited = {}; function dfs(v) {