Skip to content

Conversation

@MartinKanera
Copy link
Contributor

@MartinKanera MartinKanera commented Mar 7, 2025

Description

Add a new layout package utilizing the Microsoft Automatic Graph Layout (MSAGL).

The layout function arranges either an entire JointJS graph or a specified subset of cells using MSAGL. It converts the cells into MSAGL geometry and applies the Sugiyama layout algorithm with Rectilinear or SplineBundling edge routing.

Key Features

  • Hierarchical Layout: Uses the Sugiyama algorithm for layered graph layout
  • Nested Subgraphs: Supports elements with embedded children as subgraphs
  • Edge Routing: Rectilinear or SplineBundling edge routing modes
  • Customizable Callbacks: Control how layout results are applied to elements

Caveats

  • Rectilinear self‑loops – When edgeRoutingMode is set to Rectilinear, self‑edges use a configurable vertical offset controlled by rectilinearSelfEdgeOffset (default 10). This is a stop‑gap until upstream msagljs provides native rectilinear self‑loop geometry.
  • Subgraph resizing – Parent elements that embed other elements are resized by the layout to tightly pack all their children.
  • Subgraph layout direction - Layout inside subgraphs is always set to TB (Top-to-Bottom) direction, as other directions can cause layout issues.
  • Link labels in subgraphs – Link labels within subgraphs may be positioned incorrectly, despite the layout correctly reserving space for them.
  • Obstacle padding (first‑bend distance) – MSAGL exposes an edgeRoutingSettings.Padding option intended to keep edges a minimum distance away from obstacles (nodes) and thus control where the first bend occurs. In msagljs this setting is currently not working.
  • ID handling - MSAGL expects IDs to be string values so id: 1 and id: '1' are considered the same.

API

// Callback type definitions
type GetSizeCallback = (element: dia.Element) => dia.Size;
type GetLabelSizeCallback = (cell: dia.Cell) => dia.Size | undefined;
type SetPositionCallback = (element: dia.Element, position: dia.Point) => void;
type SetVerticesCallback = (link: dia.Link, vertices: dia.Point[]) => void;
type SetLabelsCallback = (link: dia.Link, labelBBox: dia.BBox, points: dia.Point[]) => void;
type SetAnchorCallback = (link: dia.Link, linkEndPoint: dia.Point, bbox: dia.BBox, endType: 'source' | 'target') => void;
type SetClusterSizeCallback = (element: dia.Element, size: dia.Size) => void;

interface Options {
    // Layout direction (default: LayerDirectionEnum.TB)
    layerDirection?: LayerDirectionEnum;

    // Spacing (defaults: layerSeparation: 40, nodeSeparation: 20)
    layerSeparation?: number;
    nodeSeparation?: number;

    // Edge routing (defaults: edgeRoutingMode: EdgeRoutingMode.Rectilinear, polylinePadding: 1)
    edgeRoutingMode?: EdgeRoutingMode;
    polylinePadding?: number;

    // Rectilinear self-loop offset
    // Vertical pixel offset applied to the two inner vertices of a self-edge
    // when edgeRoutingMode is Rectilinear. Default: 10
    rectilinearSelfEdgeOffset?: number;

    // Grid, origin and padding (defaults: gridSize: 0, x: 10, y: 10, clusterPadding: 10
    gridSize?: number;
    x?: number;
    y?: number;
    clusterPadding?: dia.Sides;

    // Element sizing callbacks (defaults: element.size(), element/get('labelSize'))
    getSize?: GetSizeCallback;
    getLabelSize?: GetLabelSizeCallback;

    // Layout application callbacks
    setPosition?: SetPositionCallback; // Default: true - built-in implementation
    setVertices?: boolean | SetVerticesCallback; // Default: true - built-in implementation
    setLabels?: boolean | SetLabelsCallback; // Default: true - built-in implementation
    setAnchor?: boolean | SetAnchorCallback; // Default: true - built-in implementation
    setClusterSize?: SetClusterSizeCallback; // Default: element.size(size)
}

interface LayoutResult {
    bbox: g.Rect;
    msGraph: Graph;
    msGeomGraph: GeomGraph;
}

// Enums
enum LayerDirectionEnum {
    TB, // Top to Bottom
    BT, // Bottom to Top  
    LR, // Left to Right
    RL  // Right to Left
}

enum EdgeRoutingMode {
    SplineBundling = 1, // Smooth curved edges
    Rectilinear = 4     // Orthogonal edges with right angles
}
function layout(graphOrCells: dia.Graph | dia.Cell[], options?: Options): LayoutResult

@MartinKanera MartinKanera self-assigned this Mar 7, 2025
@MartinKanera MartinKanera requested a review from kumilingus March 22, 2025 00:49
@MartinKanera MartinKanera marked this pull request as ready for review August 7, 2025 14:17
@MartinKanera MartinKanera changed the title (WIP) feat: add @joint/layout-msagl package feat: add @joint/layout-msagl package Aug 7, 2025
…function return type and add LayoutResult interface details
…I and graph presets, update styles and TypeScript types
@kumilingus kumilingus merged commit 7b268a2 into clientIO:master Oct 17, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants