Reliability Block Diagram layout

Hi folks, I’m trying to do a layout like this, based on Reliability Block Diagram model:
image

I am using LayeredDigraphLayout I’m getting this (ignore style, the important thing is the alignment):
image

I tried using ParallelLayout but there is some limitations with that layout.

Can you guys give me any tips on how can I align those parallel blocks to be centered like the first picture?
Or even how can I do that programmatically?

I would think that ParallelLayout is what you would want. What was the problem with that?

How is your model data structured? Or how do you want it to be?

Here is one possible solution, assuming you don’t need to nest:

<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
  function init() {
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
        {
          isReadOnly: true,
          layout: $(go.TreeLayout, { layerSpacing: 20 })
        });

    myDiagram.nodeTemplate =
      $(go.Node, "Spot",
        { selectionObjectName: "BODY" },
        $(go.Panel, "Auto",
          { name: "BODY", width: 80, height: 35 },
          $(go.Shape,
            { fill: "white" },
            new go.Binding("fill", "color")),
          $(go.TextBlock,
            new go.Binding("text"))
        ),
        // in place of links connecting with each node
        $(go.Shape, "LineH", { alignment: go.Spot.Left, alignmentFocus: go.Spot.Right, width: 10, height: 0 }),
        $(go.Shape, "LineH", { alignment: go.Spot.Right, alignmentFocus: go.Spot.Left, width: 10, height: 0 })
      );

    myDiagram.nodeTemplateMap.add("Split",
      $(go.Node,
        { locationSpot: go.Spot.Center },
        $(go.Shape, "Circle",
          { fill: "white", desiredSize: new go.Size(6, 6) })
      ));

    myDiagram.nodeTemplateMap.add("Merge",
      $(go.Node,
        { locationSpot: go.Spot.Center },
        $(go.Shape, "Circle",
          { desiredSize: new go.Size(6, 6) })
      ));

    myDiagram.linkTemplate =
      $(go.Link,
        { selectable: false, routing: go.Link.Orthogonal },
        $(go.Shape)
      );

    // define the Group template to be fairly simple
    myDiagram.groupTemplate =
      $(go.Group, "Auto",
        { layout: $(go.GridLayout, { wrappingColumn: 1, cellSize: new go.Size(1, 1) }) },
        $(go.Shape, { fill: "transparent" }),  // draws vertical segments as if links connecting each node
        $(go.Placeholder, { padding: new go.Margin(-17.5, 0) })  // half the node height
      );

    myDiagram.model = new go.GraphLinksModel(
    [
      { key: 0, category: "Split" },
      { key: 9999, category: "Merge" },
      { key: -1, isGroup: true },
      { key: 10, text: "Alpha 1", color: "lightblue", group: -1 },
      { key: 11, text: "Alpha 2", color: "lightblue", group: -1 },
      { key: 12, text: "Alpha 3", color: "lightblue", group: -1 },
      { key: -2, isGroup: true },
      { key: 20, text: "Beta 1", color: "orange", group: -2 },
      { key: 21, text: "Beta 2", color: "orange", group: -2 },
      { key: -3, isGroup: true },
      { key: 30, text: "Gamma", color: "lightgreen", group: -3 },
      { key: -4, isGroup: true },
      { key: 40, text: "Delta 1", color: "pink", group: -4 },
      { key: 41, text: "Delta 2", color: "pink", group: -4 }
    ],
    [
      { from: 0, to: -1, },
      { from: -1, to: -2 },
      { from: -2, to: -3 },
      { from: -3, to: -4 },
      { from: -4, to: 9999 }
    ]);
  }
</script>
</head>
<body onload="init()">
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
</body>
</html>

The result:

The idea for the model is that you just need to define a bunch of groups, basically one per “column”, and you define the relationships between the groups so that you get the ordering that you want.

I suppose the “Split” and “Merge” (a.k.a. “Begin” and “End”) nodes don’t need to be part of the model – they could be added whenever one loads a model.

Thanks for the reply!
My model isn’t static. The user can add more blocks dinamically and there are a ton of possibilities (e.g. after the “begin” could have a series block or parallel blocks)

The diagram starts like this:

