Hero Background

Sugiyama

May 8, 2025

A Brief explanation of sugiyama methodology in typescript

  • typescriptTypescript
  • algorithmsAlgorithms
  • visualizationVisualization
typescriptalgorithmsvisualization

Sugiyama Methodology

What is it

Layered graph drawing or hierarchical graph drawing is a type of graph drawing in which the vertices of a directed graph are drawn in horizontal rows or layers with the edges generally directed downwards. It is also known as Sugiyama-style graph drawing after Kozo Sugiyama, who first developed this drawing style.

The Sugiyama Method(or the Sugiyama-framework) is the gold standard for creating layered drawings or DAGs. The layout algorithm consists of four-major steps: Cycle Removal, Layer Assignment, Crossing Reduction, and Straightening and Packing.

The problem

Given a Direct Graph

1. Cycle removal :

Make the graph a DAG (Directed Acyclic Graph). By inverting the direction of some of the edges we remove every cycle.

2. Layer assignment

Assign nodes to layers, discretes y-coordinates and adding dummy nodes for long edges (spanning multiple layers)

3. Crossing removal

Ordering nodes across layers to minimize intersections. Here we add dummy nodes.

4. Assign coordinates

Assign exact X-coordinates and remove dummy nodes*.

5. Drawing

Draw the actual lines/curves and nodes.

Implementation

export function SugiyamaMethodology(inputGraph:IGraph){
 
    printGraph(inputGraph);
    console.log("Is cyclic?",inputGraph.isCyclic());
 
    // Remove Cycle by inverting some edges
    let gcr = new GreedyCycleRemoval(inputGraph);
    let invertedEdges = gcr.removeCycle();
 
    console.log("Inverted Edges:",invertedEdges);
    console.log("Is now cyclic?",inputGraph.isCyclic());
 
    // Calculate Longest Path Layering
    let lpl = new LongestPathLayering(inputGraph);
    let { layers, graphDummy } = lpl.computeLayering();
 
    //Remove Crossings using barycentr algorithm
    let cr = new CrossingRemovalBarycenter();
    cr.removeCrossings(layers,graphDummy);
 
    // Assign coordinates
    let ca = new CoordinateAssignment();
    let coordMap = ca.assignCoord(layers);
 
    return {layers, graphDummy, coordMap};
 
}

1. Cycle Removal

  1. Find the feedback arc set R by performing a DFS that determines the ordering and adding all the back edges to R

    • Reverses |E| - |V| + 1 edges
  2. Choose an ordering arbitrarily and count the number of leward edges
    If greater than half of the edges, choose the opposite ordering

    • Ensures that R is less than half of the edges

Step 1: Greedy Cycle Removal

To identify a feedback arc set R through a vertex ordering and subsequent removal of the leward edges

private invertLewardEdges(){
    let vertexOrder = this.findGreedyOrder();
    let lewardEdges = [];
    for(let edge of this.graph.getEdges()){
        let s = edge.getSource();
        let t = edge.getTarget();
        if(vertexOrder.indexOf(s) > vertexOrder.indexOf(t)){
            lewardEdges.push(edge);
        }
    }
    // Invert Edges
    for(let edge of lewardEdges){
        this.graph.invertEdge(edge);
    }
    return lewardEdges;
}

2. Longest Path layering

The longest path algorithm of Tamassia determine a layering with the minimum number of complexity O(n+m)

private assignLayers() {
    let graph = copyGraph(this.graph);
    // Define Layers
    let layers = [];
 
    while (graph.getSink()) {
        let sinks = graph.getAllSinks();
 
        layers.unshift(sinks);
        for (let sink of sinks) {
            graph.removeNode(sink);
        }
    }
    return layers;
}
let layers = this.assignLayers();
let graphDummy =  this.createDummyVerteces(layers);

3. Crossing Removal

 removeCrossings(
        layers: IVertex[][],
        graph: IGraph,
        iterations = 4
    ) {
 
        for (let iter = 0; iter < iterations; iter++) {
 
            // 🔽 Downward sweep
            for (let i = 1; i < layers.length; i++) {
                this.orderLayer(layers[i - 1], layers[i], graph, true);
            }
 
            // 🔼 Upward sweep
            for (let i = layers.length - 2; i >= 0; i--) {
                this.orderLayer(layers[i + 1], layers[i], graph, false);
            }
        }
    }
    
 private orderLayer(
        fixedLayer: IVertex[],
        freeLayer: IVertex[],
        graph: IGraph,
        useIncoming: boolean
    ) {
 
        const pos = new Map<IVertex, number>();
        fixedLayer.forEach((v, i) => pos.set(v, i));
 
        const originalIndex = new Map<IVertex, number>();
        freeLayer.forEach((v, i) => originalIndex.set(v, i));
        // Compute Barycenter
        const bary = this.computeBarycenter(
            pos,
            freeLayer,
            graph,
            useIncoming
        );
        // Order Barycenter
        freeLayer.sort((a, b) => {
            const diff = bary.get(a)! - bary.get(b)!;
            return diff !== 0
                ? diff
                : originalIndex.get(a)! - originalIndex.get(b)!;
        });
    }

4. Coordinates Assignment

assignCoord(layers: IVertex[][]) {
 
    let map = new Map<IVertex, { x: number, y: number }>();
 
    let maxLayer = Math.max(...layers.map(l => l.length));
 
    for (let i = 0; i < layers.length; i++) {
        const dx = (maxLayer * this._xSpacing) / layers[i].length;
 
        let x = dx / 2;
        let y = i * this._ySpacing;
 
        for (let v of layers[i]) {
            map.set(v, { x, y });
            x += dx;
        }
    }
 
    return map;
}

5. Drawing

For rendering and visualization, we use D3.js, a powerful JavaScript library for creating dynamic and interactive data-driven graphics. D3 provides control over DOM and SVG elements, allowing us to render nodes using the calculated x and y coordinates.