GoJS provides a comprehensive layout system with multiple built-in algorithms for automatically positioning nodes and routing links. Layouts can be applied to entire diagrams or specific groups, with extensive customization options for each algorithm.
Base class for all automatic layout algorithms, providing common functionality and lifecycle management.
abstract class Layout {
/**
* Creates a new Layout.
* @param init - Optional initialization properties
*/
constructor(init?: Partial<Layout>);
// Association
diagram: Diagram | null;
group: Group | null;
network: LayoutNetwork | null;
// Behavior
isOngoing: boolean;
isInitial: boolean;
isRouting: boolean;
isRealtime: boolean;
// Validation
isValidLayout: boolean;
conditions: LayoutConditions;
// Abstract Methods (implemented by subclasses)
abstract doLayout(coll?: Iterable<Part>): void;
// Lifecycle Methods
invalidateLayout(): void;
prepareDiagram(): void;
arrangeParts(parts: List<Part>, union: boolean): Rect;
// Network Creation
createNetwork(coll?: Iterable<Part>): LayoutNetwork;
makeNetwork(coll: Iterable<Part>): LayoutNetwork;
// Utility Methods
commitLayout(): void;
moveToPositions(): void;
}
enum LayoutConditions {
Standard = 'Standard',
NodeSized = 'NodeSized',
DocumentBounds = 'DocumentBounds'
}Hierarchical layout for tree structures with customizable orientation and spacing.
class TreeLayout extends Layout {
/**
* Creates a new TreeLayout.
* @param init - Optional initialization properties
*/
constructor(init?: Partial<TreeLayout>);
// Orientation and Direction
angle: number;
arrangement: TreeArrangement;
sorting: TreeSorting;
compaction: TreeCompaction;
breadthLimit: number;
rowSpacing: number;
rowIndent: number;
// Node Positioning
alignment: TreeAlignment;
nodeIndent: number;
nodeIndentPastParent: number;
layerSpacing: number;
layerSpacingParentOverlap: number;
nodeSpacing: number;
// Tree Structure
treeStyle: TreeStyle;
alternateAngle: number;
alternateAlignment: TreeAlignment;
alternateNodeSpacing: number;
alternateNodeIndent: number;
alternateNodeIndentPastParent: number;
alternateLayerSpacing: number;
alternateLayerSpacingParentOverlap: number;
alternatePortSpot: Spot;
alternateChildPortSpot: Spot;
// Ports and Connection Points
portSpot: Spot;
childPortSpot: Spot;
// Root Handling
rootDefaults: boolean;
rootSpacing: number;
// Path and Routing
path: TreePath;
// Methods
doLayout(coll?: Iterable<Part>): void;
commitNodes(): void;
commitLinks(): void;
assignTreeVertexValues(v: TreeVertex): void;
}
// Tree Layout Enums
enum TreeArrangement {
FixedRoots = 'FixedRoots',
Horizontal = 'Horizontal',
Vertical = 'Vertical'
}
enum TreeSorting {
Forward = 'Forward',
Reverse = 'Reverse',
Ascending = 'Ascending',
Descending = 'Descending'
}
enum TreeAlignment {
Start = 'Start',
End = 'End',
CenterChildren = 'CenterChildren',
CenterSubtrees = 'CenterSubtrees'
}
enum TreeStyle {
Layered = 'Layered',
LastParents = 'LastParents',
RootOnly = 'RootOnly'
}
enum TreePath {
Source = 'Source',
Destination = 'Destination'
}Usage Examples:
// Basic tree layout (top-down)
const treeLayout = new go.TreeLayout({
angle: 90, // 90 degrees = top-down
layerSpacing: 35, // vertical spacing between levels
nodeSpacing: 10, // horizontal spacing between siblings
alignment: go.TreeAlignment.CenterSubtrees
});
// Horizontal tree layout (left-to-right)
const horizontalTree = new go.TreeLayout({
angle: 0, // 0 degrees = left-to-right
layerSpacing: 50,
nodeSpacing: 20,
arrangement: go.TreeArrangement.Vertical,
sorting: go.TreeSorting.Ascending
});
// Radial tree layout
const radialTree = new go.TreeLayout({
angle: 90,
arrangement: go.TreeArrangement.FixedRoots,
treeStyle: go.TreeStyle.RootOnly,
layerSpacing: 80,
alternateAngle: 90,
alternateLayerSpacing: 40
});Directed graph layout with nodes arranged in layers to minimize edge crossings.
class LayeredDigraphLayout extends Layout {
/**
* Creates a new LayeredDigraphLayout.
* @param init - Optional initialization properties
*/
constructor(init?: Partial<LayeredDigraphLayout>);
// Direction and Spacing
direction: number; // 0=right, 90=down, 180=left, 270=up
layerSpacing: number; // spacing between layers
columnSpacing: number; // spacing between nodes in same layer
// Cycle Removal
cycleRemoveOption: LayeredDigraphCycleRemove;
// Layer Assignment
layeringOption: LayeredDigraphLayering;
// Node Initialization
initializeOption: LayeredDigraphInit;
// Crossing Reduction
aggressiveOption: LayeredDigraphAggressive;
iterations: number;
// Node Packing
packOption: LayeredDigraphPack;
setsPortSpots: boolean;
// Alignment
alignOption: LayeredDigraphAlign;
// Methods
doLayout(coll?: Iterable<Part>): void;
assignLayers(): void;
reduceCrossings(): void;
}
// LayeredDigraph Enums
enum LayeredDigraphCycleRemove {
DepthFirst = 'DepthFirst',
Greedy = 'Greedy'
}
enum LayeredDigraphLayering {
OptimalLinkLength = 'OptimalLinkLength',
LongestPathSource = 'LongestPathSource',
LongestPathSink = 'LongestPathSink'
}
enum LayeredDigraphInit {
DepthFirstOut = 'DepthFirstOut',
DepthFirstIn = 'DepthFirstIn',
Naive = 'Naive'
}
enum LayeredDigraphAggressive {
None = 'None',
Less = 'Less',
More = 'More'
}
enum LayeredDigraphPack {
None = 'None',
Expand = 'Expand',
StraightenAndExpand = 'StraightenAndExpand',
Median = 'Median',
All = 'All'
}
enum LayeredDigraphAlign {
None = 'None',
UpperLeft = 'UpperLeft',
UpperRight = 'UpperRight',
LowerLeft = 'LowerLeft',
LowerRight = 'LowerRight'
}Usage Examples:
// Standard flowchart layout (top-down)
const flowchartLayout = new go.LayeredDigraphLayout({
direction: 90,
layerSpacing: 30,
columnSpacing: 30,
setsPortSpots: false
});
// Left-to-right process flow
const processLayout = new go.LayeredDigraphLayout({
direction: 0, // left to right
layerSpacing: 50,
columnSpacing: 20,
cycleRemoveOption: go.LayeredDigraphCycleRemove.Greedy,
layeringOption: go.LayeredDigraphLayering.LongestPathSource,
packOption: go.LayeredDigraphPack.All
});Arranges nodes in circular or spiral patterns with customizable radius and spacing.
class CircularLayout extends Layout {
/**
* Creates a new CircularLayout.
* @param init - Optional initialization properties
*/
constructor(init?: Partial<CircularLayout>);
// Circle Properties
radius: number;
aspectRatio: number;
startAngle: number;
sweepAngle: number;
// Spacing and Arrangement
spacing: number;
arrangement: CircularArrangement;
direction: CircularDirection;
sorting: CircularSorting;
// Node Sizing
nodeDiameterFormula: CircularNodeDiameterFormula;
// Methods
doLayout(coll?: Iterable<Part>): void;
actualCenter: Point;
actualXRadius: number;
actualYRadius: number;
}
// Circular Layout Enums
enum CircularArrangement {
ConstantSpacing = 'ConstantSpacing',
ConstantAngle = 'ConstantAngle',
ConstantDistance = 'ConstantDistance',
Packed = 'Packed'
}
enum CircularDirection {
Clockwise = 'Clockwise',
Counterclockwise = 'Counterclockwise',
BidirectionalLeft = 'BidirectionalLeft',
BidirectionalRight = 'BidirectionalRight'
}
enum CircularSorting {
Forward = 'Forward',
Reverse = 'Reverse',
Ascending = 'Ascending',
Descending = 'Descending',
Optimized = 'Optimized'
}
enum CircularNodeDiameterFormula {
Pythagorean = 'Pythagorean',
Circular = 'Circular'
}Usage Examples:
// Basic circular layout
const circularLayout = new go.CircularLayout({
radius: 100,
spacing: 6,
direction: go.CircularDirection.Clockwise,
sorting: go.CircularSorting.Optimized
});
// Spiral layout
const spiralLayout = new go.CircularLayout({
radius: 50,
spacing: 20,
arrangement: go.CircularArrangement.ConstantSpacing,
sweepAngle: 720 // two full rotations
});Physics-based layout using attractive and repulsive forces to position nodes naturally.
class ForceDirectedLayout extends Layout {
/**
* Creates a new ForceDirectedLayout.
* @param init - Optional initialization properties
*/
constructor(init?: Partial<ForceDirectedLayout>);
// Simulation Parameters
maxIterations: number;
epsilonDistance: number;
infinityDistance: number;
defaultComputeEffectiveForce: boolean;
// Spring Forces (attractive)
defaultSpringForce: boolean;
defaultSpringLength: number;
defaultSpringStiffness: number;
// Electrical Forces (repulsive)
defaultElectricalForce: boolean;
defaultElectricalCharge: number;
// Gravitational Forces
defaultGravitationalForce: boolean;
defaultGravitationalMass: number;
// Movement and Positioning
randomNumberGenerator: (() => number) | null;
setsPortSpots: boolean;
arrangesToOrigin: boolean;
// Methods
doLayout(coll?: Iterable<Part>): void;
moveLimit: number;
currentIteration: number;
}Usage Examples:
// Network diagram layout
const networkLayout = new go.ForceDirectedLayout({
maxIterations: 200,
defaultSpringLength: 30,
defaultElectricalCharge: 100,
defaultSpringStiffness: 0.05
});
// Tight clustering layout
const clusterLayout = new go.ForceDirectedLayout({
maxIterations: 150,
defaultSpringLength: 20,
defaultElectricalCharge: 50, // lower repulsion
defaultSpringStiffness: 0.1 // stronger attraction
});Arranges nodes in a grid pattern with configurable rows, columns, and spacing.
class GridLayout extends Layout {
/**
* Creates a new GridLayout.
* @param init - Optional initialization properties
*/
constructor(init?: Partial<GridLayout>);
// Grid Dimensions and Wrapping
wrappingWidth: number; // Width beyond which layout starts new row (NaN = viewport width)
wrappingColumn: number; // Maximum items in single row (NaN = no limit)
cellSize: Size; // Minimum part size for grid positioning (NaN x NaN = variable)
spacing: Size; // Minimum horizontal and vertical space between parts
// Alignment and Arrangement
alignment: GridAlignment; // Whether to use Part.location or position
arrangement: GridArrangement; // How to arrange parts in each row
sorting: GridSorting; // What order to place the parts
comparer: (a: Part, b: Part) => number; // Comparison function for sorting
// Layout Properties
arrangementOrigin: Point; // Top-left point for positioning the grid
// Methods
doLayout(coll: Diagram | Group | Iterable<Part>): void;
}
// Grid Layout Enums
enum GridAlignment {
Position = 0, // Position the top-left corner of each part at a grid point
Location = 1 // Position the part's Part.location at a grid point
}
enum GridArrangement {
LeftToRight = 10, // Fill each row from left to right
RightToLeft = 11 // Fill each row from right to left
}
enum GridSorting {
Forwards = 20, // Lay out each child in the order found
Reverse = 21, // Lay out each child in reverse order
Ascending = 22, // Lay out according to comparer sort order
Descending = 23 // Lay out in reverse comparer sort order
}Usage Examples:
// Icon grid layout
const iconGrid = new go.GridLayout({
wrappingWidth: 400, // wrap after 400 pixels width
cellSize: new go.Size(80, 80),
spacing: new go.Size(10, 10),
alignment: go.GridAlignment.Location,
arrangement: go.GridArrangement.LeftToRight
});
// Thumbnail gallery
const galleryGrid = new go.GridLayout({
wrappingColumn: 5, // 5 columns per row
cellSize: new go.Size(120, 90),
spacing: new go.Size(15, 15),
sorting: go.GridSorting.Ascending
});Layout algorithms work with network representations of the diagram structure.
class LayoutNetwork {
diagram: Diagram;
layout: Layout;
vertexes: List<LayoutVertex>;
edges: List<LayoutEdge>;
addParts(coll: Iterable<Part>): void;
addNodes(coll: Iterable<Node>): void;
addLinks(coll: Iterable<Link>): void;
findVertex(data: ObjectData): LayoutVertex | null;
}
class LayoutVertex {
network: LayoutNetwork;
node: Node;
data: ObjectData;
bounds: Rect;
focus: Point;
centerX: number;
centerY: number;
destinationEdges: List<LayoutEdge>;
sourceEdges: List<LayoutEdge>;
}
class LayoutEdge {
network: LayoutNetwork;
link: Link;
data: ObjectData;
fromVertex: LayoutVertex;
toVertex: LayoutVertex;
}// Different layouts based on node count
function selectLayout(nodeCount: number): go.Layout {
if (nodeCount < 10) {
return new go.CircularLayout();
} else if (nodeCount < 50) {
return new go.ForceDirectedLayout();
} else {
return new go.GridLayout({
wrappingWidth: 600,
cellSize: new go.Size(100, 60)
});
}
}
diagram.layout = selectLayout(diagram.nodes.count);// Tree layout for groups, force-directed for main diagram
diagram.layout = new go.ForceDirectedLayout();
diagram.groupTemplate = new go.Group('Auto', {
layout: new go.TreeLayout({
angle: 90,
layerSpacing: 20,
nodeSpacing: 10
})
})
.add(
new go.Shape('RoundedRectangle', {
fill: 'rgba(128,128,128,0.2)',
stroke: 'gray'
}),
new go.Placeholder({ padding: 10 })
);// Smooth layout transitions
function animateToNewLayout(newLayout: go.Layout) {
diagram.startTransaction('change layout');
// Store old positions
const oldPositions = new go.Map<go.Part, go.Point>();
diagram.nodes.each(node => {
oldPositions.add(node, node.position.copy());
});
// Apply new layout
diagram.layout = newLayout;
diagram.layoutDiagram(true);
// Animate from old to new positions
const animation = new go.Animation();
diagram.nodes.each(node => {
const oldPos = oldPositions.getValue(node);
const newPos = node.position;
animation.add(node, 'position', oldPos, newPos);
});
diagram.commitTransaction('change layout');
animation.start();
}class CustomLayout extends go.Layout {
doLayout(coll?: go.Iterable<go.Part>): void {
const diagram = this.diagram;
if (!diagram) return;
// Custom positioning logic
const nodes = (coll || diagram.nodes).toArray();
const center = new go.Point(0, 0);
const radius = 100;
nodes.forEach((node, i) => {
const angle = (i / nodes.length) * 2 * Math.PI;
const x = center.x + radius * Math.cos(angle);
const y = center.y + radius * Math.sin(angle);
node.position = new go.Point(x, y);
});
}
}
// Use custom layout
diagram.layout = new CustomLayout();