Symmetric Layout in React Diagram

The symmetric layout is a force-directed algorithm that positions nodes by simulating physical forces between them. Nodes are repositioned iteratively by moving them closer together or pushing them further apart until the system reaches an equilibrium state, creating a balanced and visually appealing arrangement.

Understanding Symmetric Layout

Symmetric layout works by applying spring-like forces between connected nodes and repulsion forces between all nodes. This creates a natural, organic layout where strongly connected components cluster together while maintaining proper spacing throughout the diagram.

The layout’s springLength property (of type number, default 50) defines the ideal length that edges should maintain. This serves as the resting length for the springs connecting nodes.

Edge attraction and vertex repulsion forces are controlled using the layout’s springFactor property (of type number). Increasing this value strengthens the repulsion force between nodes, pushing them further apart; decreasing it strengthens the attraction force between connected nodes, pulling them closer together.

The algorithm continues iterating until node positions stabilize and relative positions no longer change significantly between iterations. You can control the maximum number of iterations using the layout’s maxIteration (of type number, default 40).

The layout’s margin property (of type MarginModel) specifies the spacing between the layout content and the diagram boundary, keeping the arranged nodes from being placed flush against the diagram edges.

Implementation

To use the symmetric layout, inject the SymmetricLayout module into the diagram.

To arrange nodes using the symmetric layout, set the layout type as SymmetricalLayout. The following code demonstrates how to arrange nodes using the symmetric layout:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { DiagramComponent, Inject, SymmetricLayout } from '@syncfusion/ej2-react-diagrams';


//Initialize nodes 
let nodes = [];

//Initializes connectors
let connectors = [];

// creating the connection between the layout nodes and connectors.
function connectNodes(parentNode, childNode) {
    const connector = {
        id: parentNode.id + childNode.id,
        sourceID: parentNode.id,
        targetID: childNode.id,
        targetDecorator: { shape: 'None' },
    };
    return connector;
}

// Creates an elliptical node for the symmetric layout.
function getEllipse(name) {
    const shape = { type: 'Basic', shape: 'Ellipse' };
    const node = {
        id: name,
        height: 25,
        width: 25,
        style: { fill: '#ff6329' },
        shape: shape,
    };
    return node;
}

// creating the symmetrical layout child elements hierarchy.
function populateNodes() {
    const parentEllipse = getEllipse('p');
    nodes.push(parentEllipse);
    for (let i = 0; i < 2; i++) {
        const childEllipse_i = getEllipse('c' + i);
        nodes.push(childEllipse_i);
        for (let j = 0; j < 2; j++) {
            const childEllipse_j = getEllipse('c' + i + '-' + j);
            nodes.push(childEllipse_j);
            for (let k = 0; k < 6; k++) {
                const childEllipse_k = getEllipse('c' + i + '-' + j + '-' + k);
                nodes.push(childEllipse_k);
                connectors.push(connectNodes(childEllipse_j, childEllipse_k));
            }
            connectors.push(connectNodes(childEllipse_i, childEllipse_j));
        }
        connectors.push(connectNodes(parentEllipse, childEllipse_i));
    }
    return nodes;
}

//sets the layout child elements
populateNodes();

const layout = {
    //Sets layout type
    type: 'SymmetricalLayout',
    springLength: 80,
    springFactor: 0.8,
    maxIteration: 500,
    margin: { left: 20, top: 20 },
};

export default function App() {

    return (
        <div>
            <DiagramComponent
                id="container"
                width={'80%'}
                height={'550px'}
                nodes={nodes}
                connectors={connectors}

                //Uses layout to auto-arrange nodes on the diagram page
                layout={layout}
            >

                {/* Inject necessary services for the diagram */}
                <Inject services={[SymmetricLayout]} />
            </DiagramComponent>

        </div>
    );
}

// Render the App component into the 'diagram' element in the DOM
const root = ReactDOM.createRoot(document.getElementById('diagram'));
root.render(<App />);
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { DiagramComponent, Inject, SymmetricLayout, ConnectorModel, NodeModel, BasicShapeModel } from '@syncfusion/ej2-react-diagrams';


//Initialize nodes 
let nodes: NodeModel[] = [];

//Initializes connectors
let connectors: ConnectorModel[] = [];

// creating the connection between the layout nodes and connectors.
function connectNodes(parentNode: NodeModel | any, childNode: NodeModel): ConnectorModel {
  const connector: ConnectorModel = {
    id: parentNode.id + childNode.id,
    sourceID: parentNode.id,
    targetID: childNode.id,
    targetDecorator: { shape: 'None' },
  };
  return connector;
}

// Creates an elliptical node for the symmetric layout.
function getEllipse(name: string): NodeModel {
  const shape: BasicShapeModel = {
    type: 'Basic',
    shape: 'Ellipse',
  };
  const node: NodeModel = {
    id: name,
    height: 25,
    width: 25,
    style: { fill: '#ff6329' },
    shape: shape,
  };
  return node;
}

// creating the symmetrical layout child elements hierarchy.
function populateNodes() {
  const parentEllipse: NodeModel = getEllipse('p');
  nodes.push(parentEllipse);
  for (let i = 0; i < 2; i++) {
    const childEllipse_i: NodeModel = getEllipse('c' + i);
    nodes.push(childEllipse_i);
    for (let j = 0; j < 2; j++) {
      const childEllipse_j: NodeModel = getEllipse('c' + i + '-' + j);
      nodes.push(childEllipse_j);
      for (let k = 0; k < 6; k++) {
        const childEllipse_k: NodeModel = getEllipse('c' + i + '-' + j + '-' + k);
        nodes.push(childEllipse_k);
        connectors.push(connectNodes(childEllipse_j, childEllipse_k));
      }
      connectors.push(connectNodes(childEllipse_i, childEllipse_j));
    }
    connectors.push(connectNodes(parentEllipse, childEllipse_i));
  }
  return nodes;
}

//sets the layout child elements
populateNodes();

const layout = {
  //Sets layout type
  type: 'SymmetricalLayout',
  springLength: 80,
  springFactor: 0.8,
  maxIteration: 500,
  margin: { left: 20, top: 20 },
};

export default function App() {

  return (
    <div>
      <DiagramComponent
        id="container"
        width={'80%'}
        height={'550px'}
        nodes={nodes}
        connectors={connectors}

        //Uses layout to auto-arrange nodes on the diagram page
        layout={layout}
      >

        {/* Inject necessary services for the diagram */}
        <Inject services={[SymmetricLayout]} />
      </DiagramComponent>

    </div>
  );
}

// Render the App component into the 'diagram' element in the DOM
const root = ReactDOM.createRoot(document.getElementById('diagram') as HTMLElement);
root.render(<App />);

Symmetric layout arranging nodes via spring attraction and repulsion forces