179 lines
5.4 KiB
TypeScript
179 lines
5.4 KiB
TypeScript
"use strict";
|
|
/*
|
|
* Copyright (C) 1998-2017 by Northwoods Software Corporation. All Rights Reserved.
|
|
*/
|
|
|
|
import * as go from "../release/go";
|
|
|
|
// A custom Layout that lays out a chain of nodes in a snake-like fashion
|
|
|
|
/**
|
|
* @constructor
|
|
* @extends Layout
|
|
* @class
|
|
* This layout assumes the graph is a chain of Nodes,
|
|
* positioning nodes in horizontal rows back and forth, alternating between left-to-right
|
|
* and right-to-left within the {@link #wrap} limit.
|
|
* {@link #spacing} controls the distance between nodes.
|
|
* <p/>
|
|
* When this layout is the Diagram.layout, it is automatically invalidated when the viewport changes size.
|
|
*/
|
|
export class SerpentineLayout extends go.Layout {
|
|
public isViewportSized: boolean = true;
|
|
private _spacing: go.Size = new go.Size(30, 30);
|
|
private _wrap: number = NaN;
|
|
|
|
/**
|
|
* @ignore
|
|
* Copies properties to a cloned Layout.
|
|
* @this {SerpentineLayout}
|
|
* @param {Layout} copy
|
|
* @override
|
|
*/
|
|
|
|
public cloneProtected(copy: SerpentineLayout) {
|
|
super.cloneProtected.call(this, copy);
|
|
copy._spacing = this._spacing;
|
|
copy._wrap = this._wrap;
|
|
};
|
|
|
|
/**
|
|
* This method actually positions all of the Nodes, assuming that the ordering of the nodes
|
|
* is given by a single link from one node to the next.
|
|
* This respects the {@link #spacing} and {@link #wrap} properties to affect the layout.
|
|
* @this {SerpentineLayout}
|
|
* @param {Diagram|Group|Iterable} coll the collection of Parts to layout.
|
|
*/
|
|
public doLayout(coll: go.Iterable<go.Part>) {
|
|
var diagram = this.diagram;
|
|
coll = this.collectParts(coll);
|
|
|
|
var root = null;
|
|
// find a root node -- one without any incoming links
|
|
var it = coll.iterator;
|
|
while (it.next()) {
|
|
var n = it.value;
|
|
if (!(n instanceof go.Node)) continue;
|
|
if (root === null) root = n;
|
|
if (n.findLinksInto().count === 0) {
|
|
root = n;
|
|
break;
|
|
}
|
|
}
|
|
// couldn't find a root node
|
|
if (root === null) return;
|
|
|
|
var spacing = this.spacing;
|
|
|
|
// calculate the width at which we should start a new row
|
|
var wrap = this.wrap;
|
|
if (diagram !== null && isNaN(wrap)) {
|
|
if (this.group === null) { // for a top-level layout, use the Diagram.viewportBounds
|
|
var pad = diagram.padding as go.Margin;
|
|
wrap = Math.max(spacing.width * 2, diagram.viewportBounds.width - 24 - pad.left - pad.right);
|
|
} else {
|
|
wrap = 1000; // provide a better default value?
|
|
}
|
|
}
|
|
|
|
// implementations of doLayout that do not make use of a LayoutNetwork
|
|
// need to perform their own transactions
|
|
if (diagram !== null) diagram.startTransaction("Serpentine Layout");
|
|
|
|
// start on the left, at Layout.arrangementOrigin
|
|
var x = this.arrangementOrigin.x;
|
|
var rowh = 0;
|
|
var y = this.arrangementOrigin.y;
|
|
var increasing = true;
|
|
var node = root;
|
|
while (node !== null) {
|
|
var b = node.actualBounds;
|
|
// get the next node, if any
|
|
var nextlink = node.findLinksOutOf().first();
|
|
var nextnode = (nextlink !== null ? nextlink.toNode : null);
|
|
var nb = (nextnode !== null ? nextnode.actualBounds : new go.Rect());
|
|
if (increasing) {
|
|
node.move(new go.Point(x, y));
|
|
x += b.width;
|
|
rowh = Math.max(rowh, b.height);
|
|
if (x + spacing.width + nb.width > wrap) {
|
|
y += rowh + spacing.height;
|
|
x = wrap - spacing.width;
|
|
rowh = 0;
|
|
increasing = false;
|
|
if (nextlink !== null) {
|
|
nextlink.fromSpot = go.Spot.Right;
|
|
nextlink.toSpot = go.Spot.Right;
|
|
}
|
|
} else {
|
|
x += spacing.width;
|
|
if (nextlink !== null) {
|
|
nextlink.fromSpot = go.Spot.Right;
|
|
nextlink.toSpot = go.Spot.Left;
|
|
}
|
|
}
|
|
} else {
|
|
x -= b.width;
|
|
node.move(new go.Point(x, y));
|
|
rowh = Math.max(rowh, b.height);
|
|
if (x - spacing.width - nb.width < 0) {
|
|
y += rowh + spacing.height;
|
|
x = 0;
|
|
rowh = 0;
|
|
increasing = true;
|
|
if (nextlink !== null) {
|
|
nextlink.fromSpot = go.Spot.Left;
|
|
nextlink.toSpot = go.Spot.Left;
|
|
}
|
|
} else {
|
|
x -= spacing.width;
|
|
if (nextlink !== null) {
|
|
nextlink.fromSpot = go.Spot.Left;
|
|
nextlink.toSpot = go.Spot.Right;
|
|
}
|
|
}
|
|
}
|
|
node = nextnode;
|
|
}
|
|
|
|
if (diagram !== null) diagram.commitTransaction("Serpentine Layout");
|
|
};
|
|
|
|
// Public properties
|
|
|
|
/**
|
|
* Gets or sets the {@link Size} whose width specifies the horizontal space between nodes
|
|
* and whose height specifies the minimum vertical space between nodes.
|
|
* The default value is 30x30.
|
|
* @name SerpentineLayout#spacing
|
|
* @function.
|
|
* @return {Size}
|
|
*/
|
|
get spacing(): go.Size { return this._spacing }
|
|
set spacing(val: go.Size) {
|
|
if (!(val instanceof go.Size)) throw new Error("new value for SerpentineLayout.spacing must be a Size, not: " + val);
|
|
if (!this._spacing.equals(val)) {
|
|
this._spacing = val;
|
|
this.invalidateLayout();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets or sets the total width of the layout.
|
|
* The default value is NaN, which for {@link Diagram#layout}s means that it uses
|
|
* the {@link Diagram#viewportBounds}.
|
|
* @name SerpentineLayout#wrap
|
|
* @function.
|
|
* @return {number}
|
|
*/
|
|
get wrap(): number { return this._wrap }
|
|
set wrap(val: number) {
|
|
if (this._wrap !== val) {
|
|
this._wrap = val;
|
|
this.invalidateLayout();
|
|
}
|
|
}
|
|
|
|
}
|
|
|