Page Index Toggle Pages: [1] 2  Send TopicPrint
Hot Topic (More than 10 Replies) Questions about the LayeredLayout (Read 493 times)
jay youngblood
YaBB Newbies
*
Offline


I Love MindFusion!

Posts: 8
Location: Germany
Joined: Mar 11th, 2026
Questions about the LayeredLayout
Mar 11th, 2026 at 1:08pm
Print Post  
Hello,

we use the LayeredLayout to display a Corporate Structure Diagram. The basics are working great but I have some questions:

1. How can I order nodes horizontaly in a level?

2. How can I have all the links that have the same destination overlay each other? If multiple nodes have a link to the same node the links should overlay each other.

3. How can I get the triangle to contain it's text? Either have the text break or the triangle to be bigger.

Code (Javascript)
Select All
export class AppComponent {
  @ViewChild(DiagramView) diagramViewControl!: DiagramView;
  diagram!: Diagramming.Diagram;

  public ngOnInit() {
    this.diagram = new Diagramming.Diagram();
    this.diagram.bounds = new Drawing.Rect(0, 0, 1000, 1000);
    this.diagram.linkRouter = new Diagramming.GridRouter();
  }

  onLoadClick(): void {
    this.diagram.clearAll();
    this.buildDiagram2(companies);
  }

  buildDiagram2(graph: any): void {
    this.diagram.showGrid = true;

    var nodeMap = [];

    const entities = graph.entities.sort((a: any, b: any) => {
      if (a.tier != b.tier) return a.tier - b.tier;
      return a.order - b.order;
    });

    for (var entity of entities) {

      const node = new Diagramming.ShapeNode(this.diagram);
      nodeMap[entity.id] = node;
      node.text = entity.name;
      node.id = entity.id;
      node.tag = entity.tier;
      node.bounds = new Rect(0, 0, 30, 30);
      node.anchorPattern = Diagramming.AnchorPattern.topInBottomOut;

      if (entity.shape == "oval") {
        node.shape = "Ellipse";
        node.brush = entity.isMain ? "#0070a8" : "#f3fafe";
        node.pen = "#0070a8"
        node.bounds = new Rect(0, 0, 53, 26)
      } else if (entity.shape == "triangle") {
        node.shape = "Triangle";
        node.brush = "#ccc";
        node.bounds = new Rect(0, 0, 53, 33);
      } else {
        node.shape = "RoundRect";
        node.brush = entity.isMain ? "#0070a8" : "#f3fafe";
        node.pen = "#0070a8"
        node.bounds = new Rect(0, 0, 30, 26);
      }
      //node.resizeToFitText(Diagramming.FitSize.KeepRatio);
      this.diagram.addItem(node);
    }

    const links = graph.relationships;
    for (const link of links) {
      const l = new Diagramming.DiagramLink(this.diagram, nodeMap[link.from], nodeMap[link.to]);
      l.headShape = Diagramming.ArrowHeads.None();
      l.segmentCount = 2;
      this.diagram.addItem(l);
    }
	const layout = new Graphs.LayeredLayout();
	layout.direction = Graphs.LayoutDirection.TopToBottom;
	layout.siftingRounds = 0;
	layout.nodeDistance = 8;
	layout.layerDistance = 12;
	layout.anchoring = Graphs.Anchoring.Reassign;

	this.diagram.arrange(layout);
  }
} 




« Last Edit: Mar 12th, 2026 at 2:17pm by jay youngblood »  
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #1 - Mar 11th, 2026 at 1:25pm
Print Post  
Hi,

By "order nodes horizontally in a level" do you mean specifying a custom order? Generally that's not something LayeredLayout supports, as it swaps positions of nodes while it tries to minimize number of link crossings across layers. A custom order might still work if you set siftingRounds to 0, and  sort parent node's outgoing links to match the order you need.

If your diagram represents a tree (each node having a single parent), and not a general graph with multiple in/out links per node, you could run TreeLayout instead. Order of child nodes should depend on order of parent's links to children then. TreeLayout should also take care of your second issue.

Another possibility to make links overlay might be setting diagram.router.avoidOverlaps to false and calling routeAllLinks after applying layout.

Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
jay youngblood
YaBB Newbies
*
Offline


I Love MindFusion!

Posts: 8
Location: Germany
Joined: Mar 11th, 2026
Re: Questions about the LayeredLayout
Reply #2 - Mar 11th, 2026 at 1:33pm
Print Post  
Yes I mean a specifying a custom order or just order in the order the're added would be fine too.

The diagram looks like a tree but a node can have none, one or multiple parents and the same for children.

The router.avoidOverlaps didn't change the behavior.

I added a Screenshot of how it should loo, hope that helps Smiley
  

prototype.png ( 55 KB | 12 Downloads )
prototype.png
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #3 - Mar 11th, 2026 at 1:48pm
Print Post  
Try sorting both diagram.nodes array and all parent-to-child links then, these would both affect order while building layers. Then either set LayereLayout.siftingRounds = 0 to stop swapping nodes in layer, or try running TreeLayout. TreeLayout will skip links that are not part of the spanning tree, but if you follow it up with routeAllLinks call, it might still look good enough.

Re avoidOverlaps, it's not available in GridRouter. If you are on version 4.8+ it's available in new default CompositeRouter, and on older versions it's available in older default simple Router class. GridRouter might work if you set its crossingCost to 0 (that's since v4.7.1).

Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #4 - Mar 11th, 2026 at 1:53pm
Print Post  
On older versions, you could adjust links' controlPoints coordinates after routing, to make sure horizontal segments' points are on same Y when they have same target node.

Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #5 - Mar 11th, 2026 at 2:12pm
Print Post  
Quote:
3. How can I get the triangle to contain it's text? Either have the text break or the triangle to be bigger.


Text does not wrap because it fits within node's width, comparing with sizes of other nodes from your screenshot. I think you are using "Triangle" shape, which is intended for arrowheads and does not cover full width of nodes. You could replace it with "Alternative" shape to make actual node width more apparent.

"Alternative" wouldn't fix the wrapping problem though, as in JavaScript version we don't consider shape geometry, only rectangular boundaries. We'll try to port our PolygonalTextLayout property from desktop versions to JS for next release. Meanwhile you could try setting larger textPadding on the node to force it wrap at smaller widths (and optionally move text close to bottom). Or set the Shape.textArea property of "Alternative" / "Triangle" shapes to a Rect instance specified as percentage, to achieve similar effect as textPadding, but globally for all triangular nodes.

Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
jay youngblood
YaBB Newbies
*
Offline


I Love MindFusion!

Posts: 8
Location: Germany
Joined: Mar 11th, 2026
Re: Questions about the LayeredLayout
Reply #6 - Mar 11th, 2026 at 2:32pm
Print Post  
Thank you very much for the answers Smiley

At the moment we use the latest version of mindfusion since we're just testing I downloaded the dagram-starter-angular project and modified that. In the package.json we have: "@mindfusion/diagramming-angular": "latest".

The idea with changing the text-padding worked and I also changed the Shape to "Alternative".

But the ordering still doesn't work Sad I changed the example to be smaller.

Regarding the TreeLayout, I tought it couldn't handle multiple parents?

Code (Javascript)
Select All
const node = new Diagramming.ShapeNode(this.diagram);
nodeMap[entity.id] = node;
node.text = entity.name;
node.id = entity.id;
node.tag = `${entity.tier}-${entity.order ?? 1}`;


const l = new Diagramming.DiagramLink(this.diagram, nodeMap[link.from], nodeMap[link.to]);
l.headShape = Diagramming.ArrowHeads.None();
l.tag = li;
li++;
this.diagram.addItem(l);


const layout = new Graphs.LayeredLayout();
layout.direction = Graphs.LayoutDirection.TopToBottom;
layout.siftingRounds = 0;
layout.nodeDistance = 8;
layout.layerDistance = 12;
layout.anchoring = Graphs.Anchoring.Reassign;

