Yes, all Parts use document coordinates, so Groups and their members all use the same coordinate system, not a coordinate system that is specific for each Group.
Ignoring the problem with pretending to have group coordinates, is there a problem with either what I outlined or what you outlined? I guess I’m confused about what you want help with.
Regarding relative locations for member Nodes (relative to their containing Groups), I suppose you could look at this old sample:
<!DOCTYPE html>
<html>
<head>
<title>Basic GoJS Sample</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
function init() {
var $ = go.GraphObject.make; // for conciseness in defining templates
myDiagram =
$(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
{
// allow double-click in background to create a new node
"clickCreatingTool.archetypeNodeData": { text: "Node", color: "white" },
// allow Ctrl-G to call groupSelection()
"commandHandler.archetypeGroupData": { text: "Group", isGroup: true, color: "blue" },
"ModelChanged": function(e) {
if (e.isTransactionFinished) {
document.getElementById("savedModel").textContent = myDiagram.model.toJson();
}
},
// enable undo & redo
"undoManager.isEnabled": true
});
// Define the appearance and behavior for Nodes:
// Assume the data.loc property has a relative location for the node compared to its containing group.
// (If there is no group data, it is the location in document coordinates.)
// toLocation should not depend on any Node.location because they might not have been initialized yet.
function toLocation(data) {
var loc = go.Point.parse(data.loc);
if (data.group !== undefined) {
var groupdata = myDiagram.model.findNodeDataForKey(data.group);
if (groupdata) {
loc.add(toLocation(groupdata));
}
}
return loc;
};
// fromLocation just saves in data.loc either the absolute location if there's no containing Group,
// or the relative location with its containing Group.
function fromLocation(location, data) {
if (data.group !== undefined) {
var group = myDiagram.findNodeForKey(data.group);
if (group) {
var loc = location.copy().subtract(group.location);
data.loc = loc.x.toFixed(2) + " " + loc.y.toFixed(2); //go.Point.stringify(loc);
}
} else {
data.loc = go.Point.stringify(location);
}
};
// These nodes have text surrounded by a rounded rectangle
// whose fill color is bound to the node data.
// The user can drag a node by dragging its TextBlock label.
// Dragging from the Shape will start drawing a new link.
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "", toLocation).makeTwoWay(fromLocation),
$(go.Shape, "RoundedRectangle",
{
fill: "white", // the default fill, if there is no data-binding
portId: "", cursor: "pointer", // the Shape is the port, not the whole Node
// allow all kinds of links from and to this port
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true
},
new go.Binding("fill", "color")),
$(go.TextBlock,
{
font: "bold 14px sans-serif",
stroke: '#333',
margin: 6, // make some extra space for the shape around the text
isMultiline: false, // don't allow newlines in text
editable: true // allow in-place editing by user
},
new go.Binding("text", "text").makeTwoWay()) // the label shows the node data's text
);
// The link shape and arrowhead have their stroke brush data bound to the "color" property
myDiagram.linkTemplate =
$(go.Link,
{ relinkableFrom: true, relinkableTo: true }, // allow the user to relink existing links
$(go.Shape,
{ strokeWidth: 2 },
new go.Binding("stroke", "color")),
$(go.Shape,
{ toArrow: "Standard", stroke: null },
new go.Binding("fill", "color"))
);
// Define the appearance and behavior for Groups:
// Groups consist of a title in the color given by the group node data
// above a translucent gray rectangle surrounding the member parts
myDiagram.groupTemplate =
$(go.Group, "Vertical",
new go.Binding("location", "", toLocation).makeTwoWay(fromLocation),
{ selectionObjectName: "PANEL", // selection handle goes around shape, not label
ungroupable: true }, // enable Ctrl-Shift-G to ungroup a selected Group
$(go.TextBlock,
{
font: "bold 19px sans-serif",
isMultiline: false, // don't allow newlines in text
editable: true // allow in-place editing by user
},
new go.Binding("text", "text").makeTwoWay(),
new go.Binding("stroke", "color")),
$(go.Panel, "Auto",
{ name: "PANEL" },
$(go.Shape, "Rectangle", // the rectangular shape around the members
{ fill: "rgba(128,128,128,0.2)", stroke: "gray", strokeWidth: 3 }),
$(go.Placeholder, { padding: 10 }) // represents where the members are
)
);
// Define the behavior for the Diagram background:
// Create the Diagram's Model:
var nodeDataArray = [
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 0" },
{ key: 2, text: "Beta", color: "orange", loc: "100 0" },
{ key: 3, text: "Gamma", color: "lightgreen", group: 5, loc: "10 10" },
{ key: 4, text: "Delta", color: "pink", group: 5, loc: "100 100" },
{ key: 5, text: "Epsilon", color: "green", isGroup: true, loc: "50 100" }
];
var linkDataArray = [
{ from: 1, to: 2, color: "blue" },
{ from: 2, to: 2 },
{ from: 3, to: 4, color: "green" },
{ from: 3, to: 1, color: "purple" }
];
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
<pre id="savedModel" />
</div>
</body>
</html>