gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
356 lines (355 loc) • 17.4 kB
JavaScript
/*
* Copyright (C) 1998-2023 by Northwoods Software Corporation. All Rights Reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../release/go.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParallelLayout = void 0;
/*
* This is an extension and not part of the main GoJS library.
* Note that the API for this class may change with any version, even point releases.
* If you intend to use an extension in production, you should copy the code to your own source directory.
* Extensions can be found in the GoJS kit under the extensions or extensionsJSM folders.
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
*/
var go = require("../release/go.js");
/**
* A custom {@link TreeLayout} that can be used for laying out stylized flowcharts.
* Each layout requires a single "Split" node and a single "Merge" node.
* The "Split" node should be the root of a tree-like structure if one excludes links to the "Merge" node.
* This will position the "Merge" node to line up with the "Split" node.
*
* You can set all of the TreeLayout properties that you like,
* except that for simplicity this code just works for angle === 0 or angle === 90.
*
* If you want to experiment with this extension, try the <a href="../../extensionsJSM/Parallel.html">Parallel Layout</a> sample.
* @category Layout Extension
*/
var ParallelLayout = /** @class */ (function (_super) {
__extends(ParallelLayout, _super);
/**
* Constructs a ParallelLayout and sets the following properties:
* - {@link #isRealtime} = false
* - {@link #alignment} = {@link TreeLayout.AlignmentCenterChildren}
* - {@link #compaction} = {@link TreeLayout.CompactionNone}
* - {@link #alternateAlignment} = {@link TreeLayout.AlignmentCenterChildren}
* - {@link #alternateCompaction} = {@link TreeLayout.CompactionNone}
*/
function ParallelLayout() {
var _this = _super.call(this) || this;
_this._splitNode = null;
_this._mergeNode = null;
_this.isRealtime = false;
_this.alignment = go.TreeLayout.AlignmentCenterChildren;
_this.compaction = go.TreeLayout.CompactionNone;
_this.alternateAlignment = go.TreeLayout.AlignmentCenterChildren;
_this.alternateCompaction = go.TreeLayout.CompactionNone;
return _this;
}
Object.defineProperty(ParallelLayout.prototype, "splitNode", {
/**
* This read-only property returns the node that the tree will extend from.
*/
get: function () { return this._splitNode; },
set: function (val) { this._splitNode = val; },
enumerable: false,
configurable: true
});
Object.defineProperty(ParallelLayout.prototype, "mergeNode", {
/**
* This read-only property returns the node that the tree will converge at.
*/
get: function () { return this._mergeNode; },
set: function (val) { this._mergeNode = val; },
enumerable: false,
configurable: true
});
/**
* Overridable predicate for deciding if a Node is a Split node.
* By default this checks the node's {@link Part#category} to see if it is
* "Split", "Start", "For", "While", "If", or "Switch".
* @param {Node} node
* @return {boolean}
*/
ParallelLayout.prototype.isSplit = function (node) {
if (!(node instanceof go.Node))
return false;
var cat = node.category;
return (cat === "Split" || cat === "Start" || cat === "For" || cat === "While" || cat === "If" || cat === "Switch");
};
/**
* Overridable predicate for deciding if a Node is a Merge node.
* By default this checks the node's {@link Part#category} to see if it is
* "Merge", "End", "EndFor", "EndWhile", "EndIf", or "EndSwitch".
* @param {Node} node
* @return {boolean}
*/
ParallelLayout.prototype.isMerge = function (node) {
if (!(node instanceof go.Node))
return false;
var cat = node.category;
return (cat === "Merge" || cat === "End" || cat === "EndFor" || cat === "EndWhile" || cat === "EndIf" || cat === "EndSwitch");
};
/**
* Overridable predicate for deciding if a Node is a conditional or "If" type of Split Node
* expecting to have two links coming out of the sides.
* @param {Node} node
* @return {boolean}
*/
ParallelLayout.prototype.isConditional = function (node) {
if (!(node instanceof go.Node))
return false;
return node.category === "If";
};
/**
* Overridable predicate for deciding if a Node is a "Switch" type of Split Node
* expecting to have three links coming out of the bottom/right side.
* @param {Node} node
* @return {boolean}
*/
ParallelLayout.prototype.isSwitch = function (node) {
if (!(node instanceof go.Node))
return false;
return node.category === "Switch";
};
/**
* Find a Split vertex and a Merge vertex for this layout.
* This signals an error if there is not exactly one vertex/Node that {@link #isSplit}
* and exactly one vertex/Node that {@link #isMerge}.
* This can be overridden; any override must set {@link #splitNode} and {@link #mergeNode}.
* @param {Iterable<TreeVertex>} vertexes
*/
ParallelLayout.prototype.findSplitMerge = function (vertexes) {
var split = null;
var merge = null;
var it = vertexes.iterator;
while (it.next()) {
var v = it.value;
if (!v.node)
continue;
if (this.isSplit(v.node)) {
if (split)
throw new Error("Split node already exists in " + this + " -- existing: " + split + " new: " + v.node);
split = v.node;
}
else if (this.isMerge(v.node)) {
if (merge)
throw new Error("Merge node already exists in " + this + " -- existing: " + merge + " new: " + v.node);
merge = v.node;
}
}
if (!split)
throw new Error("Missing Split node in " + this);
if (!merge)
throw new Error("Missing Merge node in " + this);
this._splitNode = split;
this._mergeNode = merge;
};
/**
* @hidden @internal
*/
ParallelLayout.prototype.makeNetwork = function (coll) {
var net = _super.prototype.makeNetwork.call(this, coll);
// Groups might be unbalanced -- position them so that the Split node is centered under the parent node.
var it = net.vertexes.iterator;
while (it.next()) {
var v = it.value;
var g = v.node;
if (g instanceof go.Group && g.isSubGraphExpanded && g.placeholder !== null && g.layout instanceof ParallelLayout) {
var split = g.layout.splitNode;
if (split) {
if (this.angle === 0) {
v.focusY = split.location.y - g.position.y;
}
else if (this.angle === 90) {
v.focusX = split.location.x - g.position.x;
}
}
}
}
if (this.group && !this.group.isSubGraphExpanded)
return net;
// look for and remember the one Split node and the one Merge node
this.findSplitMerge(net.vertexes.iterator);
// don't have TreeLayout lay out the Merge node; commitNodes will do it
if (this.mergeNode)
net.deleteNode(this.mergeNode);
// for each vertex that does not have an incoming edge,
// connect to it from the splitNode vertex with a dummy edge
if (this.splitNode) {
var splitv = net.findVertex(this.splitNode);
net.vertexes.each(function (v) {
if (splitv === null || v === splitv)
return;
if (v.sourceEdges.count === 0) {
net.linkVertexes(splitv, v, null);
}
});
}
return net;
};
/**
* @hidden @internal
*/
ParallelLayout.prototype.commitNodes = function () {
_super.prototype.commitNodes.call(this);
// Line up the Merge node to the center of the Split node
var mergeNode = this.mergeNode;
var splitNode = this.splitNode;
if (mergeNode === null || splitNode === null || this.network === null)
return;
var splitVertex = this.network.findVertex(splitNode);
if (splitVertex === null)
return;
if (this.angle === 0) {
mergeNode.location = new go.Point(splitVertex.x + splitVertex.subtreeSize.width + this.layerSpacing + mergeNode.actualBounds.width / 2, splitVertex.centerY);
}
else if (this.angle === 90) {
mergeNode.location = new go.Point(splitVertex.centerX, splitVertex.y + splitVertex.subtreeSize.height + this.layerSpacing + mergeNode.actualBounds.height / 2);
}
mergeNode.ensureBounds();
};
/**
* @hidden @internal
*/
ParallelLayout.prototype.commitLinks = function () {
var splitNode = this.splitNode;
var mergeNode = this.mergeNode;
if (splitNode === null || mergeNode === null || this.network === null)
return;
// set default link spots based on this.angle
var it = this.network.edges.iterator;
while (it.next()) {
var e = it.value;
var link = e.link;
if (!link)
continue;
if (this.angle === 0) {
if (this.setsPortSpot)
link.fromSpot = go.Spot.Right;
if (this.setsChildPortSpot)
link.toSpot = go.Spot.Left;
}
else if (this.angle === 90) {
if (this.setsPortSpot)
link.fromSpot = go.Spot.Bottom;
if (this.setsChildPortSpot)
link.toSpot = go.Spot.Top;
}
}
// Make sure links coming into and going out of a Split node come in the correct way
if (splitNode) {
// Handle links coming into the Split node
var cond = this.isConditional(splitNode);
var swtch = this.isSwitch(splitNode);
// Handle links going out of the Split node
var first = true; // handle "If" nodes specially
var lit = splitNode.findLinksOutOf();
while (lit.next()) {
var link = lit.value;
if (this.angle === 0) {
if (this.setsPortSpot)
link.fromSpot = cond ? (first ? go.Spot.Top : go.Spot.Bottom) : (swtch ? go.Spot.RightSide : go.Spot.Right);
if (this.setsChildPortSpot)
link.toSpot = go.Spot.Left;
}
else if (this.angle === 90) {
if (this.setsPortSpot)
link.fromSpot = cond ? (first ? go.Spot.Left : go.Spot.Right) : (swtch ? go.Spot.BottomSide : go.Spot.Bottom);
if (this.setsChildPortSpot)
link.toSpot = go.Spot.Top;
}
first = false;
}
}
if (mergeNode) {
// Handle links going into the Merge node
var iit = mergeNode.findLinksInto();
while (iit.next()) {
var link = iit.value;
if (!this.isSplit(link.fromNode)) { // if link connects Split with Merge directly, only set fromSpot once
if (this.angle === 0) {
if (this.setsPortSpot)
link.fromSpot = go.Spot.Right;
if (this.setsChildPortSpot)
link.toSpot = go.Spot.Left;
}
else if (this.angle === 90) {
if (this.setsPortSpot)
link.fromSpot = go.Spot.Bottom;
if (this.setsChildPortSpot)
link.toSpot = go.Spot.Top;
}
}
if (!link.isOrthogonal)
continue;
// have all of the links coming into the Merge node have segments
// that share a common X (or if angle==90, Y) coordinate
link.updateRoute();
if (link.pointsCount >= 6) {
var pts = link.points.copy();
var p2 = pts.elt(pts.length - 4);
var p3 = pts.elt(pts.length - 3);
if (this.angle === 0 && p2.x === p3.x) {
var x = mergeNode.position.x - this.layerSpacing / 2;
pts.setElt(pts.length - 4, new go.Point(x, p2.y));
pts.setElt(pts.length - 3, new go.Point(x, p3.y));
}
else if (this.angle === 90 && p2.y === p3.y) {
var y = mergeNode.position.y - this.layerSpacing / 2;
pts.setElt(pts.length - 4, new go.Point(p2.x, y));
pts.setElt(pts.length - 3, new go.Point(p3.x, y));
}
link.points = pts;
}
}
// handle links coming out of the Merge node, looping back left/up
var oit = mergeNode.findLinksOutOf();
while (oit.next()) {
var link = oit.value;
// if connects internal with external node, it isn't a loop-back link
if (link.toNode && link.toNode.containingGroup !== mergeNode.containingGroup)
continue;
if (this.angle === 0) {
if (this.setsPortSpot)
link.fromSpot = go.Spot.TopBottomSides;
if (this.setsChildPortSpot)
link.toSpot = go.Spot.TopBottomSides;
}
else if (this.angle === 90) {
if (this.setsPortSpot)
link.fromSpot = go.Spot.LeftRightSides;
if (this.setsChildPortSpot)
link.toSpot = go.Spot.LeftRightSides;
}
link.routing = go.Link.AvoidsNodes;
}
}
};
return ParallelLayout;
}(go.TreeLayout));
exports.ParallelLayout = ParallelLayout;
});