Ports in Nodes

Although you have some control over where links will connect at a node (at a particular spot, along one or more sides, or at the intersection with the edge), there are times when you want to have different logical and graphical places at which links should connect. The elements at which a link may connect are called ports. There may be any number of ports in a node. By default there is just one port, the whole node, which results in the effect of having the whole node act as the port, as you have seen in all of the previous examples.

To declare that a particular element in a Node is a port, set the GraphObject.portId property to a string. Note that the port-or-link-related properties may apply to any element in the visual tree of the node, which is why they are properties of GraphObject rather than Node.

Port-like GraphObjects can only be in Nodes or Groups, not in Links or Adornments or simple Parts. So there is no reason to try to set GraphObject.portId on any object in a Link.

See samples that make use of ports in the samples index.

Single Ports

In many situations you want to consider links logically related to the node as a whole but you don't want links connecting to the whole node. In this case each node has only one port, but you do not want the whole node to act as the one port.

For example, consider how links connect to the nodes when the whole node is acting as a port in one common manner. The GraphObject.fromSpot and GraphObject.toSpot are at the middles of the sides. Because the height of the whole node includes the text label, the middle of the side is not the middle of the "icon", which in this case is a circle.

  diagram.nodeTemplate =
    $(go.Node, "Vertical",
      { fromSpot: go.Spot.Right, toSpot: go.Spot.Left },   // port properties on the node
      $(go.Shape, "Ellipse",
        { width: 30, height: 30, fill: "green" }),
      $(go.TextBlock,
        { font: "20px sans-serif" },
        new go.Binding("text", "key"))
    );

  var nodeDataArray = [
    { key: "Alpha" },
    { key: "Beta" }
  ];
  var linkDataArray = [
    { from: "Alpha", to: "Beta" }
  ];
  diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
  diagram.initialContentAlignment = go.Spot.Center;

This appearance does not look or behave quite right. Really we want links to connect to the circular Shape.

If you want a particular element to act as the port rather than the whole node, just set its GraphObject.portId to the empty string. The empty string is the name of the default port.

In this example, we set GraphObject.portId on the circular shape. Note that we move the other port-related properties, such as the port spots, to that object too.

  diagram.nodeTemplate =
    $(go.Node, "Vertical",
      $(go.Shape, "Ellipse",
        { width: 30, height: 30, fill: "green",
          portId: "",  // now the Shape is the port, not the whole Node
          fromSpot: go.Spot.Right,  // port properties go on the port!
          toSpot: go.Spot.Left
        }),
      $(go.TextBlock,
        { font: "20px sans-serif" },
        new go.Binding("text", "key"))
    );

  var nodeDataArray = [
    { key: "Alpha" },
    { key: "Beta" }
  ];
  var linkDataArray = [
    { from: "Alpha", to: "Beta" }
  ];
  diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
  diagram.initialContentAlignment = go.Spot.Center;

Notice how the links nicely connect the circular shapes by ignoring the text labels.

General Ports

It is also common to have diagrams where you want more than one port in a node. The number of ports might even vary dynamically.

In order for a link data object to distinguish which port the link should connect to, the GraphLinksModel supports two additional data properties that identify the names of the ports in the nodes at both ends of the link. GraphLinksModel.getToKeyForLinkData identifies the node to connect to; GraphLinksModel.getToPortIdForLinkData identifies the port within the node. Similarly, GraphLinksModel.getFromKeyForLinkData and GraphLinksModel.getFromPortIdForLinkData identify the node and its port.

Normally a GraphLinksModel assumes that there is no need to recognize port information on link data. If you want to support port identifiers on link data, you need to set GraphLinksModel.linkToPortIdProperty and GraphLinksModel.linkFromPortIdProperty to be the names of the link data properties. If you do not set these properties, all port identifiers are assumed to be the empty string, which is the name of the one default port for a node.

