Custom properties for input controls using DataInspector

Hi,

My requirement is to change the no.of inputs of logical circuit controls using data inspector.
When a AND/OR/XOR etc., control is drag and dropped onto a diagram block and selected, using data inspector i am able to change few properties of the controls.

I want to change the no.of inputs of the control.

For example, if i select AND gate, by default there will be 2 inputs. I should be able to provide user with an option in data inspector and if user types 5 then AND gates should display 5 inputs.

Could someone help me?
Thanks in advance.

1 Like

I’m assuming that your node template supports a variable number of ports by having a Binding of Panel.itemArray and a Panel.itemTemplate that is the port that you want. For more information, read https://gojs.net/latest/intro/itemarrays.html and see https://gojs.net/latest/samples/dynamicPorts.html or https://gojs.net/latest/samples/pipes.html.

In the property descriptor for your property you can provide a propertyModified function that adds or removes objects from the node property that is that Array that describes each port. Call Model.addArrayItem or Model.removeArrayItem in order to modify the Array.

Thanks Walter for the suggestion.
I am very much new to GoJs.

Any example for the above issue would be very helpful to me.

Any examples please.

I have modified the Logic Circuit sample to include a DataInspector and extended the “and” node template to include a variable number of input ports and a “numInputs” property that can be edited in the inspector.

Here is the new “and” template. Compare it with the original “and” template of the Logic Circuit sample:

      var andTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Panel, "Table",
            $(go.Shape, "AndGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
            $(go.Panel, "Table",
              new go.Binding("itemArray", "inputPorts"),
              {
                margin: new go.Margin(4, 0),
                alignment: go.Spot.Left,
                stretch: go.GraphObject.Vertical,
                rowSizing: go.RowColumnDefinition.ProportionalExtra,
                itemTemplate:
                  $(go.Panel, "TableRow",
                    $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                  )
              }
            )
          ),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

Note how there is a Table Panel holding all of the input ports. It is sharing a single cell of another Table Panel with the “AndGate” Shape. The Table Panel holding the input ports is stretched vertically to match the height of the “AndGate” Shape, minus some margin. Its Panel.rowSizing is ProportionalExtra so that the item panels (in this case the port elements) are spread out vertically.

Each port is defined by the Panel.itemTemplate. In this case each port is a Shape that occupies a “TableRow”. It has a Binding on GraphObject.portId, so that we can make sure each port has a different identifier.

