Need to achieve this diagram using dynamic ports layout

You should not need separate ports on each node. But perhaps I misunderstand your requirements. Why do you think you need a different port for each connected link when you can assign Link.fromSpot and Link.toSpot on any link?

Hi @walter Is there any way to automatically/dynamically place ports (in the left or right or top or bottom) on the nodes based on the connecting links? But Link should not come over the node to connect the other node’s port.

If you don’t use separate small ports you will not have that problem.
So I suggest that you don’t try to specify on which side a links connects.

Try to describe your requirements without specifying implementation technique.

I have 10 systems (with different # of ports).
nda = [
{key : ‘system1’, portArray : [{portId : 0}, {portId : 1}]},
{key : ‘system2’, portArray : [{portId : 0}, {portId : 1}, {portId : 2}]},
{key : ‘system3’, portArray : [{portId : 0}, {portId : 1}]},

{key : ‘system4’, portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}]},
{key : ‘system5’, portArray : [{portId : 0}, {portId : 1}]},

{key : ‘system6’, portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}, {portId : 4}]},
{key : ‘system7’, portArray : [{portId : 0}, {portId : 1}]},
{key : ‘system8’, portArray : [{portId : 0}, {portId : 1}]},
{key : ‘system9’, portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}]},
{key : ‘system10’, portArray : [{portId : 0}, {portId : 1}]}
];
lda = [{“from”:“system2”,“fromPort”:0,“to”:“system8”,“toPort”:0},{“from”:“system2”,“fromPort”:2,“to”:“system9”,“toPort”:2},{“from”:“system3”,“fromPort”:0,“to”:“system9”,“toPort”:1},{“from”:“system3”,“fromPort”:1,“to”:“system6”,“toPort”:1},{“from”:“system4”,“fromPort”:2,“to”:“system6”,“toPort”:0},{“from”:“system5”,“fromPort”:0,“to”:“system10”,“toPort”:1},{“from”:“system5”,“fromPort”:1,“to”:“system9”,“toPort”:3},{“from”:“system6”,“fromPort”:0,“to”:“system4”,“toPort”:0},{“from”:“system6”,“fromPort”:2,“to”:“system1”,“toPort”:0},{“from”:“system6”,“fromPort”:3,“to”:“system10”,“toPort”:1},{“from”:“system6”,“fromPort”:4,“to”:“system4”,“toPort”:0},{“from”:“system7”,“fromPort”:0,“to”:“system2”,“toPort”:1},{“from”:“system7”,“fromPort”:1,“to”:“system4”,“toPort”:1},{“from”:“system8”,“fromPort”:0,“to”:“system1”,“toPort”:1},{“from”:“system9”,“fromPort”:0,“to”:“system8”,“toPort”:0},{“from”:“system9”,“fromPort”:2,“to”:“system1”,“toPort”:0},{“from”:“system10”,“fromPort”:1,“to”:“system9”,“toPort”:2}];

I want all these nodes and ports has to be set in a layout looking like below (Just an example). Nodes and links should not collide.
image

image
It should look like this with ports.

Please help on this @walter

I still would like to understand why you believe you need “ports”.

Is the requirement that two or more links that connect with a node using the same port connect at the same point on the side of the node?

But you don’t care where the port is on its node?

In addition you don’t want links crossing over nodes.

Is the requirement that two or more links that connect with a node using the same port connect at the same point on the side of the node?

But you don’t care where the port is on its node?
In addition you don’t want links crossing over nodes.

EXACTLY!!

OK, that is an unusual requirement but should be achievable. I can work on it later today.

By the way, your bigger screenshot with the orange links does not convey your requirement because there are no cases where multiple links connect with the same node at the same point on that node.

Thanks.

sorry for that @walter

@walter Also nodes can have more number of ports. Based on the # of ports, the nodes size should increase or else please suggest me a good option in that case

Surely there’s some content that you want to show within the node, yes?
Wouldn’t you want the node size to be influenced by how big the content is?
If not, that’s OK too.

No need to influence the nodes by content inside it

OK, then how should the node size be determined?
Presumably you have some notion of how given the number of ports.
Perhaps also influenced by how many ports are on a side?

The node size is determined by number ports it have. Maximum 10 ports it can have.
It doesn’t mean it should only have these many number of ports on a side.
Ultimately, the diagram should look good and connected well

I took the liberty of putting a label showing the portId at each end of each link:

Without calling mergeLinksAtPorts at the end of each layout:

The result after callling mergeLinksAtPorts:

The complete source code follows. Feel free to adapt the code for your own purposes. I didn’t bother changing the size of nodes based on any criteria, but you can do that if you want.

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const $ = go.GraphObject.make;

const myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      layout: new go.ForceDirectedLayout({ randomNumberGenerator: null, maxIterations: 400 }),
      "LayoutCompleted": mergeLinksAtPorts,
      "undoManager.isEnabled": true,
      "ModelChanged": e => {     // just for demonstration purposes,
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      }
    });

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    { width: 120, height: 60, locationSpot: go.Spot.Center },
    new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
    $(go.Shape, "RoundedRectangle",
      {
        fill: "white", stroke: "gray", strokeWidth: 3,
        portId: "",
        fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides
      }),
    $(go.TextBlock,
      new go.Binding("text"))
  );

myDiagram.linkTemplate =
  $(go.Link,
    { routing: go.Link.AvoidsNodes, corner: 10 },
    //new go.Binding("routing", "straight", s => s ? go.Link.Normal : go.Link.AvoidsNodes),
    $(go.Shape, { stroke: "orange", strokeWidth: 3 }),
    $(go.TextBlock, { segmentIndex: 0, segmentOffset: new go.Point(-10, 0) },
      new go.Binding("text", "fromPort")),
    $(go.TextBlock, { segmentIndex: -1, segmentOffset: new go.Point(10, 0) },
      new go.Binding("text", "toPort"))
  );

function mergeLinksAtPorts(e) {
  const map = new go.Map();
  e.diagram.nodes.each(n => {
    map.clear();
    n.findLinksConnected().each(l => {
      // can we assume no reflexive links?
      if (l.fromNode === n) {
        const fid = l.data.fromPort;
        let arr = map.get(fid);
        if (!arr) map.set(fid, arr = []);
        arr.push(l);
      } else if (l.toNode === n) {
        const tid = l.data.toPort;
        let arr = map.get(tid);
        if (!arr) map.set(tid, arr = []);
        arr.push(l);
      }
    });
    map.each(kvp => {
      const id = kvp.key;
      const arr = kvp.value;
      if (!arr || arr.length <= 1) return;
      const first = arr[0];
      let spot;
      if (first.fromNode === n) {
        spot = computeSpot(n, first.getPoint(0));
      } else if (first.toNode === n) {
        spot = computeSpot(n, first.getPoint(first.pointsCount-1));
      }
      arr.forEach(link => {
        if (link.fromNode === n) {
          link.fromSpot = spot;
        } else if (link.toNode === n) {
          link.toSpot = spot;
        }
      });
    })
  });
}

function computeSpot(node, pt) {
  const b = node.getDocumentBounds();
  return new go.Spot((pt.x - b.x) / (b.width || 1), (pt.y - b.y) / (b.height || 1));
}

const nda = [
  {key: "system1", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system2", portArray : [{portId : 0}, {portId : 1}, {portId : 2}]},
  {key: "system3", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system4", portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}]},
  {key: "system5", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system6", portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}, {portId : 4}]},
  {key: "system7", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system8", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system9", portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}]},
  {key: "system10", portArray : [{portId : 0}, {portId : 1}]}
];
const lda = [
  {"from":"system2","fromPort":0,"to":"system8","toPort":0},
  {"from":"system2","fromPort":2,"to":"system9","toPort":2},
  {"from":"system3","fromPort":0,"to":"system9","toPort":1},
  {"from":"system3","fromPort":1,"to":"system6","toPort":1},
  {"from":"system4","fromPort":2,"to":"system6","toPort":0},
  {"from":"system5","fromPort":0,"to":"system10","toPort":1},
  {"from":"system5","fromPort":1,"to":"system9","toPort":3},
  {"from":"system6","fromPort":0,"to":"system4","toPort":0},
  {"from":"system6","fromPort":2,"to":"system1","toPort":0},
  {"from":"system6","fromPort":3,"to":"system10","toPort":1},
  {"from":"system6","fromPort":4,"to":"system4","toPort":0},
  {"from":"system7","fromPort":0,"to":"system2","toPort":1},
  {"from":"system7","fromPort":1,"to":"system4","toPort":1},
  {"from":"system8","fromPort":0,"to":"system1","toPort":1},
  {"from":"system9","fromPort":0,"to":"system8","toPort":0},
  {"from":"system9","fromPort":2,"to":"system1","toPort":0},
  {"from":"system10","fromPort":1,"to":"system9","toPort":2}
];
myDiagram.model = new go.GraphLinksModel(nda, lda);
  </script>
</body>
</html>

@walter While dragging the nodes, the ports are colliding each other. Is there way to align it automatically without colliding on top of each other?
Initial View


After dragging the nodes

@walter Here you are having ports on the link.
But what if the node should show just port without any links or there are ports that is not connected to any nodes (Node requirement) ?

You keep adding new requirements. From your original description it wouldn’t make sense to have particular positions for ports unless there was at least one connected link determining its position based on the relative location of the node that the link connects with.

Apology for that @walter . I thought there would be requirement for that. No needed. Thanks for the quick response.
BTW,
While dragging the nodes, the ports are colliding each other. Is there way to align it automatically without colliding on top of each other?

Here’s one way to allow interactive dragging of nodes.

If you wanted to show the final configuration during the drag, that’s possible too but would be slower because the natural link routing and the merging step would need to be done on each mouse move.

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const $ = go.GraphObject.make;

const myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      layout: new go.ForceDirectedLayout({ randomNumberGenerator: null, maxIterations: 400 }),
      "LayoutCompleted": e => mergeLinksAtPorts(e.diagram.nodes),
      "draggingTool.doActivate": function() {
        go.DraggingTool.prototype.doActivate.call(this);
        this.draggedParts.iteratorKeys.each(n => {
          if (!(n instanceof go.Node)) return;
          n.findLinksConnected().each(l => {
            l.fromSpot = go.Spot.Default;
            l.toSpot = go.Spot.Default;
          });
        });
      },
      "SelectionMoved": e => mergeLinksAtPorts(e.diagram.nodes),
      "undoManager.isEnabled": true,
      "ModelChanged": e => {     // just for demonstration purposes,
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      }
    });

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    { width: 120, height: 60, locationSpot: go.Spot.Center },
    new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
    $(go.Shape, "RoundedRectangle",
      {
        fill: "white", stroke: "gray", strokeWidth: 3,
        portId: "",
        fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides
      }),
    $(go.TextBlock,
      new go.Binding("text"))
  );

myDiagram.linkTemplate =
  $(go.Link,
    { routing: go.Link.AvoidsNodes, corner: 10 },
    $(go.Shape, { stroke: "orange", strokeWidth: 3 }),
    $(go.TextBlock, { segmentIndex: 0, segmentOffset: new go.Point(-10, 0) },
      new go.Binding("text", "fromPort")),
    $(go.TextBlock, { segmentIndex: -1, segmentOffset: new go.Point(10, 0) },
      new go.Binding("text", "toPort"))
  );

function mergeLinksAtPorts(nodes) {
  const map = new go.Map();
  nodes.each(n => {
    if (!(n instanceof go.Node)) return;
    map.clear();
    n.findLinksConnected().each(l => {
      // can we assume no reflexive links?
      if (l.fromNode === n) {
        const fid = l.data.fromPort;
        let arr = map.get(fid);
        if (!arr) map.set(fid, arr = []);
        arr.push(l);
      } else if (l.toNode === n) {
        const tid = l.data.toPort;
        let arr = map.get(tid);
        if (!arr) map.set(tid, arr = []);
        arr.push(l);
      }
    });
    map.each(kvp => {
      const id = kvp.key;
      const arr = kvp.value;
      if (!arr || arr.length <= 1) return;
      const first = arr[0];
      let spot;
      if (first.fromNode === n) {
        spot = computeSpot(n, first.getPoint(0));
      } else if (first.toNode === n) {
        spot = computeSpot(n, first.getPoint(first.pointsCount-1));
      }
      arr.forEach(link => {
        if (link.fromNode === n) {
          link.fromSpot = spot;
        } else if (link.toNode === n) {
          link.toSpot = spot;
        }
      });
    })
  });
}

function computeSpot(node, pt) {
  const b = node.getDocumentBounds();
  return new go.Spot(
      Math.max(0, Math.min((pt.x - b.x) / (b.width || 1), 1)),
      Math.max(0, Math.min((pt.y - b.y) / (b.height || 1), 1))
    );
}

const nda = [
  {key: "system1", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system2", portArray : [{portId : 0}, {portId : 1}, {portId : 2}]},
  {key: "system3", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system4", portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}]},
  {key: "system5", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system6", portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}, {portId : 4}]},
  {key: "system7", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system8", portArray : [{portId : 0}, {portId : 1}]},
  {key: "system9", portArray : [{portId : 0}, {portId : 1}, {portId : 2}, {portId : 3}]},
  {key: "system10", portArray : [{portId : 0}, {portId : 1}]}
];
const lda = [
  {"from":"system2","fromPort":0,"to":"system8","toPort":0},
  {"from":"system2","fromPort":2,"to":"system9","toPort":2},
  {"from":"system3","fromPort":0,"to":"system9","toPort":1},
  {"from":"system3","fromPort":1,"to":"system6","toPort":1},
  {"from":"system4","fromPort":2,"to":"system6","toPort":0},
  {"from":"system5","fromPort":0,"to":"system10","toPort":1},
  {"from":"system5","fromPort":1,"to":"system9","toPort":3},
  {"from":"system6","fromPort":0,"to":"system4","toPort":0},
  {"from":"system6","fromPort":2,"to":"system1","toPort":0},
  {"from":"system6","fromPort":3,"to":"system10","toPort":1},
  {"from":"system6","fromPort":4,"to":"system4","toPort":0},
  {"from":"system7","fromPort":0,"to":"system2","toPort":1},
  {"from":"system7","fromPort":1,"to":"system4","toPort":1},
  {"from":"system8","fromPort":0,"to":"system1","toPort":1},
  {"from":"system9","fromPort":0,"to":"system8","toPort":0},
  {"from":"system9","fromPort":2,"to":"system1","toPort":0},
  {"from":"system10","fromPort":1,"to":"system9","toPort":2}
];
myDiagram.model = new go.GraphLinksModel(nda, lda);
  </script>
</body>
</html>

2 posts were split to a new topic: Routing of many links between nodes that are close to each other