var myModel = GO(go.GraphLinksModel);
  myModel.nodeDataArray = [
    { key: "Inicio", category: "simple" },
    { key: "Fim", category: "simple" },
    { key: "1", Nome: "Alpha", Confiabilidade: 0, category: "block" },
  ];
  myModel.linkDataArray = [
    { from: "Inicio", to: "1" },
    { from: "1", to: "Fim" },
  ];

image

And the user can add series or parallel blocks wherever he wants.

ParallelLayout needs to define Merge and Split category, but I’m already using this property. Maybe I should adapt my model.

In your reply

myDiagram =
      $(go.Diagram, "myDiagramDiv",
        {
          isReadOnly: true,
          layout: $(go.**TreeLayout**, { layerSpacing: 20 })
        });

The layout should be ParallelLayout?

The idea for the model is that you just need to define a bunch of groups, basically one per “column”, and you define the relationships between the groups so that you get the ordering that you want.

This sounds a good idea. But I can’t understand how the TreeLayout would do the alignment like I want.

You can change the names and values of any properties that you use in your model.

For cases like what you presented, TreeLayout is good enough. Note also that the Group.layout just arranges the nodes vertically.

I made a GIF to illustrate how it’s working now before I try your suggestion. (first, I add 3 blocks in series, then 3 blocks in parallel, then 2 blocks in parallel). Hope it helps to understand what I’m doing.

I’ll try your suggestion right now. Thank you!

About this

Can you help me to adapt your template to be vertical? In my template, the problem is that the shape “LineH” is not aligned with the main shape. My idea is to the label be below the shape
This is my template:

var blockTemplate = GO(
    go.Node,
    "Vertical",
    { locationSpot: go.Spot.Center },
    GO(
      go.Shape,
      "RoundedRectangle",
      {
        portId: "",
        fromSpot: go.Spot.Right,
        toSpot: go.Spot.Left,
      },
      { width: 80, height: 40 }
    ),
    GO(go.TextBlock, { margin: 4 }, new go.Binding("text", "Nome"))
  );

You want to have the horizontal line meet up in the vertical middle of the “RoundedRectangle” Shape? Use alignment values of new go.Spot(0, 0, 0, 20) and new go.Spot(1, 0, 0, 20). Where 20 is half the height of that shape.

I fixed by adding a panel Spot wrapping my “RoundedRectangle Shape” and the 2 “LineH shapes”;

var blockTemplate = GO(
    go.Node,
    "Vertical",
    GO(
      go.Panel,
      "Spot",
      GO(
        go.Shape,
        "RoundedRectangle",
        { portId: "" },
        { width: 80, height: 40 }
      ),

      GO(go.Shape, "LineH", { alignment: go.Spot.Left, alignmentFocus: go.Spot.Right, width: 20, height: 0, }),
      GO(go.Shape, "LineH", {alignment: go.Spot.Right, alignmentFocus: go.Spot.Left, width: 20, height: 0, })
    ),
    GO(go.TextBlock, { margin: 4 }, new go.Binding("text", "Nome"))
  );

@walter Thanks for your assistance. You helped me alot!
I’d like to ask one more question.

In this case:

Do you have any suggestion to increase proportionally the width of “LineH”?

Ah, so you do need to nest parallel graphs. The quick hack that I gave you above won’t work for these kinds of cases – you really do need to use the ParallelLayout extension after all. The only real difference is that now you have to actually construct Split nodes and Merge nodes, one per Group, and links that connect each horizontal chain with both the Split node and the Merge node. Whether you construct those extra nodes and links when you build the model or after loading the model or after assigning the model to the diagram is your choice.

@walter Thanks for the help, but I’m still stuck on this. Could you give me any tips on how can I implement cases like this:
image

block1 and block5 are a parallel group inside the first group, as well as block3 and block6 are a series group.

I’m already using ParallelLayout and trying to do this dynamically (the user will give a command and then program will do this programatically).

My previous reply gave you that advice. You’ll want a Group containing blocks 1 and 5, another Group containing that Group and blocks 3, 6, and 4, and a third Group containing blocks 7, 9, 11, and 10.