The Palette is initialized slightly differently for the “and” node:

      palette.model.nodeDataArray = [
        . . .,
        { category: "and", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
        . . . ];

Note how the inputPorts property holds the Array to which the Table Panel is data bound. Each item in the Array describes a port. You could add other properties there is you want.

There is also a numInputs property that the user can edit to control the number of ports.

The inspector is set up by:

      // support editing the properties of the selected person in HTML
      if (window.Inspector) {
        // Inspector helper function
        function updateNumInputs(name, value) {
          if (name !== "numInputs") return;
          var data = myInspector.inspectedObject.data;
          var inputs = data.inputPorts;
          if (value > inputs.length) {
            while (value > inputs.length) {
              myDiagram.model.addArrayItem(inputs, { portId: "in" + (inputs.length+1).toString() });
            }
          } else if (value < inputs.length) {
            while (value < inputs.length) {
              myDiagram.model.removeArrayItem(inputs, inputs.length - 1);
            }
          }
        }

        myInspector = new Inspector("myInspector", myDiagram,
        {
          properties: {
            "key": { readOnly: true, show: Inspector.showIfPresent },
            "text": { },
            "fill": { type: "color", defaultValue: "lightgray", show: Inspector.showIfNode },
            "stroke": { type: "color", defaultValue: "black", show: Inspector.showIfLink },
            "numInputs": { type: "number", defaultValue: 2, show: Inspector.showIfPresent },
            "inputPorts": { show: false }
          },
          propertyModified: updateNumInputs
        });
      }

Note how the inputPorts property is never shown, so the user never sees that Array.

But the numInputs property is shown and is editable. The propertyModified function is called after each update. In this case it checks for the numInputs property having been changed, and it updates the inputPorts Array appropriately.

Here is the whole file:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Logic Circuit</title>
  <meta name="description" content="A simple logic circuit editor." />
  <!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
  <meta charset="UTF-8">
  <script src="go.js"></script>
  <link rel="stylesheet" href="../extensions/dataInspector.css" />
  <script src="../extensions/dataInspector.js"></script>
  <script id="code">
    function init() {
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      myDiagram =
        $(go.Diagram, "myDiagramDiv",  // create a new Diagram in the HTML DIV element "myDiagramDiv"
          {
            initialContentAlignment: go.Spot.Center,
            allowDrop: true,  // Nodes from the Palette can be dropped into the Diagram
            "draggingTool.isGridSnapEnabled": true,  // dragged nodes will snap to a grid of 10x10 cells
            "undoManager.isEnabled": true
          });

      // when the document is modified, add a "*" to the title and enable the "Save" button
      myDiagram.addDiagramListener("Modified", function(e) {
        var button = document.getElementById("saveModel");
        if (button) button.disabled = !myDiagram.isModified;
        var idx = document.title.indexOf("*");
        if (myDiagram.isModified) {
          if (idx < 0) document.title += "*";
        } else {
          if (idx >= 0) document.title = document.title.substr(0, idx);
        }
      });

      // creates relinkable Links that will avoid crossing Nodes when possible and will jump over other Links in their paths
      myDiagram.linkTemplate =
        $(go.Link,
          {
            routing: go.Link.AvoidsNodes,
            curve: go.Link.JumpOver,
            corner: 3,
            relinkableFrom: true, relinkableTo: true,
            selectionAdorned: false, // Links are not adorned when selected so that their color remains visible.
            shadowOffset: new go.Point(0, 0), shadowBlur: 5, shadowColor: "blue",
            toolTip:
              $(go.Adornment, "Auto",
                $(go.Shape, { fill: "lightyellow" }),
                $(go.TextBlock, { margin: 4 },
                  new go.Binding("text"))
              )
          },
          new go.Binding("isShadowed", "isSelected").ofObject(),
          $(go.Shape,
            { name: "SHAPE", strokeWidth: 2 },
            new go.Binding("stroke").makeTwoWay())
        );

      // node template helpers
      // define some common property settings

      function nodeStyle() {
        return [new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
                new go.Binding("isShadowed", "isSelected").ofObject(),
                {
                  selectionAdorned: false,
                  shadowOffset: new go.Point(0, 0),
                  shadowBlur: 15,
                  shadowColor: "blue",
                  toolTip:
                    $(go.Adornment, "Auto",
                      $(go.Shape, { fill: "lightyellow" }),
                      $(go.TextBlock, { margin: 4 },
                        new go.Binding("text", "", function(d) {
                          var msg = d.key+ ": " + d.category;
                          if (d.text) msg += "\n" + d.text;
                          return msg;
                        }))
                    )
                }];
      }

      function shapeStyle() {
        return [
          {
            name: "NODESHAPE",
            fill: "lightgray",
            stroke: "darkslategray",
            desiredSize: new go.Size(40, 40),
            strokeWidth: 2
          },
          new go.Binding("fill").makeTwoWay()
        ];
      }

      function portStyle(input) {
        return {
          desiredSize: new go.Size(6, 6),
          fill: "black",
          fromSpot: go.Spot.Right,
          fromLinkable: !input,
          toSpot: go.Spot.Left,
          toLinkable: input,
          toMaxLinks: 1,
          cursor: "pointer",
          toolTip:
            $(go.Adornment, "Auto",
              $(go.Shape, { fill: "lightyellow" }),
              $(go.TextBlock, { margin: 4 },
                new go.Binding("text", "adornedObject", function(obj) {
                  return obj.portId + " on " + obj.part.data.key;
                }).ofObject())
            )
        };
      }

      // define templates for each type of node
      var inputTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "Circle", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(false),  // the only port
            { portId: "", alignment: new go.Spot(1, 0.5) })
        );

      var outputTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "Rectangle", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(true),  // the only port
            { portId: "", alignment: new go.Spot(0, 0.5) })
        );

      var andTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Panel, "Table",
            $(go.Shape, "AndGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
            $(go.Panel, "Table",
              new go.Binding("itemArray", "inputPorts"),
              {
                margin: new go.Margin(4, 0),
                alignment: go.Spot.Left,
                stretch: go.GraphObject.Vertical,
                rowSizing: go.RowColumnDefinition.ProportionalExtra,
                itemTemplate:
                  $(go.Panel, "TableRow",
                    $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                  )
              }
            )
          ),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

      var orTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "OrGate", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in1", alignment: new go.Spot(0.16, 0.3) }),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in2", alignment: new go.Spot(0.16, 0.7) }),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

      var xorTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "XorGate", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in1", alignment: new go.Spot(0.26, 0.3) }),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in2", alignment: new go.Spot(0.26, 0.7) }),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

      var norTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "NorGate", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in1", alignment: new go.Spot(0.16, 0.3) }),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in2", alignment: new go.Spot(0.16, 0.7) }),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

      var xnorTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "XnorGate", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in1", alignment: new go.Spot(0.26, 0.3) }),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in2", alignment: new go.Spot(0.26, 0.7) }),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

      var nandTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "NandGate", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in1", alignment: new go.Spot(0, 0.3) }),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in2", alignment: new go.Spot(0, 0.7) }),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

      var notTemplate =
        $(go.Node, "Spot", nodeStyle(),
          $(go.Shape, "Inverter", shapeStyle()),
          $(go.Shape, "Rectangle", portStyle(true),
            { portId: "in", alignment: new go.Spot(0, 0.5) }),
          $(go.Shape, "Rectangle", portStyle(false),
            { portId: "out", alignment: new go.Spot(1, 0.5) })
        );

      // add the templates created above to myDiagram and palette
      myDiagram.nodeTemplateMap.add("input", inputTemplate);
      myDiagram.nodeTemplateMap.add("output", outputTemplate);
      myDiagram.nodeTemplateMap.add("and", andTemplate);
      myDiagram.nodeTemplateMap.add("or", orTemplate);
      myDiagram.nodeTemplateMap.add("xor", xorTemplate);
      myDiagram.nodeTemplateMap.add("not", notTemplate);
      myDiagram.nodeTemplateMap.add("nand", nandTemplate);
      myDiagram.nodeTemplateMap.add("nor", norTemplate);
      myDiagram.nodeTemplateMap.add("xnor", xnorTemplate);

      // share the template map with the Palette
      var palette = new go.Palette("palette");  // create a new Palette in the HTML DIV element "palette"
      palette.nodeTemplateMap = myDiagram.nodeTemplateMap;

      palette.model.nodeDataArray = [
        { category: "input" },
        { category: "output" },
        { category: "and", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
        { category: "or" },
        { category: "xor" },
        { category: "not" },
        { category: "nand" },
        { category: "nor" },
        { category: "xnor" }
      ];

      // support editing the properties of the selected person in HTML
      if (window.Inspector) {
        // Inspector helper function
        function updateNumInputs(name, value) {
          if (name !== "numInputs") return;
          var data = myInspector.inspectedObject.data;
          var inputs = data.inputPorts;
          if (value > inputs.length) {
            while (value > inputs.length) {
              myDiagram.model.addArrayItem(inputs, { portId: "in" + (inputs.length+1).toString() });
            }
          } else if (value < inputs.length) {
            while (value < inputs.length) {
              myDiagram.model.removeArrayItem(inputs, inputs.length - 1);
            }
          }
        }

        myInspector = new Inspector("myInspector", myDiagram,
        {
          properties: {
            "key": { readOnly: true, show: Inspector.showIfPresent },
            "text": { },
            "fill": { type: "color", defaultValue: "lightgray", show: Inspector.showIfNode },
            "stroke": { type: "color", defaultValue: "black", show: Inspector.showIfLink },
            "numInputs": { type: "number", defaultValue: 2, show: Inspector.showIfPresent },
            "inputPorts": { show: false }
          },
          propertyModified: updateNumInputs
        });
      }

      // load the initial diagram
      load();
    }

    // save a model to and load a model from JSON text, displayed below the Diagram
    function save() {
      document.getElementById("mySavedModel").value = myDiagram.model.toJson();
      myDiagram.isModified = false;
    }
    function load() {
      myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
    }
  </script>
