I want to know if there is a way to avoid node overlapping when importing an schematic.
Here is the code for the creation of the diagram:
interlockDiagram = new go.Diagram(diagramContainer, {
// Diagram initialization options
"draggingTool.isGridSnapEnabled": true,
"commandHandler.archetypeGroupData": {
text: "Group",
isGroup: true,
color: "blue",
},
"undoManager.isEnabled": true,
"draggingTool.dragsLink": true,
"draggingTool.isGridSnapEnabled": true,
"linkingTool.isUnconnectedLinkValid": true,
"linkingTool.portGravity": 20,
"relinkingTool.isUnconnectedLinkValid": true,
"relinkingTool.portGravity": 20,
"relinkingTool.fromHandleArchetype": $(go.Shape, "Diamond", {
segmentIndex: 0,
cursor: "pointer",
desiredSize: new go.Size(8, 8),
fill: "tomato",
stroke: "darkred",
}),
"relinkingTool.toHandleArchetype": $(go.Shape, "Diamond", {
segmentIndex: -1,
cursor: "pointer",
desiredSize: new go.Size(8, 8),
fill: "darkred",
stroke: "tomato",
}),
"linkReshapingTool.handleArchetype": $(go.Shape, "Diamond", {
desiredSize: new go.Size(7, 7),
fill: "lightblue",
stroke: "deepskyblue",
}),
"rotatingTool.handleAngle": 270,
"rotatingTool.handleDistance": 30,
"rotatingTool.snapAngleMultiple": 15,
"rotatingTool.snapAngleEpsilon": 15,
"undoManager.isEnabled": true,
scrollMode: go.Diagram.DocumentScroll,
maxScale: 2,
minScale: 0.5
});
Also, here is the template for the Node:
const $ = go.GraphObject.make;
const portSize = new go.Size(10, 4);
const normalWidth = 50;
const halfWidth = 25;
const standardPosition = 0.5;
const GradientLightBlue = $(go.Brush, "Linear", {
0: "#DBF3FF",
1: "#0099df",
});
const GradientLightGray = $(go.Brush, "Linear", {
0: "#e4e4e4",
1: "#b7b7b7",
});
const gates = {
"DIN": { shapeType: "RoundedRectangle", width : halfWidth },
"DOUT": { shapeType: "Rectangle", width : halfWidth },
"SOUT": { shapeType: "RoundedRectangle", width : halfWidth },
"or": { shapeType: "OrGate", width : normalWidth },
"xor": { shapeType: "XorGate", width : normalWidth },
"and": { shapeType: "AndGate", width : normalWidth },
"buffer": { shapeType: "TriangleRight", width : normalWidth },
"nor": { shapeType: "NorGate", width : normalWidth },
"xnor": { shapeType: "XnorGate", width : normalWidth },
"nand": { shapeType: "NandGate", width : normalWidth },
"norBuffer": { shapeType: "NorBufferGate", width : normalWidth },
"latch": { shapeType: "Rectangle", width : normalWidth }
}
function createTimerSymbolPanel() {
const circleSize = 15;
const scaleFactor = circleSize / 20; // original size was 20
return $(go.Panel, "Spot", // Use "Spot" panel to overlay shapes
// Outer circle
$(go.Shape, "Circle", {
width: circleSize,
height: circleSize,
stroke: "black",
fill: null // Ensure the circle is empty
},
new go.Binding("visible", "timer_symbol", function(symbol) {
return symbol === 0 || symbol === 1 || symbol === 2;
})),
// Pulse symbol
$(go.Shape, {
geometryString: "M0 10 L5 10 L5 0 L15 0 L15 10 L20 10", // Custom geometry string to match the shape in the image
stroke: "black",
strokeWidth: 1,
visible: false, // Initially hidden
scale: scaleFactor // Scale the symbol
},
new go.Binding("visible", "timer_symbol", function(symbol) {
return symbol === 0; // Show if timer_symbol is 0 (Pulse)
})),
// On switch symbol
$(go.Shape, {
geometryString: "M4 16 L10 16 L10 4 L16 4", // Adjusted custom geometry string for "On"
stroke: "black",
strokeWidth: 1,
visible: false, // Initially hidden
scale: scaleFactor // Scale the symbol
},
new go.Binding("visible", "timer_symbol", function(symbol) {
return symbol === 1; // Show if timer_symbol is 1 (On)
})),
// Off switch symbol (reversed On symbol)
$(go.Shape, {
geometryString: "M16 16 L10 16 L10 4 L4 4", // Reversed custom geometry string for "Off"
stroke: "black",
strokeWidth: 1,
visible: false, // Initially hidden
scale: scaleFactor // Scale the symbol
},
new go.Binding("visible", "timer_symbol", function(symbol) {
return symbol === 2; // Show if timer_symbol is 2 (Off)
})),
);
}
// function to calculate X position for the icon rendered in the Toolbar
function CalculateIconPosition(gateName) {
const iconsToBeCentered = ["or", "and", "buffer"]
let positionX = iconsToBeCentered.findIndex(icon => icon === gateName) > -1 ? 0.52 : standardPosition
positionX = gateName === "xor" ? 0.49 : positionX;
return new go.Spot(positionX, standardPosition);
}
// function to calculate the X position for the port in the Toolbar
function CalculatePortPositionForToolbarIcon(gateName) {
const iconsToBeCentered = ["or", "xor"]
const positionX = iconsToBeCentered.findIndex(icon => icon === gateName) > -1 ? 0.3 : 0.23
return new go.Spot(positionX, standardPosition);
}
// function to return the size of the gate per GateName
function CalculateGateSize(gateName) {
let width = normalWidth;
const gateProperties = gates[gateName];
if (gateProperties)
width = gateProperties.width;
return new go.Size(normalWidth, width);
}
// function to return the Shape that will be rendered per GateName
function GetShapePerGateName(gateName) {
let shapeType = "Rectangle";
const gateProperties = gates[gateName]
if (gateProperties)
shapeType = gateProperties.shapeType;
return shapeType;
}
// function to display Signal Label,
// only DIN, DOUT & SOUT gate will display Signal Label
// otherwise position will be changed when adding input ports
function DisplaySignalLabelPerGate(gateName) {
const gates = ["DIN", "DOUT", "SOUT"]
return gates.findIndex(icon => icon === gateName) > -1;
}
// function that defines the template to be used by each gate
// when rendered as Node in Diagram
function DiagramTemplateFactory(gateName) {
const itemTemplate = $(
go.Node,
"Spot",
{
locationObjectName: "BODY",
locationSpot: go.Spot.Center,
selectionObjectName: "BODY",
selectionAdorned: false,
shadowOffset: new go.Point(0, 0),
shadowBlur: 10,
shadowColor: "#555",
toolTip: $(
"ToolTip",
{
"Border.figure": "RoundedRectangle"
},
$(
go.TextBlock,
{
margin: 2,
minSize: new go.Size(30, 10),
textAlign: "center",
},
new go.Binding("text", "category")
)
),
},
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), // Binding the location coordinates in workspace
new go.Binding("isShadowed", "isSelected").ofObject(),
// the panel holding the body of the node
$(
go.Panel,
"Auto",
$(
go.Shape,
GetShapePerGateName(gateName),
{
name: "SHAPE", // Add a name to the shape for easier reference
fill: GradientLightGray, // Initial fill color
stroke: "darkslategray",
strokeWidth: 1,
desiredSize: CalculateGateSize(gateName),
cursor: "pointer",
}
)
),
// Text block for signal Label
$(
go.TextBlock,
{
name: "LABEL",
textAlign: "center",
stroke: "black",
isMultiline: false
},
new go.Binding("text", "signalLabel").makeTwoWay(),
new go.Binding("font", "", (data, node) => {
return DisplaySignalLabelPerGate(data.category) ? `bold 9.8px sans-serif`: `bold 15px sans-serif`;
}),
new go.Binding("alignment", "", (data, node) => {
return DisplaySignalLabelPerGate(data.category) ? go.Spot.Center : new go.Spot(0.5, -0.3);
}),
new go.Binding("margin", "", (data, node) => {
return DisplaySignalLabelPerGate(data.category) ? 0 : 4;
}),
),
//TextBlock for Block Labels
$(
go.Panel,
"Auto",
new go.Binding("alignment", "", (data) => {
return data.category === "DIN" ? new go.Spot(1, 0.5, -120, 0) : new go.Spot(0, 0.5, 120, 0);
}),
$(
go.TextBlock,
{
stroke: "#484848",
isMultiline: false,
editable: true
},
new go.Binding("font", "", () => {
return `bold ${halfWidth / 2.2}px sans-serif`;
}),
new go.Binding("alignmentFocus", "", (data) => {
return data.category === "DIN" ? go.Spot.Left : go.Spot.Left;
}),
new go.Binding("text", "blockLabel").makeTwoWay()
)
),
$(
go.Panel,
"Horizontal",
{
alignment: go.Spot.Bottom,
},
new go.Binding("itemArray", "latchTermArray"),
{
itemTemplate: $(
go.Panel,
"Horizontal",
{
_side: "latchTerms", // internal property to make it easier to tell which side it's on
alignment: go.Spot.Bottom,
fromSpot: go.Spot.Bottom,
toSpot: go.Spot.Bottom,
fromLinkable: true,
toLinkable: true,
cursor: "pointer",
},
new go.Binding("portId", "portId"),
$(
go.Shape,
"Rectangle",
{
stroke: null,
fill: "#00719c",
strokeWidth: 0,
desiredSize: new go.Size(4, 12)
},
),
// Texblock to display Port Name
$(
go.TextBlock,
{
font: "6.5pt sans-serif",
margin: new go.Margin(1, 0, 0 , 2)
},
new go.Binding("text", "portName")
)
),
}
),
$(go.Panel, "Vertical",
createTimerSymbolPanel() // Create a unique instance of the timer symbol panel for each node
),
$(go.Panel, "Horizontal",
{
alignment: new go.Spot(0.5, 1.2),
},
// Add timer period text
$(go.TextBlock, {
textAlign: "center",
stroke: "blue",
font: "12pt Helvetica, Arial, sans-serif",
},
new go.Binding("text", "timer_period").makeTwoWay(),
new go.Binding("visible", "timer_period", v => v !== undefined)),
),
// 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",
{
alignment: go.Spot.Left,
alignmentFocus: new go.Spot(0, 0.5, 10, 0),
},
new go.Binding("itemArray", "inputsArray"),
{
itemTemplate: $(
go.Panel,
"Horizontal",
{
_side: "inputs", // internal property to make it easier to tell which side it's on
alignment: go.Spot.TopLeft,
fromSpot: go.Spot.Left,
toSpot: go.Spot.Left,
fromLinkable: true,
toLinkable: true,
cursor: "pointer",
margin: new go.Margin(1, 0)
},
new go.Binding("portId", "portId"),
$(
go.Shape,
"Rectangle",
{
stroke: null,
fill: "black",
strokeWidth: 0,
desiredSize: portSize,
},
),
// Texblock to display Port Name
$(
go.TextBlock,
{
font: "6.5pt sans-serif",
margin: new go.Margin(1, 0, 0 , 2)
},
new go.Binding("text", "portName")
)
), // end itemTemplate for inputs array
}
), // end Vertical 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",
{
alignment: go.Spot.Right,
alignmentFocus: new go.Spot(1, 0.5, -10, 0),
},
new go.Binding("itemArray", "outputsArray"),
{
itemTemplate: $(
go.Panel,
"Horizontal",
{
_side: "outputs",
alignment: go.Spot.TopRight,
fromSpot: go.Spot.Right,
toSpot: go.Spot.Right,
fromLinkable: true,
toLinkable: true,
cursor: "pointer",
},
new go.Binding("portId", "portId"),
// Texblock to display Port Name
$(
go.TextBlock,
{
font: "6.5pt sans-serif",
margin: new go.Margin(1, 1, 0 ,0)
},
new go.Binding("text", "portName")
),
$(
go.Shape,
"Rectangle",
{
stroke: null,
fill: "black",
strokeWidth: 0,
desiredSize: portSize,
}
),
), // end itemTemplate for outputs array
}
), // end Vertical Panel
);
return itemTemplate;
}
// function that defines the template to be used by each icon
// when rendered as Node in Toolbar (Palette)
function ToolbarTemplateFactory(gateName) {
const itemTemplate = $(
go.Node,
"Vertical",
{
locationObjectName: "SHAPE",
locationSpot: go.Spot.Center,
selectionAdorned: false,
},
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
{
resizable: false,
resizeObjectName: "SHAPE",
},
$(
go.Panel,
"Spot",
$(
go.Shape,
"Circle", // Outer circle
{
strokeWidth: 1,
name: "SHAPE",
desiredSize: new go.Size(normalWidth + 5, normalWidth + 5),
portId: "",
fromLinkable: true,
toLinkable: true,
cursor: "pointer",
fromSpot: go.Spot.RightSide,
toSpot: go.Spot.LeftSide,
fill: GradientLightBlue,
strokeWidth: 2,
stroke: "#073763",
},
), // end main shape
$(
go.Shape,
"Rectangle", // Input port
{
alignment: CalculatePortPositionForToolbarIcon(gateName), //new go.Spot(0.23, standardPosition),
desiredSize: new go.Size(8, 3),
fill: "black",
stroke: null,
visible: gateName === "DIN" || gateName === "DOUT" ? false : true
}
),
$(
go.Shape,
"Rectangle", // Output port
{
alignment: new go.Spot(0.75, standardPosition),
desiredSize: new go.Size(12, 3),
fill: "black",
stroke: null,
visible: gateName === "SOUT" ? false : true
}
),
$(
go.Shape,
GetShapePerGateName(gateName),
{
alignment: CalculateIconPosition(gateName),
desiredSize: new go.Size(halfWidth, halfWidth),
stroke: "black",
fill: "white",
cursor: "pointer"
},
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)
),
), // end Auto Panel
$(
go.TextBlock,
{
alignment: go.Spot.Center,
textAlign: "center",
margin: 5,
editable: true,
},
new go.Binding("text", "category").makeTwoWay()
)
); // end go.Node Vertical
return itemTemplate;
}
// Template that defines the middle point in the Link
// which can be used to branch a wire/link/connection
const LinkLabelTemplate = $(
"Node",
{
selectable: false,
avoidable: false,
layerName: "Foreground"
},
$(
go.Shape,
"Circle",
{
width: 5,
height: 5,
stroke: null,
portId: "",
fromLinkable: true,
toLinkable: true,
cursor: "pointer"
}
)
)
const TemplateFactory = {
DiagramTemplateFactory,
ToolbarTemplateFactory,
LinkLabelTemplate,
};
export default TemplateFactory;
And this is how one of my schematics is looking: