Node Template with attribute for several ports advanced

Some time ago Walter gave me a solution to my problem

Now I have advanced version and I need to add a port orientation(side) attribute (left, right).

How to make changes to Walter’s code so that,
depending on the orientation of the attribute,
the ports have option to be situated on the left or right side and the description appears in the middle?
go1

What’s your basic plan? How close have you come so far?

I want to achieve what is in the picture. For now I have no idea how to do it.

What would the data be for that node?

The data below:
{
key: 1, text: “Alpha”, color: “lightblue”,
ports: [
{ id: “a1”, index: 0, text: “A”, color: “yellow”, side: “right” },
{ id: “a2”, index: 1, text: “A”, color: “yellow”, side: “right” },
{ id: “a3”, index: 2, text: “A”, color: “yellow”, side: “right” },
{ id: “a4”, index: 3, text: “A”, color: “yellow”, side: “right” },
{ id: “b1”, index: 4, text: “B”, color: “lightgreen”, side: “right” },
{ id: “b2”, index: 5, text: “B”, color: “lightgreen”, side: “right” },
{ id: “c1”, index: 6, text: “C”, color: “orange”, side: “right” },
{ id: “a11”, index: 0, text: “A”, color: “yellow”, side: “left” },
{ id: “a12”, index: 1, text: “A”, color: “yellow”, side: “left” },
{ id: “a13”, index: 2, text: “A”, color: “yellow”, side: “left” },
{ id: “b11”, index: 3, text: “B”, color: “lightgreen”, side: “left” },
]
}

Well, it’s easy to filter the ports for each side:

image

<!DOCTYPE html>
<html>

<head>
  <title>Grouped Ports</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
  <meta name="description" content="A Node whose lists of ports are grouped together.">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>

  <script src="../latest/release/go.js"></script>
  <script id="code">
    const $ = go.GraphObject.make;

    const myDiagram =
      new go.Diagram("myDiagramDiv");

    myDiagram.nodeTemplate =
      $(go.Node,
        $(go.Panel, "Table",
          $(go.Shape,
            { column: 1, columnSpan: 2, fill: "white", stretch: go.Stretch.Fill },
            new go.Binding("fill", "color")),
          $(go.Panel, "Table",
            { column: 0 },
            new go.Binding("itemArray", "ports", a => getPorts(a, true)),
            {
              itemTemplate:
                $(go.Panel, "Spot",
                  new go.Binding("portId", "id"),
                  new go.Binding("row", "index"),
                  { width: 30, height: 15 },
                  $(go.Shape,
                    { fill: "white" },
                    new go.Binding("fill", "color")),
                  $(go.TextBlock,
                    { margin: new go.Margin(1, 0, 0, 0) },
                    new go.Binding("text", "id"))
                )
            }
          ),
          $(go.Panel, "Table",
            { column: 1, alignment: go.Spot.Right, margin: 2 },
            new go.Binding("itemArray", "ports", a => convertPortsToAttributes(a, true)),
            {
              itemTemplate:
                $(go.Panel, "Auto",
                  new go.Binding("row", "index"),
                  { width: 30, height: 15 },
                  new go.Binding("height", "rows", r => r * 15),
                  $(go.Shape,
                    { fill: "white", stretch: go.Stretch.Fill },
                    new go.Binding("fill", "color")),
                  $(go.TextBlock,
                    { margin: new go.Margin(1, 0, 0, 0) },
                    new go.Binding("text"))
                )
            }
          ),
          $(go.Panel, "Table",
            { column: 2, alignment: go.Spot.Right, margin: 2 },
            new go.Binding("itemArray", "ports", a => convertPortsToAttributes(a, false)),
            {
              itemTemplate:
                $(go.Panel, "Auto",
                  new go.Binding("row", "index"),
                  { width: 30, height: 15 },
                  new go.Binding("height", "rows", r => r * 15),
                  $(go.Shape,
                    { fill: "white", stretch: go.Stretch.Fill },
                    new go.Binding("fill", "color")),
                  $(go.TextBlock,
                    { margin: new go.Margin(1, 0, 0, 0) },
                    new go.Binding("text"))
                )
            }
          ),
          $(go.Panel, "Table",
            { column: 3 },
            new go.Binding("itemArray", "ports", a => getPorts(a, false)),
            {
              itemTemplate:
                $(go.Panel, "Spot",
                  new go.Binding("portId", "id"),
                  new go.Binding("row", "index"),
                  { width: 30, height: 15 },
                  $(go.Shape,
                    { fill: "white" },
                    new go.Binding("fill", "color")),
                  $(go.TextBlock,
                    { margin: new go.Margin(1, 0, 0, 0) },
                    new go.Binding("text", "id"))
                )
            }
          ),
        )
      );

    function getPorts(arr, left) {
      const a2 = [];
      arr.forEach(port => {
        if (left && port.side !== "left") return;
        if (!left && port.side === "left") return;
        a2.push(port);
      });
      return a2;
    }

    function convertPortsToAttributes(arr, left) {
      const a2 = [];
      let prevText = "";
      arr.forEach(port => {
        if (left && port.side !== "left") return;
        if (!left && port.side === "left") return;
        if (port.text !== prevText) {
          a2.push({ index: port.index, rows: 1, text: port.text, color: port.color });
          prevText = port.text;
        } else {
          a2[a2.length - 1].rows++;
        }
      });
      return a2;
    }

    myDiagram.model = new go.GraphLinksModel({
      linkFromPortIdProperty: "fpid",
      linkToPortIdProperty: "tpid",
      nodeDataArray: [
        {
          key: 1, text: "Alpha", color: "lightblue",
          ports: [
            { id: "a1", index: 0, text: "A", color: "yellow", side: "right" },
            { id: "a2", index: 1, text: "A", color: "yellow", side: "right" },
            { id: "a3", index: 2, text: "A", color: "yellow", side: "right" },
            { id: "a4", index: 3, text: "A", color: "yellow", side: "right" },
            { id: "b1", index: 4, text: "B", color: "lightgreen", side: "right" },
            { id: "b2", index: 5, text: "B", color: "lightgreen", side: "right" },
            { id: "c1", index: 6, text: "C", color: "orange", side: "right" },
            { id: "a11", index: 0, text: "A", color: "yellow", side: "left" },
            { id: "a12", index: 1, text: "A", color: "yellow", side: "left" },
            { id: "a13", index: 2, text: "A", color: "yellow", side: "left" },
            { id: "b11", index: 3, text: "B", color: "lightgreen", side: "left" },
          ]
        }
      ]
    }
    );
  </script>
