Reorder the "dummy" vertex

Hi,

The following diagram is using the SwimLaneLayout with the given nodeDataArray and linkDataArray below

const nodeDataArray = [
  {
    key: "A",
    isGroup: true,
  },
  {
    key: "Box1",
    group: "A",
  },
  {
    key: "Box2",
    group: "A",
  },
  {
    key: "Box3",
    group: "A",
  },
  {
    key: "Box4",
    group: "A",
  },
  {
    key: "Box5",
    group: "A",
  },
];

const linkDataArray = [
  {
    from: "Box1",
    to: "Box2",
  },
  {
    from: "Box2",
    to: "Box5",
  },
  {
    from: "Box1",
    to: "Box3",
  },
  {
    from: "Box3",
    to: "Box4",
  },
  {
    from: "Box4",
    to: "Box5",
  },
];

Screen Shot 2022-02-04 at 12.30.00 PM

I realized that there is a “dummy” vertex with node as null in the same layer as Box3.

Screen Shot 2022-02-04 at 12.30.10 PM

My first question is why the default layout calculation put Box3 above the dummy vertex, which causes an unnecessary link crossing (box1-box2 with box3-box4). If the dummy vertex is above Box3, there will be no link crossing.

My second question is if we could override the default calculation.

My third question is if we could manually adjust it by dragging the dummy vertex above Box3, as shown below.

Screen Shot 2022-02-04 at 12.30.21 PM

I looked at the event.subject (as the reshaped link) in the LinkReshaped event handler. But I have no clue how to put the dummy vertex above Box3. Any example code would be really appreciated!

I am pasting my HTML + JS code below as usual. Thanks so much for your time!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div
      id="myDiagramDiv"
      style="border: solid 1px black; width: 100%; height: 700px"
    ></div>
    <script src="../../release/go-debug.js"></script>
    <script src="../../extensions/SwimLaneLayout.js"></script>
    <script>
      let myDiagram;

      function init() {
        const $ = go.GraphObject.make;
        myDiagram = $(go.Diagram, "myDiagramDiv", {
          layout: $(SwimLaneLayout, {
            laneProperty: "group",
            layerSpacing: 20,
            commitLayers: function (layerRects, offset) {
              if (layerRects.length === 0) return;

              var horiz = true;
              var forwards = true;

              var rect = layerRects[forwards ? layerRects.length - 1 : 0];
              var totallength = horiz ? rect.right : rect.bottom;

              for (var i = 0; i < this.laneNames.length; i++) {
                var lane = this.laneNames[i];
                // assume lane names do not conflict with node names
                var group = this.diagram.findNodeForKey(lane);
                if (group === null) {
                  this.diagram.model.addNodeData({ key: lane, isGroup: true });
                  group = this.diagram.findNodeForKey(lane);
                }
                if (horiz) {
                  group.location = new go.Point(
                    -this.layerSpacing / 2,
                    this.lanePositions.get(lane) * this.columnSpacing +
                      offset.y,
                  );
                } else {
                  group.location = new go.Point(
                    this.lanePositions.get(lane) * this.columnSpacing +
                      offset.x,
                    -this.layerSpacing / 2,
                  );
                }
                var ph = group.findObject("PLACEHOLDER"); // won't be a go.Placeholder, but just a regular Shape
                if (ph === null) ph = group;
                if (horiz) {
                  ph.desiredSize = new go.Size(
                    totallength,
                    this.laneBreadths.get(lane) * this.columnSpacing,
                  );
                } else {
                  ph.desiredSize = new go.Size(
                    this.laneBreadths.get(lane) * this.columnSpacing,
                    totallength,
                  );
                }
              }
            },
          }),
        });

        myDiagram.nodeTemplate = $(
          go.Node,
          "Spot",
          new go.Binding("location", "loc", go.Point.parse),
          $(go.Shape, "RoundedRectangle", {
            fill: "white",
            width: 100,
            height: 50,
            portId: "",
            fromLinkable: true,
            toLinkable: true,
            cursor: "pointer",
          }),
          $(
            go.TextBlock, // the text label
            new go.Binding("text", "key"),
            {
              verticalAlignment: go.Spot.Center,
              textAlign: "center",
            },
          ),
        );

        myDiagram.linkTemplate = $(
          go.Link,
          {
            curve: go.Link.Bezier,
            toShortLength: 8,
            fromEndSegmentLength: 50,
            toEndSegmentLength: 50,
            reshapable: true,
          },
          $(go.Shape, { isPanelMain: true, strokeWidth: 2 }),
          $(go.Shape, { toArrow: "Standard", stroke: null }),
        );

        myDiagram.groupTemplate = $(
          go.Group,
          "Horizontal",
          {
            layerName: "Background",
            movable: false,
            copyable: false,
            locationObjectName: "PLACEHOLDER",
            layout: null,
            avoidable: false,
          },
          $(
            go.TextBlock,
            {
              font: "bold 12pt sans-serif",
              angle: 270,
            },
            new go.Binding("text", "key"),
          ),
          $(
            go.Panel,
            "Auto",
            $(go.Shape, { fill: "transparent", stroke: "orange" }),
            $(go.Shape, {
              name: "PLACEHOLDER",
              fill: null,
              stroke: null,
              strokeWidth: 0,
            }),
          ),
        );

        const nodeDataArray = [
          {
            key: "A",
            isGroup: true,
          },
          {
            key: "Box1",
            group: "A",
          },
          {
            key: "Box2",
            group: "A",
          },
          {
            key: "Box3",
            group: "A",
          },
          {
            key: "Box4",
            group: "A",
          },
          {
            key: "Box5",
            group: "A",
          },
        ];

        const linkDataArray = [
          {
            from: "Box1",
            to: "Box2",
          },
          {
            from: "Box2",
            to: "Box5",
          },
          {
            from: "Box1",
            to: "Box3",
          },
          {
            from: "Box3",
            to: "Box4",
          },
          {
            from: "Box4",
            to: "Box5",
          },
        ];

        const model = new go.GraphLinksModel();
        model.nodeDataArray = nodeDataArray;
        model.linkDataArray = linkDataArray;
        myDiagram.model = model;

        myDiagram.addDiagramListener("LinkReshaped", (event) => {
          console.log(event.subject.data);
        });
      }

      window.addEventListener("DOMContentLoaded", init);
    </script>
  </body>
</html>

The regular LayeredDigraphLayout doesn’t have this problem. It appears that the SwimLaneLayout isn’t handling those dummy vertexes specially. I’ll look into it, but it will take a while.

Thank you so much, Walter. You are always the life saver :) Looking forward to the informative solution from you. Have a great weekend!

Hi Walter, I know the dummy vertex issue is not an easy one, and there is nothing urgent from my side as well. But I am wondering if you could give me a general idea when I could receive some solution proposals/suggestions. Then I could adjust my development schedule accordingly. Thanks so much for your help in advance!

When I had some free time I spent several hours on it, but I was not able to find a satisfactory implementation for this new feature. Don’t assume that an implementation will be found quickly.

Could you please try the updated SwimLaneLayout extension that is in v2.2.3 ?
I’m hoping that it behaves better.

Thank you so much, Walter. I will try it and let you know. Have a great weekend!

Hi Walter, the new version of SwimLaneLayout works very well. Thank you so much!