this.diagram.arrange(layout);

this.diagram.nodes.sort((a:any, b:any) => {
const aP = a.tag.split("-");
const bP = b.tag.split("-");

if (aP[0] != bP[0]) return aP[0] - bP[0];
return aP[1] - bP[1];
});

this.diagram.links.sort((a:any, b:any) => {
return a.tag - b.tag;
}); 


{
     "entities": [
           {
                 "id": "if_a",
                 "order": 4,
                 "name": "Investment Fund A",
                 "jurisdiction": "Ontario",
                 "shape": "triangle",
                 "tier": 5
           },
           {
                 "id": "h_b",
                 "name": "Holding B",
                 "order": 2,
                 "jurisdiction": "Luxembourg",
                 "shape": "oval",
                 "highlight": true,
                 "tier": 6,
                 "isMain": true
           },
           {
                 "id": "h_a",
                 "name": "Holding A",
                 "order": 1,
                 "jurisdiction": "Spain",
                 "shape": "oval",
                 "tier": 6
           },
           {
                 "id": "h_c",
                 "name": "Holding C",
                 "order": 3,
                 "jurisdiction": "Spain",
                 "shape": "oval",
                 "tier": 6
           },
           {
                 "id": "c_aa",
                 "name": "Company AA",
                 "order": 5,
                 "jurisdiction": "Spain",
                 "shape": "rectangle",
                 "tier": 7
           },
           {
                 "id": "c_bb",
                 "name": "Company BB",
                 "order": 6,
                 "jurisdiction": "Spain",
                 "shape": "rectangle",
                 "tier": 8
           },
           {
                 "id": "c_a",
                 "name": "Company A",
                 "order": 7,
                 "jurisdiction": "Spain",
                 "shape": "rectangle",
                 "tax_group_spain": true,
                 "tier": 9
           },
           {
                 "id": "c_b",
                 "name": "Company B",
                 "order": 8,
                 "jurisdiction": "Spain",
                 "shape": "rectangle",
                 "tax_group_spain": true,
                 "tier": 9
           },
           {
                 "id": "c_c",
                 "name": "Company C",
                 "order": 9,
                 "jurisdiction": "Spain",
                 "shape": "rectangle",
                 "tax_group_spain": true,
                 "tier": 9
           },
           {
                 "id": "c_d",
                 "name": "Company D",
                 "order": 10,
                 "jurisdiction": "Spain",
                 "shape": "rectangle",
                 "tax_group_spain": true,
                 "tier": 9
           }
     ],
     "relationships": [
           {
                 "from": "if_a",
                 "to": "h_b",
                 "type": "ownership",
                 "stake_pct": 100
           },
           {
                 "from": "h_b",
                 "to": "c_aa",
                 "type": "ownership",
                 "stake_pct": 64.78
           },
           {
                 "from": "h_a",
                 "to": "c_aa",
                 "type": "ownership",
                 "stake_pct": 5.0
           },
           {
                 "from": "h_c",
                 "to": "c_aa",
                 "type": "ownership",
                 "stake_pct": 5.0
           },
           {
                 "from": "c_aa",
                 "to": "c_bb",
                 "type": "ownership",
                 "stake_pct": 100
           },
           {
                 "from": "c_bb",
                 "to": "c_a",
                 "type": "ownership",
                 "stake_pct": 99.9
           },
           {
                 "from": "c_bb",
                 "to": "c_b",
                 "type": "ownership",
                 "stake_pct": 89.82
           },
           {
                 "from": "c_bb",
                 "to": "c_c",
                 "type": "ownership",
                 "stake_pct": 89.82
           },
           {
                 "from": "c_bb",
                 "to": "c_d",
                 "type": "ownership",
                 "stake_pct": 89.82
           }
     ]
}


  

smaller_sample.png ( 60 KB | 14 Downloads )
smaller_sample.png
Back to top
 
IP Logged
 
jay youngblood
YaBB Newbies
*
Offline


I Love MindFusion!

Posts: 8
Location: Germany
Joined: Mar 11th, 2026
Re: Questions about the LayeredLayout
Reply #7 - Mar 11th, 2026 at 2:45pm
Print Post  
If I remove the link from "Investment Fund A" to "Holding B" the ordering is correct.
  
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #8 - Mar 11th, 2026 at 4:40pm
Print Post  
Right, there's some first-in-first-out queueing involved starting from the start node and following link connectivity, and for second layer it would first queue the Holding B node. So the sorting idea would work only for nodes under same parent (when links are sorted) and for nodes without parent.

We could add some support for sorting layers using a custom comparer for next release if that's very important. Until then, you could also try sorting the graph layers yourself by replacing LayeredLayout.prototype.assignLayers with your own function that calls the original and sorts each layer. The original assignLayers ends with this code:

Code
Select All
for (const [node, layer] of layering)
{
    node.layer = layer;
    this.layers[layer].push(node);
} 



So after calling the original, your replacement could loop over the this.layers arrays and sort each one. The layer arrays contain Vertex objects, whose Vertex.owner field points to respective DiagramNode. Something like this assuming you have layer-wide order and not just when under same parent:

Code
Select All
// untested code
const originalAssignLayers = Graphs.LayeredLayout.prototype.assignLayers;

Graphs.LayeredLayout.prototype.assignLayers = function () {

    // call the original method first
    originalAssignLayers.call(this);

    // sort each layer afterwards
    for (let i = 0; i < this.layers.length; i++) {
        const layer = this.layers[i];

        layer.sort(function (v1, v2) {
            // v1 and v2 are Vertex objects
            const n1 = v1.owner; // DiagramNode
            const n2 = v2.owner; // DiagramNode

            // compareNodes should return -1 / 0 / 1
            return compareNodes(n1, n2);
        });
    }
};

// implements this according to your order fields
function compareNodes(nodeA, nodeB) {
    ...
} 



Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
jay youngblood
YaBB Newbies
*
Offline


I Love MindFusion!

Posts: 8
Location: Germany
Joined: Mar 11th, 2026
Re: Questions about the LayeredLayout
Reply #9 - Mar 12th, 2026 at 8:12am
Print Post  
Thanks again for the answers. After the sorting the Vertex are sorted correctly in the specific Layers but are still displayed in the wrong order in the diagram.

Code (Javascript)
Select All
console.log("using layered layout");

      // Patch assignLayers to respect our order field
      const originalAssignLayers = Graphs.LayeredLayout.prototype.assignLayers;

      Graphs.LayeredLayout.prototype.assignLayers = function () {
        // Call original first
        originalAssignLayers.call(this);

        // Sort each layer by our entity order
        for (let i = 0; i < this.layers.length; i++) {
          console.log(`layer ${i} before sorting`, this.layers[i].map((x: any) => x.debugId));

          // this.layers[i].sort((v1: any, v2: any) => {
          //   const n1 = v1.owner as Diagramming.DiagramNode;
          //   const n2 = v2.owner as Diagramming.DiagramNode;
          //   const res = compareNodes(n1, n2);
          //   return res;
          // });

          const layer = this.layers[i];
          const vertices = [...layer];
          layer.length = 0;

          vertices.sort((v1: any, v2: any) => {
            const tag1 = (v1.owner as Diagramming.DiagramNode).tag as string;
            const tag2 = (v2.owner as Diagramming.DiagramNode).tag as string;
            const order1 = parseInt(tag1 ?? '0');
            const order2 = parseInt(tag2 ?? '0');
            return order1 - order2;
          });

          vertices.forEach((v: any) => layer.push(v));

          console.log(`layer ${i} after sorting`, this.layers[i].map((x: any) => x.debugId));
        }
      };

      const compareNodes = (nodeA: Diagramming.DiagramNode, nodeB: Diagramming.DiagramNode): number => {
        const entityA = entities.find((e: any) => e.id === nodeA.id);
        const entityB = entities.find((e: any) => e.id === nodeB.id);
        const orderA = entityA?.order ?? 0;
        const orderB = entityB?.order ?? 0;

        console.log(`comparing ${entityA.name} to ${entityB.name}. order A: ${orderA} order B: ${orderB}`);

        if (orderA > orderB) return 1;
        if (orderA < orderB) return -1;
        return 0;
      };

      const layout = new Graphs.LayeredLayout();
      layout.direction = Graphs.LayoutDirection.TopToBottom;
      layout.siftingRounds = 3;
      layout.nodeDistance = 8;
      layout.layerDistance = 15;
      layout.anchoring = Graphs.Anchoring.Reassign;

      console.log("arranging diagram");
      this.diagram.arrange(layout);
      console.log("arranged diagram"); 

  