</head>
<body onload="init()">
<div id="sample">
  <div style="width:100%; white-space:nowrap;">
    <span style="display: inline-block; vertical-align: top; width:100px">
      <div id="palette" style="border: solid 1px black; height: 500px"></div>
    </span>
    <span style="display: inline-block; vertical-align: top; width:60%">
      <div id="myDiagramDiv" style="border: solid 1px black; height: 500px"></div>
    </span>
    <span style="display: inline-block; vertical-align: top; width:150px">
      <div id="myInspector">
      </div>
    </span>
  </div>
  <div>
    <div>
      <button id="saveModel" onclick="save()">Save</button>
      <button onclick="load()">Load</button>
      Diagram Model saved in JSON format:
    </div>
    <textarea id="mySavedModel" style="width:100%;height:200px">
{ "class": "go.GraphLinksModel",
  "linkFromPortIdProperty": "fromPort",
  "linkToPortIdProperty": "toPort",
  "copiesArrays": true,
  "copiesArrayObjects": true,
  "nodeDataArray": [
{"category":"input", "key":"input1", "loc":"-150 -80" },
{"category":"or", "key":"or1", "loc":"-70 0" },
{"category":"not", "key":"not1", "loc":"10 0" },
{"category":"xor", "key":"xor1", "loc":"100 0" },
{"category":"or", "key":"or2", "loc":"200 0" },
{"category":"output", "key":"output1", "loc":"200 -100", "text":"this only has an input port" }
 ],
  "linkDataArray": [
{"from":"input1", "fromPort":"out", "to":"or1", "toPort":"in1", "text":"coming from the only input node"},
{"from":"or1", "fromPort":"out", "to":"not1", "toPort":"in"},
{"from":"not1", "fromPort":"out", "to":"or1", "toPort":"in2"},
{"from":"not1", "fromPort":"out", "to":"xor1", "toPort":"in1"},
{"from":"xor1", "fromPort":"out", "to":"or2", "toPort":"in1"},
{"from":"or2", "fromPort":"out", "to":"xor1", "toPort":"in2"},
{"from":"xor1", "fromPort":"out", "to":"output1", "toPort":""}
 ]}
    </textarea>
  </div>
</div>
</body>
</html>
1 Like

I am using dynamic ports for logic gates, based on the input specified in the data inspector of respective node i am able to update the ports now. Here is my code.

I used html code in above reply.

The problem is when i am dragging 2 input ports and 1 AND gate on to the diagram and click on Save to generate Json i am seeing below output.

{ “class”: “go.GraphLinksModel”,
“nodeDataArray”: [
{“category”:“input”, “key”:-1, “loc”:"-280 -80"},
{“category”:“input”, “key”:-2, “loc”:"-270 40"},
{“category”:“and”, “numInputs”:2, “inputPorts”:[ {“portId”:“in1”},{“portId”:“in2”} ], “key”:-3, “loc”:"-80 -40"}
],
“linkDataArray”: [
{“from”:-1, “to”:-3},
{“from”:-2, “to”:-3}
]}
The linkDataArray here is only giving conenctions of nodes but not ports.

Also in UI i am able to connect from input node 1 to port 1 of AND GATE. When i am trying to connect input node 2 to port 2 of AND GATE i am not able to connect. I am able to do vice versa. I am able to connect port 2 of AND GATE to input node. But the connection is not to port 2. The line is showing in between port 1 and port 2 of AND GATE.

How can i get the linked array updated with ports and their connections along with nodes.

Please help me in fixing this.

You are not setting those four critical properties on GraphLinksModel that my code set, as you can see in the saved model, above in the textarea.

Thanks walter, I got that now after adding missed properties.

I have few other questions.

  1. Since i am giving user an option to increase no.of inputs for logical gates, how to increase the size of the gate with respect to no.of input ports? I tried the same as in dynamic ports for size increase but could not.

  2. Along with logical gates i also want to add a new component in the palette so that i can use it by defining no.of input ports and output ports using data inspector. This should also increase in size with respect to no.of ports.

I tried using dynamic ports in gojs but looks like i am missing something where i am not able to use both the logical gates and custom component(same component as in dynamic ports section) at the same time.