If you have set or bound GraphObject.portId on any element to be a non-empty string, you will need to use a GraphLinksModel and set GraphLinksModel.linkToPortIdProperty and GraphLinksModel.linkFromPortIdProperty to be the names of two properties on your link data, or you will need to hard code the portId names in the link template(s) (i.e. Link.fromPortId and Link.toPortId), in order for the user to be able to link with those ports.

  diagram.nodeTemplate =
    $(go.Node, "Auto",
      $(go.Shape, "Rectangle", { fill: "lightgray" }),
      $(go.Panel, "Table",
        $(go.RowColumnDefinition,
          { column: 0, alignment: go.Spot.Left}),
        $(go.RowColumnDefinition,
          { column: 2, alignment: go.Spot.Right }),
        $(go.TextBlock,  // the node title
          { column: 0, row: 0, columnSpan: 3, alignment: go.Spot.Center,
            font: "bold 10pt sans-serif", margin: new go.Margin(4, 2) },
          new go.Binding("text", "key")),
        $(go.Panel, "Horizontal",
          { column: 0, row: 1 },
          $(go.Shape,  // the "A" port
            { width: 6, height: 6, portId: "A", toSpot: go.Spot.Left }),
          $(go.TextBlock, "A")  // "A" port label
        ),
        $(go.Panel, "Horizontal",
          { column: 0, row: 2 },
          $(go.Shape,  // the "B" port
            { width: 6, height: 6, portId: "B", toSpot: go.Spot.Left }),
          $(go.TextBlock, "B")  // "B" port label
        ),
        $(go.Panel, "Horizontal",
          { column: 2, row: 1, rowSpan: 2 },
          $(go.TextBlock, "Out"),  // "Out" port label
          $(go.Shape,  // the "Out" port
            { width: 6, height: 6, portId: "Out", fromSpot: go.Spot.Right })
        )
      )
    );

  diagram.linkTemplate =
    $(go.Link,
      { routing: go.Link.Orthogonal, corner: 3 },
      $(go.Shape),
      $(go.Shape, { toArrow: "Standard" })
    );

  diagram.layout = $(go.LayeredDigraphLayout, { columnSpacing: 10 });
  diagram.initialContentAlignment = go.Spot.Center;

  diagram.model =
    $(go.GraphLinksModel,
      { linkFromPortIdProperty: "fromPort",  // required information:
        linkToPortIdProperty: "toPort",      // identifies data property names
        nodeDataArray: [
          { key: "Add1" },
          { key: "Add2" },
          { key: "Subtract1" }
        ],
        linkDataArray: [
          { from: "Add1", fromPort: "Out", to: "Subtract1", toPort: "A" },
          { from: "Add2", fromPort: "Out", to: "Subtract1", toPort: "B" }
        ] });

Setting either or both of the GraphObject.fromLinkable and GraphObject.toLinkable properties to true allows users to interactively draw new links between ports.

To draw a new link, mouse down on an "Out" port, move (drag) to nearby an input port, and then mouse-up to complete the link.

  diagram.nodeTemplate =
    $(go.Node, "Auto",
      $(go.Shape, "Rectangle", { fill: "lightgray" }),
      $(go.Panel, "Table",
        $(go.RowColumnDefinition,
          { column: 0, alignment: go.Spot.Left}),
        $(go.RowColumnDefinition,
          { column: 2, alignment: go.Spot.Right }),
        $(go.TextBlock,  // the node title
          { column: 0, row: 0, columnSpan: 3, alignment: go.Spot.Center,
            font: "bold 10pt sans-serif", margin: new go.Margin(4, 2) },
          new go.Binding("text", "key")),
        $(go.Panel, "Horizontal",
          { column: 0, row: 1 },
          $(go.Shape,  // the "A" port
            { width: 6, height: 6, portId: "A", toSpot: go.Spot.Left,
              toLinkable: true, toMaxLinks: 1 }),  // allow user-drawn links from here
          $(go.TextBlock, "A")  // "A" port label
        ),
        $(go.Panel, "Horizontal",
          { column: 0, row: 2 },
          $(go.Shape,  // the "B" port
            { width: 6, height: 6, portId: "B", toSpot: go.Spot.Left,
              toLinkable: true, toMaxLinks: 1 }),  // allow user-drawn links from here
          $(go.TextBlock, "B")  // "B" port label
        ),
        $(go.Panel, "Horizontal",
          { column: 2, row: 1, rowSpan: 2 },
          $(go.TextBlock, "Out"),  // "Out" port label
          $(go.Shape,  // the "Out" port
            { width: 6, height: 6, portId: "Out", fromSpot: go.Spot.Right,
              fromLinkable: true })  // allow user-drawn links to here
        )
      )
    );

  diagram.linkTemplate =
    $(go.Link,
      { routing: go.Link.Orthogonal, corner: 3 },
      $(go.Shape),
      $(go.Shape, { toArrow: "Standard" })
    );

  diagram.layout = $(go.LayeredDigraphLayout, { columnSpacing: 10 });
  diagram.initialContentAlignment = go.Spot.Center;

  diagram.toolManager.linkingTool.temporaryLink.routing = go.Link.Orthogonal;

  diagram.model =
    $(go.GraphLinksModel,
      { linkFromPortIdProperty: "fromPort",  // required information:
        linkToPortIdProperty: "toPort",      // identifies data property names
        nodeDataArray: [
          { key: "Add1" },
          { key: "Add2" },
          { key: "Subtract1" }
        ],
        linkDataArray: [
          // no predeclared links
        ] });

By default the user may not draw more than one link in the same direction between any pair of ports, nor may the user draw a link connecting a node with itself. Please read a general discussion of Linking Validation.

By setting GraphObject.toMaxLinks to 1, as shown in this example, the user may draw at most one link going into that port. And because GraphObject.fromLinkable is false for that port element, the user will not be able to connect any links coming out of that port.

If you want to prevent the user from connecting any more than one Link with a Node, regardless of direction, you will need to implement a LinkingBaseTool.linkValidation or a Node.linkValidation predicate. See the discussion about General Linking Validation