How about this?
This is going into the next baselevel of version 3.1, which is still in pre-release.
Here’s the code I used. It’s basically your code without bothering to try to be clever, hence the commented out lines and deleted code to try to use the node’s TextBlock as the port.
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:800px"></div>
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
<!-- <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script> -->
<script src="../out/go-dev.js"></script>
<script id="code">
const mediumModelData = {
nodeLabels: [
'Assigned',
'Closed',
'IT Support - Americas',
'IT Support Service Desk Level 1',
'IT Systems SRT',
'In Progress',
'New',
'Process End',
'Process Start',
'Resolved'
],
edgeLabels: [
'Assigned --> In Progress',
'Assigned --> Process End',
'Assigned --> Resolved',
'Closed --> Process End',
'IT Support - Americas --> New',
'IT Support - Americas --> Process End',
'IT Support Service Desk Level 1 --> Process End',
'IT Systems SRT --> Process End',
'In Progress --> Process End',
'In Progress --> Resolved',
'New --> Assigned',
'New --> Process End',
'Process Start --> IT Support - Americas',
'Process Start --> IT Support Service Desk Level 1',
'Process Start --> IT Systems SRT',
'Resolved --> Closed',
'Resolved --> Process End'
]
};
/**
* Builds a demo GoJS model based on provided node and edge labels.
* @param {Object} param0
* @param {string[]} param0.nodeLabels - Array of node labels
* @param {string[]} param0.edgeLabels - Array of edge labels, with format "fromNodeLabel --> toNodeLabel"
* @returns
*/
function buildModel({ nodeLabels, edgeLabels }) {
const nodes = nodeLabels.map(label => ({ key: label }));
const edges = edgeLabels.map(label => {
const [fromNodeLabel, toNodeLabel] = label.split('-->').map(s => s.trim());
return { from: fromNodeLabel, to: toNodeLabel };
});
return { nodes, edges };
}
/**
* Sets up a GoJS diagram with a custom layout and predefined node and edge templates.
* @param {Object} param0
* @param {go.Diagram} param0.diagram
*/
function setupDiagram({ diagram }) {
diagram.layout = new go.LayeredDigraphLayout({
direction: 90, // Downward,
setsPortSpots: false,
layerSpacing: 70,
columnSpacing: 30,
initializeOption: go.LayeredDigraphLayout.InitDepthFirstIn, // go.LayeredDigraphInit.DepthFirstIn,
alignOption: go.LayeredDigraphLayout.AlignNone, // go.LayeredDigraphAlign.None,
packOption: go.LayeredDigraphLayout.PackStraighten,
layeringOption: go.LayeredDigraphLayout.LayerOptimalLinkLength, // go.LayeredDigraphLayering.OptimalLinkLength,
aggressiveOption: go.LayeredDigraphLayout.AggressiveMore, // go.LayeredDigraphAggressive.More,
cycleRemoveOption: go.LayeredDigraphLayout.CycleDepthFirst, // go.LayeredDigraphCycleRemove.DepthFirst,
});
diagram.nodeTemplate = new go.Node(go.Panel.Auto, {
//fromSpot: go.Spot.Center,
//toSpot: go.Spot.Center,
})
.add(
new go.Shape('RoundedRectangle', { fill: 'LightYellow', stroke: 'Black', parameter1: 25 }),
new go.Panel(go.Panel.Vertical)
.add(
new go.TextBlock({ font: 'bold 14pt sans-serif' })
.bind('text', '', data => data.key)
)
);
diagram.linkTemplate = new go.Link({
curve: go.Curve.Bezier,
//curviness: 0,
//fromPortId: 'label',
//toPortId: 'label',
})
.add(
new go.Shape({ stroke: 'Gray' }),
new go.Shape({ toArrow: 'Standard' })
);
const model = buildModel(mediumModelData);
diagram.model = new go.GraphLinksModel(model.nodes, model.edges);
}
const myDiagram = new go.Diagram("myDiagramDiv", {
initialAutoScale: go.AutoScale.Uniform
});
setupDiagram({ diagram: myDiagram })
</script>
</body>
</html>