OK, I started from the already-modified sample, genogramTwins.html.
First, I set GraphObject.fromLinkable and GraphObject.toLinkable on the ports of the “M” and “F” node templates:
{ width: 40, height: 40, strokeWidth: 2, fill: "white", portId: "", fromLinkable: true, toLinkable: true }),
Second I added templates both for the new label nodes on parent links and the new links connecting twins. Note that these new links connect not the children themselves but their respective links from their shared parent marriage label node, which itself is a label node on “Marriage” links connecting the birth mother and the birth father.
// the representation of each label node connecting twins -- nothing shows on a parent Link
myDiagram.nodeTemplateMap.add("TwinLinkLabel",
$(go.Node, { segmentIndex: -2, segmentFraction: 0.5, selectable: false, width: 1, height: 1, isLayoutPositioned: false }));
myDiagram.linkTemplateMap.add("TwinLink", // for connecting twins/triplets
$(go.Link, { selectable: false, isTreeLink: false, isLayoutPositioned: false },
$(go.Shape, { strokeWidth: 2, stroke: "blue" })
));
Also I declared that “Marriage” links are not part of a tree relationship:
myDiagram.linkTemplateMap.add("Marriage", // for marriage relationships
$(go.Link, { selectable: false, isTreeLink: false },
$(go.Shape, { strokeWidth: 2, stroke: "darkgreen" })
));
That is so that we can call the “tree” methods on the Node class while ignoring all “Marriage” links.
Third, I defined a new LinkingTool that is customized to only draw new links between “twins”.
function TwinDrawingTool() {
go.LinkingTool.call(this);
}
go.Diagram.inherit(TwinDrawingTool, go.LinkingTool);
TwinDrawingTool.prototype.findLinkablePort = function() {
var diagram = this.diagram;
if (diagram === null) return null;
var obj = this.startObject;
if (obj === null) {
obj = diagram.findObjectAt(diagram.firstInput.documentPoint, null, null);
}
if (obj === null) return null;
var node = obj.part;
if (!(node instanceof Node)) return null;
// require the node to have parents and siblings
var parentMarriageNode = node.findTreeParentNode();
if (parentMarriageNode === null) return null;
if (parentMarriageNode.findTreeChildrenLinks().count <= 1) return null;
return node.port;
};
TwinDrawingTool.prototype.isValidTo = function(tonode, toport) {
if (tonode === null || toport === null) return false;
if (tonode.data.birth !== undefined) return false; // already a twin?
if (tonode === this.originalFromNode) return false; // not to itself!
// require both the fromnode and the tonode to have the same marriage (label) parent node
return this.originalFromNode.findTreeParentNode() === tonode.findTreeParentNode();
};
TwinDrawingTool.prototype.insertLink = function(fromnode, fromport, tonode, toport) {
var diagram = this.diagram;
if (diagram === null) return null;
var fromParentLink = fromnode.findTreeParentLink();
var toParentLink = tonode.findTreeParentLink();
// assert
if (fromParentLink === null || toParentLink === null || fromParentLink.fromNode !== toParentLink.fromNode) {
throw new Error("this should never happen");
}
var commonParent = fromParentLink.fromNode;
var model = diagram.model;
// make sure the parent link of the fromnode has a label node; create it if needed
var fromParentLabelNode = fromParentLink.labelNodes.first();
var fromLabelData = null;
if (fromParentLabelNode === null) {
fromLabelData = { };
model.setCategoryForNodeData(fromLabelData, "TwinLinkLabel");
model.addNodeData(fromLabelData); // assigns key
model.addLabelKeyForLinkData(fromParentLink.data, model.getKeyForNodeData(fromLabelData));
} else {
fromLabelData = fromParentLabelNode.data;
}
// make sure the parent link of the tonode has a label node; create it if needed
var toParentLabelNode = toParentLink.labelNodes.first();
var toLabelData = null;
if (toParentLabelNode === null) {
toLabelData = { };
model.setCategoryForNodeData(toLabelData, "TwinLinkLabel");
model.addNodeData(toLabelData); // assigns key
model.addLabelKeyForLinkData(toParentLink.data, model.getKeyForNodeData(toLabelData));
} else {
toLabelData = toParentLabelNode.data;
}
// now that both label nodes exist, add a TwinLink between them
var twinLinkData = {};
model.setCategoryForLinkData(twinLinkData, "TwinLink");
model.setFromKeyForLinkData(twinLinkData, model.getKeyForNodeData(fromLabelData));
model.setToKeyForLinkData(twinLinkData, model.getKeyForNodeData(toLabelData));
model.addLinkData(twinLinkData);
// make sure "birth" is set on both nodes
if (fromnode.data.birth === undefined) {
// how many sets of twins/triplets does this marriage have already?
var maxBirth = 0;
commonParent.findTreeChildrenNodes().each(function(n) {
if (n.data.birth !== undefined) {
maxBirth = Math.max(maxBirth, n.data.birth);
}
})
model.setDataProperty(fromnode.data, "birth", maxBirth + 1);
}
model.setDataProperty(tonode.data, "birth", fromnode.data.birth);
return diagram.findLinkForData(twinLinkData);
};
Install this tool by replacing the standard ToolManager.linkingTool when initializing the Diagram:
linkingTool: new TwinDrawingTool(),