Each Group must contain exactly one “Split” node and one “Merge” node. Those nodes need not be apparent to the user. And each Group need not be seen by the user either, unless you want them to be.

<!DOCTYPE html>
<html>
<head>
  <title>Automatic Grouping for ParallelLayout</title>
  <!-- Copyright 1998-2020 by Northwoods Software Corporation. -->

  <script src="go.js"></script>
  <script src="../extensions/ParallelLayout.js"></script>
  <script id="code">
  function AutoParallelLayout() {
    ParallelLayout.call(this);
  }
  go.Diagram.inherit(AutoParallelLayout, ParallelLayout);

  AutoParallelLayout.prototype.isSplit = isSplit;
  AutoParallelLayout.prototype.isMerge = isMerge;

  function isSplit(n) {
    return n instanceof go.Node && typeof n.data.key === "string" && n.data.key[0] === "S";
  }

  function isMerge(n) {
    return n instanceof go.Node && typeof n.data.key === "string" && n.data.key[0] === "M";
  }
  

  function init() {
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
        { layout: $(go.LayeredDigraphLayout, { columnSpacing: 1 }) });

    myDiagram.nodeTemplate =
      $(go.Node, "Auto",
        { width: 70, height: 35 },
        $(go.Shape,
          { fill: "white" }),
        $(go.TextBlock,
          new go.Binding("text", "key", function(k) { return "Block " + k; }))
      );

    myDiagram.nodeTemplateMap.add("Split",
      $(go.Node,
        $(go.Shape, "Circle", { width: 5, height: 5, fill: "white" })
      ));
    myDiagram.nodeTemplateMap.add("Merge",
      $(go.Node,
        $(go.Shape, "Circle", { width: 5, height: 5, fill: "white" })
      ));

    myDiagram.groupTemplate =
      $(go.Group,
        { layout: $(AutoParallelLayout) },
        $(go.Placeholder)
      );

    myDiagram.linkTemplate =
      $(go.Link,
        { routing: go.Link.Orthogonal, corner: 10 },
        $(go.Shape)
      );

    myDiagram.model = $(go.GraphLinksModel,
      {
        archetypeNodeData: {},
        linkDataArray:
        [
          { from: "S0", to: "S1" },
          { from: "S1", to: 1 },
          { from: "S1", to: 5 },
          { from: 1, to: "M1" },
          { from: 5, to: "M1" },
          { from: "S0", to: 3 },
          { from: 3, to: 6 },
          { from: "S0", to: 4 },
          { from: "M1", to: "M0" },
          { from: 6, to: "M0" },
          { from: 4, to: "M0" },
          { from: "M0", to: 2 },
          { from: 2, to: "S2" },
          { from: "S2", to: 7 },
          { from: 7, to: "M2" },
          { from: "S2", to: 9 },
          { from: 9, to: 11 },
          { from: 11, to: "M2" },
          { from: "S2", to: 10 },
          { from: 10, to: "M2" }
        ]
      });

    // organize into nested groups, before the initial layout happens
    myDiagram.findTreeRoots().each(walkGroups);
  }

  function walkGroups(node, gdata) {
    if (node instanceof go.Group) return;
    if (!node.isTopLevel) return;
    var model = node.diagram.model;
    if (isSplit(node)) {
      var grpdata = { isGroup: true };
      if (gdata) model.setGroupKeyForNodeData(grpdata, model.getKeyForNodeData(gdata));
      model.addNodeData(grpdata);
      model.setGroupKeyForNodeData(node.data, model.getKeyForNodeData(grpdata));
      model.setCategoryForNodeData(node.data, "Split");
      gdata = grpdata;
    } else {
      if (gdata) model.setGroupKeyForNodeData(node.data, model.getKeyForNodeData(gdata));
      if (isMerge(node)) model.setCategoryForNodeData(node.data, "Merge");
    }
    var cdata = isMerge(node) ? model.findNodeDataForKey(model.getGroupKeyForNodeData(gdata)) : gdata;
    node.findNodesOutOf().each(function(t) {
      walkGroups(t, cdata);
    });
  }
  </script>
</head>
<body onload="init()">
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
</body>
</html>