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>