I initially designed a circuit using logic gates and then i drag and dropped a custom component as below but i am not able to add ports to that component. Neither i am getting error nor the port is getting added/displayed.

The Json is as below.

{ “class”: “go.GraphLinksModel”,
“copiesArrays”: true,
“copiesArrayObjects”: true,
“linkFromPortIdProperty”: “fromPort”,
“linkToPortIdProperty”: “toPort”,
“nodeDataArray”: [
{“category”:“input”, “key”:-1, “loc”:"-240 -160"},
{“category”:“input”, “key”:-2, “loc”:"-240 -50"},
{“category”:“and”, “numInputs”:3, “inputPorts”:[ {“portId”:“in1”},{“portId”:“in2”},{“portId”:“in3”} ], “key”:-3, “loc”:"-80 -110", “text”:"", “fill”:“lightgray”},
{“category”:“input”, “key”:-4, “loc”:"-240 20"},
{“category”:“or”, “numInputs”:2, “inputPorts”:[ {“portId”:“in1”},{“portId”:“in2”} ], “key”:-5, “loc”:“70 -110”},
{“category”:“input”, “key”:-6, “loc”:"-20 -40"},
{“category”:“output”, “key”:-7, “loc”:“240 -110”},
{“category”:“common”, “numInputs”:2, “inputPorts”:[ {“portId”:“in1”},{“portId”:“in2”} ], “key”:-10, “loc”:"-120 120"}
],
“linkDataArray”: [
{“from”:-1, “to”:-3, “fromPort”:"", “toPort”:“in1”, “points”:[-194.5,-139,-184.5,-139,-137.25,-139,-137.25,-100.33333333333333,-90,-100.33333333333333,-80,-100.33333333333333]},
{“from”:-2, “to”:-3, “fromPort”:"", “toPort”:“in2”, “points”:[-194.5,-29,-184.5,-29,-149,-29,-149,-89,-90,-89,-80,-89]},
{“from”:-4, “to”:-3, “fromPort”:"", “toPort”:“in3”, “points”:[-194.5,41,-184.5,41,-137.25,41,-137.25,-77.66666666666666,-90,-77.66666666666666,-80,-77.66666666666666]},
{“from”:-3, “to”:-5, “fromPort”:“out”, “toPort”:“in1”, “points”:[-31.5,-89,-21.5,-89,19.25,-89,19.25,-97.5,60,-97.5,70,-97.5]},
{“from”:-6, “to”:-5, “fromPort”:"", “toPort”:“in2”, “points”:[25.5,-19,35.5,-19,47.75,-19,47.75,-80.5,60,-80.5,70,-80.5]},
{“from”:-5, “to”:-7, “fromPort”:“out”, “toPort”:"", “points”:[118.5,-89,128.5,-89,179.25,-89,179.25,-89,230,-89,240,-89]}
]}
Any example would really help me.

I guess you could start with the node template used in the Dynamic Ports sample, strip out all of the code involving the top and bottom ports, and add a binding on the middle Shape’s figure.

This is how i added the code.

