Yes, I’m aware of the group layouts being computed on the server and the diagram layout being computed on the client.
Here’s my latest version of the sample, intended to run on the client using the model data shown in the textarea which does have location information, even though the node templates do not have TwoWay Bindings on the “location”.
Change the value of CLIENT
to be false when wanting to pretend to run the code on a server and save the serialized model to the textarea to be loaded on the client.
<!DOCTYPE html>
<html>
<head>
<title>Loading Graph from Server Data</title>
<!-- Copyright 1998-2023 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<button id="myTestButton">Change Diagram.layout</button>
<!-- this model includes "loc" and "size" and "expanded" property values,
and thus does not require Group.layout being a TableLayout nor any TwoWay Bindings -->
<textarea id="mySavedModel" style="width:100%;height:250px">
{ "class": "GraphLinksModel",
"nodeDataArray": [
{"key":1,"isGroup":true,"loc":"0 33.81052598953247"},
{"key":2,"isGroup":true,"group":1,"row":0,"col":0,"loc":"8 75.62105197906494","size":"220 81.61052598953248"},
{"key":3,"isGroup":true,"group":1,"row":0,"col":1,"loc":"236 75.62105197906494","size":"327 81.61052598953248"},
{"key":4,"isGroup":true,"group":1,"row":0,"col":2,"loc":"571 75.62105197906494","size":"433 81.61052598953248"},
{"key":5,"isGroup":true,"group":1,"row":0,"col":3,"loc":"1012 75.62105197906494","size":"115 81.61052598953248"},
{"key":6,"isGroup":true,"row":0,"col":4,"loc":"281.5 305.3421039581298"},
{"key":7,"isGroup":true,"group":6,"row":0,"col":0,"loc":"289.5 347.1526299476623","size":"221 81.61052598953248"},
{"key":8,"isGroup":true,"group":6,"row":0,"col":1,"loc":"518.5 347.1526299476623","size":"327 81.61052598953248"},
{"key":9,"isGroup":true,"group":1,"row":1,"col":0,"loc":"8 165.2315779685974","size":"220 81.61052598953248"},
{"key":10,"isGroup":true,"group":1,"row":1,"col":1,"colSpan":3,"loc":"236 165.2315779685974","size":"891 50.31052598953247"},
{"group":2,"row":0,"col":0,"key":-11,"loc":"16 83.62105197906494"},
{"group":2,"row":0,"col":1,"key":-12,"loc":"121 83.62105197906494"},
{"group":3,"row":0,"col":0,"key":-13,"loc":"244 83.62105197906494"},
{"group":3,"row":0,"col":1,"key":-14,"loc":"350 83.62105197906494"},
{"group":3,"row":0,"col":2,"key":-15,"loc":"456 83.62105197906494"},
{"group":4,"row":0,"col":0,"key":-16,"loc":"579 83.62105197906494"},
{"group":4,"row":0,"col":1,"key":-17,"loc":"685 83.62105197906494"},
{"group":4,"row":0,"col":2,"key":-18,"loc":"791 83.62105197906494"},
{"group":4,"row":0,"col":3,"key":-19,"loc":"897 83.62105197906494"},
{"group":5,"row":0,"col":0,"key":-20,"loc":"1020 83.62105197906494"},
{"group":7,"row":0,"col":0,"key":-21,"loc":"297.5 355.1526299476623"},
{"group":7,"row":0,"col":1,"key":-22,"loc":"403.5 355.1526299476623"},
{"group":8,"row":0,"col":0,"key":-23,"loc":"526.5 355.1526299476623"},
{"group":8,"row":0,"col":1,"key":-24,"loc":"632.5 355.1526299476623"},
{"group":8,"row":0,"col":2,"key":-25,"loc":"738.5 355.1526299476623"},
{"group":9,"row":0,"col":0,"key":-26,"loc":"16 173.2315779685974"}
],
"linkDataArray": [{"from":1,"to":6}]}
</textarea>
<script src="https://unpkg.com/gojs"></script>
<script src="https://unpkg.com/gojs/extensions/TableLayout.js"></script>
<script id="code">
const CLIENT = true;
const $ = go.GraphObject.make;
const myDiagram =
$(go.Diagram, "myDiagramDiv",
{
initialAutoScale: go.Diagram.Uniform,
layout: $(go.TreeLayout, { angle: 90 }),
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
{
alignment: go.Spot.Left,
margin: 4
},
CLIENT
? new go.Binding("location", "loc", go.Point.parse)
: new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
new go.Binding("row", "row"), // these all affect TableLayout of containing Group
new go.Binding('column', 'col'),
new go.Binding('columnSpan', 'colSpan'),
$(go.Shape,
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text", "key", k => `Node key: ${k}`))
);
myDiagram.groupTemplate =
$(go.Group, "Vertical",
{
alignment: go.Spot.TopLeft, // affects TableLayout of containing group
stretch: go.GraphObject.Horizontal,
margin: 4, // affects TableLayout of containing group
layout: CLIENT ? $(go.Layout) : $(TableLayout), // affects arrangement of member nodes
minSize: new go.Size(100, 50), // affects this panel (i.e. whole group)
defaultStretch: go.GraphObject.Horizontal // affects elements of this panel
},
CLIENT
? new go.Binding("location", "loc", go.Point.parse)
: new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
new go.Binding("row", "row"), // these all affect TableLayout of containing Group
new go.Binding("column", "col"),
new go.Binding("columnSpan", "colSpan"),
new go.Binding("isSubGraphExpanded", "expanded").makeTwoWay(),
// the header
$(go.Panel, "Table",
{ padding: new go.Margin(8, 4), background: "green" },
new go.Binding("background", "color"),
$(go.Panel, "Horizontal",
$("SubGraphExpanderButton"),
$(go.TextBlock,
{ font: "bold 12pt sans-serif", margin: new go.Margin(0, 4) },
new go.Binding("text", "", data => `Table: ${data.key}`)
)
)
),
// the body holding the member nodes
$(go.Panel, "Auto",
$(go.Shape, { fill: null }),
$(go.Placeholder, { padding: 8, alignment: go.Spot.Left })
)
);
if (CLIENT) { // client side
// Load the model shown above in the textarea where each node data object
// does have location information and the templates have OneWay Bindings.
var str = document.getElementById("mySavedModel").value;
myDiagram.model = go.Model.fromJson(str);
} else { // server side
// Note that the node data do NOT have any location information,
// so this requires Group.layout being a TableLayout and the templates
// having TwoWay Bindings on "location" so as to remember the layout results.
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, isGroup: true },
{ key: 2, isGroup: true, group: 1, row: 0, col: 0 },
{ key: 3, isGroup: true, group: 1, row: 0, col: 1 },
{ key: 4, isGroup: true, group: 1, row: 0, col: 2 },
{ key: 5, isGroup: true, group: 1, row: 0, col: 3 },
{ key: 6, isGroup: true, row: 0, col: 4 },
{ key: 7, isGroup: true, group: 6, row: 0, col: 0 },
{ key: 8, isGroup: true, group: 6, row: 0, col: 1 },
{ key: 9, isGroup: true, group: 1, row: 1, col: 0 },
{ key: 10, isGroup: true, group: 1, row: 1, col: 1, colSpan: 3 },
{ group: 2, row: 0, col: 0 },
{ group: 2, row: 0, col: 1 },
{ group: 3, row: 0, col: 0 },
{ group: 3, row: 0, col: 1 },
{ group: 3, row: 0, col: 2 },
{ group: 4, row: 0, col: 0 },
{ group: 4, row: 0, col: 1 },
{ group: 4, row: 0, col: 2 },
{ group: 4, row: 0, col: 3 },
{ group: 5, row: 0, col: 0 },
{ group: 7, row: 0, col: 0 },
{ group: 7, row: 0, col: 1 },
{ group: 8, row: 0, col: 0 },
{ group: 8, row: 0, col: 1 },
{ group: 8, row: 0, col: 2 },
{ group: 9, row: 0, col: 0 },
],
[
{ from: 1, to: 6 }
]);
}
document.getElementById("myTestButton").addEventListener("click", e => {
if (myDiagram.layout.angle === 0) {
myDiagram.commit(d => d.layout.angle = 90);
} else {
myDiagram.commit(d => d.layout.angle = 0);
}
});
</script>
</body>
</html>