</body>

</html>

I know, but that’s not my goal.
I would like to do as in my drawing

You’ll need to make the filtering of data smarter. Sorry, I don’t have time now to do that.

Maybe it will be easier to make a workaround, a function that will add fake ports in place of the missing ones, so that the ABC labels overlap on all sides.
There will be the same number of ports on each side, fake ports will be invisible.
Is it possible to do?

I will try to write a function for adding fake ports myself. Can you tell me how to make the port invisible?

You could set opacity to 0 and pickable/fromLinkable/toLinkable to false.

Thanks, it works. One last question. Do you know how to set the width of ABC labels to the entire width of the node?

I assume you mean the full width of the middle area between the ports. Yes, implement that by having the “Table” Panel hold three columns:

  1. Left ports
  2. Groups/orientations that span rows corresponding to both left and right ports of each group/orientation.
  3. Right ports

The trick is to generate all of the Table Panel elements from the given Array in a smart Binding conversion function. I believe you would not use a “TableRow” Panel as the Table Panel.itemTemplate.

Hmmm, if you want the fewer ports of each group/orientation to be vertically centered relative to the other ports of the same group/orientation, then you’ll need to partition each group of ports so that they are rendered in their own “Vertical” Panels within each row, where there will be one row for each group.

What I mean is that I have the name of the node at the top, so the width of the node changes depending on the length of the text. How to influence the width of the ABC label or how to block the node width from changing?

OK, well, I misinterpreted your question and gave a fuller answer to your previous question.

Your screenshot/sketch didn’t show it, but if you had a TextBlock above the body and its ports, you could explicitly limit its actual width by either setting its width or its maxSize’s Size.width (the Size.height can be either a positive real number of NaN to allow it to grow as much as needed when wrapping).

Or if you are using a “Vertical” Panel you could set the TextBlock.stretch property to go.Stretch.Horizontal, but not set the stretch property on the “Table” Panel holding the body and ports.