function init() { var $ = go.GraphObject.make; // for conciseness in defining templates
        myDiagram =
            $(go.Diagram, "myDiagramDiv",  // create a new Diagram in the HTML DIV element "myDiagramDiv"
                {
                    initialContentAlignment: go.Spot.Center,
                    allowDrop: true,  // Nodes from the Palette can be dropped into the Diagram
                    "draggingTool.isGridSnapEnabled": true,  // dragged nodes will snap to a grid of 10x10 cells
                    "undoManager.isEnabled": true
                });

        // when the document is modified, add a "*" to the title and enable the "Save" button
        myDiagram.addDiagramListener("Modified", function (e) {
            var button = document.getElementById("saveModel");
            if (button) button.disabled = !myDiagram.isModified;
            var idx = document.title.indexOf("*");
            if (myDiagram.isModified) {
                if (idx < 0) document.title += "*";
            } else {
                if (idx >= 0) document.title = document.title.substr(0, idx);
            }
        });

        // creates relinkable Links that will avoid crossing Nodes when possible and will jump over other Links in their paths
        myDiagram.linkTemplate =
            $(go.Link,  // defined below
                {
                    routing: go.Link.AvoidsNodes,
                    corner: 4,
                    curve: go.Link.JumpGap,
                    reshapable: true,
                    resegmentable: true,
                    relinkableFrom: true,
                    relinkableTo: true
                },
                new go.Binding("points").makeTwoWay(),
                $(go.Shape, { stroke: "#2F4F4F", strokeWidth: 2 })
            );

        // node template helpers
        // define some common property settings

        function nodeStyle() {
            return [new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
            new go.Binding("isShadowed", "isSelected").ofObject(),
            {
                selectionAdorned: false,
                shadowOffset: new go.Point(0, 0),
                shadowBlur: 15,
                shadowColor: "blue",
                toolTip:
                $(go.Adornment, "Auto",
                    $(go.Shape, { fill: "lightyellow" }),
                    $(go.TextBlock, { margin: 4 },
                        new go.Binding("text", "", function (d) {
                            var msg = d.key + ": " + d.category;
                            if (d.text) msg += "\n" + d.text;
                            return msg;
                        }))
                )
            }];
        }

        function shapeStyle() {
            return [
                {
                    name: "NODESHAPE",
                    fill: "lightgray",
                    stroke: "darkslategray",
                    desiredSize: new go.Size(40, 40),
                    strokeWidth: 2
                },
                new go.Binding("fill").makeTwoWay()
            ];
        }

        function portStyle(input) {
            return {
                desiredSize: new go.Size(6, 6),
                fill: "black",
                fromSpot: go.Spot.Right,
                fromLinkable: !input,
                toSpot: go.Spot.Left,
                toLinkable: input,
                toMaxLinks: 1,
                cursor: "pointer",
                toolTip:
                $(go.Adornment, "Auto",
                    $(go.Shape, { fill: "lightyellow" }),
                    $(go.TextBlock, { margin: 4 },
                        new go.Binding("text", "adornedObject", function (obj) {
                            return obj.portId + " on " + obj.part.data.key;
                        }).ofObject())
                )
            };
        }

        // define templates for each type of node
        var inputTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Shape, "Circle", shapeStyle()),
                $(go.Shape, "Rectangle", portStyle(false),  // the only port
                    { portId: "", alignment: new go.Spot(1, 0.5) })
            );

        var outputTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Shape, "Rectangle", shapeStyle()),
                $(go.Shape, "Rectangle", portStyle(true),  // the only port
                    { portId: "", alignment: new go.Spot(0, 0.5) })
            );

        // To simplify this code we define a function for creating a context menu button:
        function makeButton(text, action, visiblePredicate) {
            return $("ContextMenuButton",
                $(go.TextBlock, text),
                { click: action },
                // don't bother with binding GraphObject.visible if there's no predicate
                visiblePredicate ? new go.Binding("visible", "", function (o, e) { return o.diagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
        }

        var nodeMenu =  // context menu for each Node
            $(go.Adornment, "Vertical",
                makeButton("Delete",
                    function (e, obj) { e.diagram.commandHandler.deleteSelection(); }),                   
                makeButton("Add input port",
                    function (e, obj) { addPort("left"); }),
                makeButton("Add output port",
                    function (e, obj) { addPort("right"); })
            );

        var portSize = new go.Size(8, 8);

        var portMenu =  // context menu for each port
            $(go.Adornment, "Vertical",
                makeButton("Remove port",
                    // in the click event handler, the obj.part is the Adornment;
                    // its adornedObject is the port
                    function (e, obj) { removePort(obj.part.adornedObject); }),
                makeButton("Remove side ports",
                    function (e, obj) { removeAll(obj.part.adornedObject); })
            );

        var commonBlock = $(go.Node, "Table",
            {
                locationObjectName: "BODY",
                locationSpot: go.Spot.Center,
                selectionObjectName: "BODY",
                contextMenu: nodeMenu
            },
            new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),

            // the body
            $(go.Panel, "Auto",
                {
                    row: 1, column: 1, name: "BODY",
                    stretch: go.GraphObject.Fill
                },
                $(go.Shape, "Rectangle",
                    {
                        fill: "lightgray", stroke: "darkslategray", strokeWidth: 1,
                        minSize: new go.Size(56, 56)
                    }),
                $(go.TextBlock,
                    { margin: 10, textAlign: "center", font: "14px  Segoe UI,sans-serif", stroke: "white", editable: true },
                    new go.Binding("text", "name").makeTwoWay())
            ),  // end Auto Panel body

            // the Panel holding the left port elements, which are themselves Panels,
            // created for each item in the itemArray, bound to data.leftArray
            $(go.Panel, "Vertical",
                new go.Binding("itemArray", "leftArray"),
                {
                    row: 1, column: 0,
                    itemTemplate:
                    $(go.Panel,
                        {
                            _side: "in",  // internal property to make it easier to tell which side it's on
                            fromSpot: go.Spot.Left, toSpot: go.Spot.Left,
                            fromLinkable: true, toLinkable: true, cursor: "pointer",
                            contextMenu: portMenu
                        },
                        new go.Binding("portId", "portId"),
                        $(go.Shape, "Rectangle",
                            {
                                stroke: null, strokeWidth: 0,
                                desiredSize: portSize,
                                margin: new go.Margin(1, 0)
                            },
                            new go.Binding("fill", "portColor"))
                    )  // end itemTemplate
                }
            ),  // end Vertical Panel

            // the Panel holding the top port elements, which are themselves Panels,
            // created for each item in the itemArray, bound to data.topArray
            $(go.Panel, "Horizontal",
                new go.Binding("itemArray", "topArray"),
                {
                    row: 0, column: 1,
                    itemTemplate:
                    $(go.Panel,
                        {
                            _side: "top",
                            fromSpot: go.Spot.Top, toSpot: go.Spot.Top,
                            fromLinkable: true, toLinkable: true, cursor: "pointer",
                            contextMenu: portMenu
                        },
                        new go.Binding("portId", "portId"),
                        $(go.Shape, "Rectangle",
                            {
                                stroke: null, strokeWidth: 0,
                                desiredSize: portSize,
                                margin: new go.Margin(0, 1)
                            },
                            new go.Binding("fill", "portColor"))
                    )  // end itemTemplate
                }
            ),  // end Horizontal Panel

            // the Panel holding the right port elements, which are themselves Panels,
            // created for each item in the itemArray, bound to data.rightArray
            $(go.Panel, "Vertical",
                new go.Binding("itemArray", "rightArray"),
                {
                    row: 1, column: 2,
                    itemTemplate:
                    $(go.Panel,
                        {
                            _side: "out",
                            fromSpot: go.Spot.Right, toSpot: go.Spot.Right,
                            fromLinkable: true, toLinkable: true, cursor: "pointer",
                            contextMenu: portMenu
                        },
                        new go.Binding("portId", "portId"),
                        $(go.Shape, "Rectangle",
                            {
                                stroke: null, strokeWidth: 0,
                                desiredSize: portSize,
                                margin: new go.Margin(1, 0)
                            },
                            new go.Binding("fill", "portColor"))
                    )  // end itemTemplate
                }
            ),  // end Vertical Panel

            // the Panel holding the bottom port elements, which are themselves Panels,
            // created for each item in the itemArray, bound to data.bottomArray
            $(go.Panel, "Horizontal",
                new go.Binding("itemArray", "bottomArray"),
                {
                    row: 2, column: 1,
                    itemTemplate:
                    $(go.Panel,
                        {
                            _side: "bottom",
                            fromSpot: go.Spot.Bottom, toSpot: go.Spot.Bottom,
                            fromLinkable: true, toLinkable: true, cursor: "pointer",
                            contextMenu: portMenu
                        },
                        new go.Binding("portId", "portId"),
                        $(go.Shape, "Rectangle",
                            {
                                stroke: null, strokeWidth: 0,
                                desiredSize: portSize,
                                margin: new go.Margin(0, 1)
                            },
                            new go.Binding("fill", "portColor"))
                    )  // end itemTemplate
                }
            )  // end Horizontal Panel
        );  // end Node

        var andTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Panel, "Table",
                    $(go.Shape, "AndGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
                    $(go.Panel, "Table",
                        new go.Binding("itemArray", "inputPorts"),
                        {
                            margin: new go.Margin(4, 0),
                            alignment: go.Spot.Left,
                            stretch: go.GraphObject.Vertical,
                            rowSizing: go.RowColumnDefinition.ProportionalExtra,
                            itemTemplate:
                            $(go.Panel, "TableRow",
                                $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                            )
                        }
                    )
                ),
                $(go.Shape, "Rectangle", portStyle(false),
                    { portId: "out", alignment: new go.Spot(1, 0.5) })
            );

        var orTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Panel, "Table",
                    $(go.Shape, "OrGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
                    $(go.Panel, "Table",
                        new go.Binding("itemArray", "inputPorts"),
                        {
                            margin: new go.Margin(4, 0),
                            alignment: go.Spot.Left,
                            stretch: go.GraphObject.Vertical,
                            rowSizing: go.RowColumnDefinition.ProportionalExtra,
                            itemTemplate:
                            $(go.Panel, "TableRow",
                                $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                            )
                        }
                    )
                ),
                $(go.Shape, "Rectangle", portStyle(false),
                    { portId: "out", alignment: new go.Spot(1, 0.5) })
            );

        var xorTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Panel, "Table",
                    $(go.Shape, "XorGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
                    $(go.Panel, "Table",
                        new go.Binding("itemArray", "inputPorts"),
                        {
                            margin: new go.Margin(4, 0),
                            alignment: go.Spot.Left,
                            stretch: go.GraphObject.Vertical,
                            rowSizing: go.RowColumnDefinition.ProportionalExtra,
                            itemTemplate:
                            $(go.Panel, "TableRow",
                                $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                            )
                        }
                    )
                ),
                $(go.Shape, "Rectangle", portStyle(false),
                    { portId: "out", alignment: new go.Spot(1, 0.5) })
            );

        var norTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Panel, "Table",
                    $(go.Shape, "NorGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
                    $(go.Panel, "Table",
                        new go.Binding("itemArray", "inputPorts"),
                        {
                            margin: new go.Margin(4, 0),
                            alignment: go.Spot.Left,
                            stretch: go.GraphObject.Vertical,
                            rowSizing: go.RowColumnDefinition.ProportionalExtra,
                            itemTemplate:
                            $(go.Panel, "TableRow",
                                $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                            )
                        }
                    )
                ),
                $(go.Shape, "Rectangle", portStyle(false),
                    { portId: "out", alignment: new go.Spot(1, 0.5) })
            );

        var xnorTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Panel, "Table",
                    $(go.Shape, "XnorGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
                    $(go.Panel, "Table",
                        new go.Binding("itemArray", "inputPorts"),
                        {
                            margin: new go.Margin(4, 0),
                            alignment: go.Spot.Left,
                            stretch: go.GraphObject.Vertical,
                            rowSizing: go.RowColumnDefinition.ProportionalExtra,
                            itemTemplate:
                            $(go.Panel, "TableRow",
                                $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                            )
                        }
                    )
                ),
                $(go.Shape, "Rectangle", portStyle(false),
                    { portId: "out", alignment: new go.Spot(1, 0.5) })
            );

        var nandTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Panel, "Table",
                    $(go.Shape, "NandGate", shapeStyle(), { margin: new go.Margin(0, 0, 0, 3) }),
                    $(go.Panel, "Table",
                        new go.Binding("itemArray", "inputPorts"),
                        {
                            margin: new go.Margin(4, 0),
                            alignment: go.Spot.Left,
                            stretch: go.GraphObject.Vertical,
                            rowSizing: go.RowColumnDefinition.ProportionalExtra,
                            itemTemplate:
                            $(go.Panel, "TableRow",
                                $(go.Shape, "Rectangle", portStyle(true), new go.Binding("portId"))
                            )
                        }
                    )
                ),
                $(go.Shape, "Rectangle", portStyle(false),
                    { portId: "out", alignment: new go.Spot(1, 0.5) })
            );

        var notTemplate =
            $(go.Node, "Spot", nodeStyle(),
                $(go.Shape, "Inverter", shapeStyle()),
                $(go.Shape, "Rectangle", portStyle(true),
                    { portId: "in", alignment: new go.Spot(0, 0.5) }),
                $(go.Shape, "Rectangle", portStyle(false),
                    { portId: "out", alignment: new go.Spot(1, 0.5) })
            );

        // add the templates created above to myDiagram and palette
        myDiagram.nodeTemplateMap.add("input", inputTemplate);
        myDiagram.nodeTemplateMap.add("output", outputTemplate);
        myDiagram.nodeTemplateMap.add("and", andTemplate);
        myDiagram.nodeTemplateMap.add("or", orTemplate);
        myDiagram.nodeTemplateMap.add("xor", xorTemplate);
        myDiagram.nodeTemplateMap.add("not", notTemplate);
        myDiagram.nodeTemplateMap.add("nand", nandTemplate);
        myDiagram.nodeTemplateMap.add("nor", norTemplate);
        myDiagram.nodeTemplateMap.add("xnor", xnorTemplate);
        myDiagram.nodeTemplateMap.add("common", commonBlock);

        // share the template map with the Palette
        var palette = new go.Palette("palette");  // create a new Palette in the HTML DIV element "palette"
        palette.nodeTemplateMap = myDiagram.nodeTemplateMap;

        palette.model.nodeDataArray = [
            { category: "input" },
            { category: "output" },
            { category: "and", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
            { category: "or", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
            { category: "xor", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
            { category: "not" },
            { category: "nand", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
            { category: "nor", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
            { category: "xnor", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] },
            { category: "common", numInputs: 2, inputPorts: [{ portId: "in1" }, { portId: "in2" }] }
        ];

        // support editing the properties of the selected person in HTML
        if (window.Inspector) {
            // Inspector helper function
            function updateNumInputs(name, value) {
                if (name !== "numInputs") return;
                var data = myInspector.inspectedObject.data;
                var inputs = data.inputPorts;
                if (value > inputs.length) {
                    while (value > inputs.length) {
                        myDiagram.model.addArrayItem(inputs, { portId: "in" + (inputs.length + 1).toString() });
                    }
                } else if (value < inputs.length) {
                    while (value < inputs.length) {
                        myDiagram.model.removeArrayItem(inputs, inputs.length - 1);
                    }
                }
            }

            myInspector = new Inspector("myInspector", myDiagram,
                {
                    properties: {
                        "key": { readOnly: true, show: Inspector.showIfPresent },
                        "text": {},
                        "fill": { type: "color", defaultValue: "lightgray", show: Inspector.showIfNode },
                        "stroke": { type: "color", defaultValue: "black", show: Inspector.showIfLink },
                        "numInputs": { type: "number", defaultValue: 2, show: Inspector.showIfPresent },
                        "inputPorts": { show: false }
                    },
                    propertyModified: updateNumInputs
                });
        }

        // load the initial diagram
        load();
    }

    // save a model to and load a model from JSON text, displayed below the Diagram
    function save() {
        document.getElementById("mySavedModel").value = myDiagram.model.toJson();
        myDiagram.isModified = false;
    }
    function load() {
        myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
    }

    // Add a port to the specified side of the selected nodes.
    function addPort(side) {
        myDiagram.startTransaction("addPort");
        myDiagram.selection.each(function (node) {
            // skip any selected Links
            if (!(node instanceof go.Node)) return;
            // compute the next available index number for the side
            var i = 0;
            while (node.findPort(side + i.toString()) !== node) i++;
            // now this new port name is unique within the whole Node because of the side prefix
            var name = side + i.toString();
            // get the Array of port data to be modified
            var arr = node.data[side + "Array"];
            if (arr) {
                // create a new port data object
                var newportdata = {
                    portId: name,
                    portColor: go.Brush.randomColor()
                    // if you add port data properties here, you should copy them in copyPortData above
                };
                // and add it to the Array of port data
                myDiagram.model.insertArrayItem(arr, -1, newportdata);
            }
        });
        myDiagram.commitTransaction("addPort");
    }

    // Remove the clicked port from the node.
    // Links to the port will be redrawn to the node's shape.
    function removePort(port) {
        myDiagram.startTransaction("removePort");
        var pid = port.portId;
        var arr = port.panel.itemArray;
        for (var i = 0; i < arr.length; i++) {
            if (arr[i].portId === pid) {
                myDiagram.model.removeArrayItem(arr, i);
                break;
            }
        }
        myDiagram.commitTransaction("removePort");
    }

    // Remove all ports from the same side of the node as the clicked port.
    function removeAll(port) {
        myDiagram.startTransaction("removePorts");
        var nodedata = port.part.data;
        var side = port._side;  // there are four property names, all ending in "Array"
        myDiagram.model.setDataProperty(nodedata, side + "Array", []);  // an empty Array
        myDiagram.commitTransaction("removePorts");
    }

    // This custom-routing Link class tries to separate parallel links from each other.
    // This assumes that ports are lined up in a row/column on a side of the node.
    function CustomLink() {
        go.Link.call(this);
    };
    go.Diagram.inherit(CustomLink, go.Link);

    CustomLink.prototype.findSidePortIndexAndCount = function (node, port) {
        var nodedata = node.data;
        if (nodedata !== null) {
            var portdata = port.data;
            var side = port._side;
            var arr = nodedata[side + "Array"];
            var len = arr.length;
            for (var i = 0; i < len; i++) {
                if (arr[i] === portdata) return [i, len];
            }
        }
        return [-1, len];
    };

    /** @override */
    CustomLink.prototype.computeEndSegmentLength = function (node, port, spot, from) {
        var esl = go.Link.prototype.computeEndSegmentLength.call(this, node, port, spot, from);
        var other = this.getOtherPort(port);
        if (port !== null && other !== null) {
            var thispt = port.getDocumentPoint(this.computeSpot(from));
            var otherpt = other.getDocumentPoint(this.computeSpot(!from));
            if (Math.abs(thispt.x - otherpt.x) > 20 || Math.abs(thispt.y - otherpt.y) > 20) {
                var info = this.findSidePortIndexAndCount(node, port);
                var idx = info[0];
                var count = info[1];
                if (port._side == "top" || port._side == "bottom") {
                    if (otherpt.x < thispt.x) {
                        return esl + 4 + idx * 8;
                    } else {
                        return esl + (count - idx - 1) * 8;
                    }
                } else {  // left or right
                    if (otherpt.y < thispt.y) {
                        return esl + 4 + idx * 8;
                    } else {
                        return esl + (count - idx - 1) * 8;
                    }
                }
            }
        }
        return esl;
    };

    /** @override */
    CustomLink.prototype.hasCurviness = function () {
        if (isNaN(this.curviness)) return true;
        return go.Link.prototype.hasCurviness.call(this);
    };

    /** @override */
    CustomLink.prototype.computeCurviness = function () {
        if (isNaN(this.curviness)) {
            var fromnode = this.fromNode;
            var fromport = this.fromPort;
            var fromspot = this.computeSpot(true);
            var frompt = fromport.getDocumentPoint(fromspot);
            var tonode = this.toNode;
            var toport = this.toPort;
            var tospot = this.computeSpot(false);
            var topt = toport.getDocumentPoint(tospot);
            if (Math.abs(frompt.x - topt.x) > 20 || Math.abs(frompt.y - topt.y) > 20) {
                if ((fromspot.equals(go.Spot.Left) || fromspot.equals(go.Spot.Right)) &&
                    (tospot.equals(go.Spot.Left) || tospot.equals(go.Spot.Right))) {
                    var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);
                    var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);
                    var c = (fromseglen - toseglen) / 2;
                    if (frompt.x + fromseglen >= topt.x - toseglen) {
                        if (frompt.y < topt.y) return c;
                        if (frompt.y > topt.y) return -c;
                    }
                } else if ((fromspot.equals(go.Spot.Top) || fromspot.equals(go.Spot.Bottom)) &&
                    (tospot.equals(go.Spot.Top) || tospot.equals(go.Spot.Bottom))) {
                    var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);
                    var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);
                    var c = (fromseglen - toseglen) / 2;
                    if (frompt.x + fromseglen >= topt.x - toseglen) {
                        if (frompt.y < topt.y) return c;
                        if (frompt.y > topt.y) return -c;
                    }
                }
            }
        }
        return go.Link.prototype.computeCurviness.call(this);
    };
    // end CustomLink class
