or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

binding-animation.mdcore-diagram.mddata-models.mdgeometry-collections.mdgraphobject-hierarchy.mdindex.mdinteractive-tools.mdlayout-system.mdtheme-management.mdvisual-elements.md
tile.json

layout-system.mddocs/

Layout System

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.

Capabilities

Layout (Abstract Base)

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'
}

TreeLayout extends Layout

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
});

LayeredDigraphLayout extends Layout

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
});

CircularLayout extends Layout

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
});

ForceDirectedLayout extends Layout

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
});

GridLayout extends Layout

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 Networks

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;
}

Common Patterns

Conditional Layouts

// 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);

Group-Specific Layouts

// 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 })
);

Animated Layout Transitions

// 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();
}

Custom Layout Algorithm

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();