console-log.png ( 55 KB | 8 Downloads )
console-log.png
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #10 - Mar 12th, 2026 at 11:24am
Print Post  
You'd still need to run this with siftingRounds = 0, as the node-swapping iterations run after that assignLayers call.

Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
jay youngblood
YaBB Newbies
*
Offline


I Love MindFusion!

Posts: 8
Location: Germany
Joined: Mar 11th, 2026
Re: Questions about the LayeredLayout
Reply #11 - Mar 12th, 2026 at 11:51am
Print Post  
Even after changing that it still doesn't work. The diagram looks the same. Huh
  
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #12 - Mar 12th, 2026 at 12:16pm
Print Post  
LayeredLayout.arrange looks like this, and siftingRounds is actually only used in the swapPairs function, but minimizeCrossings changes node order too:

Code
Select All
this.assignLayers();

// set initial grid positions
...

// reduce crossings
this.minimizeCrossings();

// further reduce crossings through pair swap
this.swapPairs();
 



You could try replacing minimizeCrossings() call with a no-op by setting it to an empty function in prototype. However that's also where longer links are processed too (ones connecting nodes from non-adjacent layers), so you might see such links remaining unsorted at the end of layers if your diagrams have them (seems not in screenshots above).

Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
jay youngblood
YaBB Newbies
*
Offline


I Love MindFusion!

Posts: 8
Location: Germany
Joined: Mar 11th, 2026
Re: Questions about the LayeredLayout
Reply #13 - Mar 13th, 2026 at 8:11am
Print Post  
Thank you for the amazing help, it works now. Sometimes the nodes are a little far away but that isn't to bad.

Code (Javascript)
Select All
(Graphs.LayeredLayout.prototype as any).minimizeCrossings = function () { }; 



Now the only thing left is to position the links that the overlap on the horizontal segments where possible Smiley
  

working.png ( 88 KB | 11 Downloads )
working.png
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3473
Joined: Oct 19th, 2005
Re: Questions about the LayeredLayout
Reply #14 - Mar 13th, 2026 at 8:43am
Print Post  
Hi,

Try removing GridRouter and setting avoidOverlaps = false on default one. If that does not help, you could loop over links to adjust control points:

Code
Select All
const distance = 10;

for (var link of diagram.links)
{
    let cps = link.controlPoints;
    if (cps.length == 4)
    {
        // check if the middle segment of the link is horizontal
        if (cps[1].y == cps[2].y)
        {
            // origin above destination
            if (link.origin.bounds.y < link.destination.bounds.y)
            {
                let newY = link.destination.bounds.y - distance;
                cps[1].y = newY;
                cps[2].y = newY;
            }
            else
            {
                let newY = link.origin.bounds.y - distance;
                cps[1].y = newY;
                cps[2].y = newY;
            }
            link.updateFromPoints();
        }
    }
} 



Regards,
Slavcho
Mindfusion
  
Back to top
 
IP Logged
 
Page Index Toggle Pages: [1] 2 
Send TopicPrint