</script>

Body section

Save Load Left Diagram Model saved in JSON format:
{ "class": "go.GraphLinksModel", "copiesArrays": true, "copiesArrayObjects": true, "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "nodeDataArray": [ {"category":"input", "key":-1, "loc":"-240 -160"}, {"category":"input", "key":-2, "loc":"-240 -50"}, {"category":"and", "numInputs":3, "inputPorts":[ {"portId":"in1"},{"portId":"in2"},{"portId":"in3"} ], "key":-3, "loc":"-80 -110", "text":"", "fill":"lightgray"}, {"category":"input", "key":-4, "loc":"-240 20"}, {"category":"or", "numInputs":2, "inputPorts":[ {"portId":"in1"},{"portId":"in2"} ], "key":-5, "loc":"70 -110"}, {"category":"input", "key":-6, "loc":"-20 -40"}, {"category":"output", "key":-7, "loc":"240 -110"} ], "linkDataArray": [ {"from":-1, "to":-3, "fromPort":"", "toPort":"in1", "points":[-194.5,-139,-184.5,-139,-137.25,-139,-137.25,-100.33333333333333,-90,-100.33333333333333,-80,-100.33333333333333]}, {"from":-2, "to":-3, "fromPort":"", "toPort":"in2", "points":[-194.5,-29,-184.5,-29,-149,-29,-149,-89,-90,-89,-80,-89]}, {"from":-4, "to":-3, "fromPort":"", "toPort":"in3", "points":[-194.5,41,-184.5,41,-137.25,41,-137.25,-77.66666666666666,-90,-77.66666666666666,-80,-77.66666666666666]}, {"from":-3, "to":-5, "fromPort":"out", "toPort":"in1", "points":[-31.5,-89,-21.5,-89,19.25,-89,19.25,-97.5,60,-97.5,70,-97.5]}, {"from":-6, "to":-5, "fromPort":"", "toPort":"in2", "points":[25.5,-19,35.5,-19,47.75,-19,47.75,-80.5,60,-80.5,70,-80.5]}, {"from":-5, "to":-7, "fromPort":"out", "toPort":"", "points":[118.5,-89,128.5,-89,179.25,-89,179.25,-89,230,-89,240,-89]} ]}

Did you have a specific question that I can answer?

These are the 2 questions.

  1. Since i am giving user an option to increase no.of inputs for logical gates, how to increase the size of the gate with respect to no.of input ports? I tried the same as in dynamic ports for size increase but could not.

  2. Along with logical gates i also want to add a new component in the palette so that i can use it by defining no.of input ports and output ports using data inspector. This should also increase in size with respect to no.of ports.

I want to achieve these with modifying above code. If you can modify and give me above code i am very much thankful to you.

  1. That depends on how you use panels in your node template. Since the node template in Dynamic Ports sample does that, you just need to change the shape’s figure.

  2. If you have extended your node template to support properties such as “numOutputs” and “outputPorts”, and added those properties to your node data, that should just work.

A post was split to a new topic: Dynamically adding input and output ports