Hi, I am trying to use the genogram sample in typescript but I can’t get the same output.
layout.model
import * as go from ‘gojs’;
export class Layout extends go.LayeredDigraphLayout {
spouseSpacing = 30;
initializeOption = Layout.InitDepthFirstIn;
/** @override */
add(net, coll, nonmemberonly) {
let multiSpousePeople = new go.Set();
// consider all Nodes in the given collection
let it = coll.iterator;
while (it.next()) {
let node = it.value;
if (!(node instanceof go.Node)) continue;
if (!node.isLayoutPositioned || !node.isVisible()) continue;
if (nonmemberonly && node.containingGroup !== null) continue;
// if it's an unmarried Node, or if it's a Link Label Node, create a LayoutVertex for it
if (node.isLinkLabel) {
// get marriage Link
let link = node.labeledLink;
let spouseA = link.fromNode;
let spouseB = link.toNode;
// create vertex representing both husband and wife
let vertex = net.addNode(node);
// now define the vertex size to be big enough to hold both spouses
vertex.width = spouseA.actualBounds.width + this.spouseSpacing + spouseB.actualBounds.width;
vertex.height = Math.max(spouseA.actualBounds.height, spouseB.actualBounds.height);
vertex.focus = new go.Point(spouseA.actualBounds.width + this.spouseSpacing / 2, vertex.height / 2);
} else {
// don't add a vertex for any married person!
// instead, code above adds label node for marriage link
// assume a marriage Link has a label Node
let marriages = 0;
node.linksConnected.each((l) => { if (l.isLabeledLink) marriages++; });
if (marriages === 0) {
let vertex = net.addNode(node);
} else if (marriages > 1) {
multiSpousePeople.add(node);
}
}
}
// now do all Links
it.reset();
while (it.next()) {
let link = it.value;
if (!(link instanceof go.Link)) continue;
if (!link.isLayoutPositioned || !link.isVisible()) continue;
if (nonmemberonly && link.containingGroup !== null) continue;
// if it's a parent-child link, add a LayoutEdge for it
if (!link.isLabeledLink) {
let parent = net.findVertex(link.fromNode); // should be a label node
let child = net.findVertex(link.toNode);
if (child !== null) { // an unmarried child
net.linkVertexes(parent, child, link);
} else { // a married child
link.toNode.linksConnected.each((l) => {
if (!l.isLabeledLink) return; // if it has no label node, it's a parent-child link
// found the Marriage Link, now get its label Node
let mlab = l.labelNodes.first();
// parent-child link should connect with the label node,
// so the LayoutEdge should connect with the LayoutVertex representing the label node
let mlabvert = net.findVertex(mlab);
if (mlabvert !== null) {
net.linkVertexes(parent, mlabvert, link);
}
});
}
}
}
while (multiSpousePeople.count > 0) {
// find all collections of people that are indirectly married to each other
let node = multiSpousePeople.first();
let cohort = new go.Set();
this.extendCohort(cohort, node);
// then encourage them all to be the same generation by connecting them all with a common vertex
let dummyvert = net.createVertex();
net.addVertex(dummyvert);
let marriages = new go.Set();
cohort.each((n) => {
n['linksConnected'].each((l) => {
marriages.add(l);
})
});
marriages.each((link) => {
// find the vertex for the marriage link (i.e. for the label node)
let mlab = link['labelNodes'].first()
let v = net.findVertex(mlab);
if (v !== null) {
net.linkVertexes(dummyvert, v, null);
}
});
// done with these people, now see if there are any other multiple-married people
multiSpousePeople.removeAll(cohort);
}
};
/** @override */
assignLayers() {
let horiz = this.direction == 0.0 || this.direction == 180.0;
// for every vertex, record the maximum vertex width or height for the vertex's layer
let maxsizes = [];
this.network.vertexes.each((v) => {
let lay = v['layer'];
let max = maxsizes[lay];
if (max === undefined) max = 0;
let sz = (horiz ? v.width : v.height);
if (sz > max) maxsizes[lay] = sz;
});
// now make sure every vertex has the maximum width or height according to which layer it is in,
// and aligned on the left (if horizontal) or the top (if vertical)
this.network.vertexes.each((v) => {
let lay = v['layer'];
let max = maxsizes[lay];
if (horiz) {
v.focus = new go.Point(0, v.height / 2);
v.width = max;
} else {
v.focus = new go.Point(v.width / 2, 0);
v.height = max;
}
});
// from now on, the LayeredDigraphLayout will think that the Node is bigger than it really is
// (other than the ones that are the widest or tallest in their respective layer).
};
extendCohort(coll, node) {
if (coll.contains(node)) return;
coll.add(node);
node.linksConnected.each((l) => {
if (l.isLabeledLink) { // if it's a marriage link, continue with both spouses
this.extendCohort(coll, l.fromNode);
this.extendCohort(coll, l.toNode);
}
});
};
/** @override */
commitNodes() {
// position regular nodes
this.network.vertexes.each((v) => {
if (v.node !== null && !v.node.isLinkLabel) {
v.node.position = new go.Point(v.x, v.y);
}
});
// position the spouses of each marriage vertex
let layout = this;
this.network.vertexes.each((v) => {
if (v.node === null) return;
if (!v.node.isLinkLabel) return;
let labnode = v.node;
let lablink = labnode.labeledLink;
// In case the spouses are not actually moved, we need to have the marriage link
// position the label node, because LayoutVertex.commit() was called above on these vertexes.
// Alternatively we could override LayoutVetex.commit to be a no-op for label node vertexes.
lablink.invalidateRoute();
let spouseA = lablink.fromNode;
let spouseB = lablink.toNode;
// prefer fathers on the left, mothers on the right
if (spouseA.data.s === "F") { // sex is female
let temp = spouseA;
spouseA = spouseB;
spouseB = temp;
}
// see if the parents are on the desired sides, to avoid a link crossing
let aParentsNode = layout.findParentsMarriageLabelNode(spouseA);
let bParentsNode = layout.findParentsMarriageLabelNode(spouseB);
if (aParentsNode !== null && bParentsNode !== null && aParentsNode.position.x > bParentsNode.position.x) {
// swap the spouses
let temp = spouseA;
spouseA = spouseB;
spouseB = temp;
}
spouseA.position = new go.Point(v.x, v.y);
spouseB.position = new go.Point(v.x + spouseA.actualBounds.width + layout.spouseSpacing, v.y);
if (spouseA.opacity === 0) {
let pos = new go.Point(v.centerX - spouseA.actualBounds.width / 2, v.y);
spouseA.position = pos;
spouseB.position = pos;
} else if (spouseB.opacity === 0) {
let pos = new go.Point(v.centerX - spouseB.actualBounds.width / 2, v.y);
spouseA.position = pos;
spouseB.position = pos;
}
});
// position only-child nodes to be under the marriage label node
this.network.vertexes.each((v) => {
if (v.node === null || v.node.linksConnected.count > 1) return;
let mnode = layout.findParentsMarriageLabelNode(v.node);
if (mnode !== null && mnode.linksConnected.count === 1) { // if only one child
let mvert = layout.network.findVertex(mnode);
let newbnds = v.node.actualBounds.copy();
newbnds.x = mvert.centerX - v.node.actualBounds.width / 2;
// see if there's any empty space at the horizontal mid-point in that layer
let overlaps = layout.diagram.findObjectsIn(newbnds, (x) => { return x.part; }, (p) => { return p !== v.node; }, true);
if (overlaps.count === 0) {
v.node.move(newbnds.position);
}
}
});
};
findParentsMarriageLabelNode(node) {
let it = node.findNodesInto();
while (it.next()) {
let n = it.value;
if (n.isLinkLabel) return n;
}
return null;
};
/** @override */
makeNetwork(coll) {
// generate LayoutEdges for each parent-child Link
let net = this.createNetwork();
if (coll instanceof go.Diagram) {
this.add(net, coll.nodes, true);
this.add(net, coll.links, true);
} else if (coll instanceof go.Group) {
this.add(net, coll.memberParts, false);
} else if (coll.iterator) {
this.add(net, coll.iterator, false);
}
return net;
};
}
diagram.model
import * as go from 'gojs';
import { Layout } from './layout.model';
go.Diagram.inherit(Layout, go.LayeredDigraphLayout);
export class Diagram extends go.Diagram {
goMake: any;
// create and initialize the Diagram.model given an array of node data representing people
init(array, focusId) {
this.goMake = go.GraphObject.make;
this.layout = this.goMake(Layout, { direction: 90, layerSpacing: 30, columnSpacing: 10 });
this.initialAutoScale = go.Diagram.Uniform;
this.initialContentAlignment= go.Spot.Center;
this.undoManager.isEnabled = true;
this.maleTemplate();
this.femaleTemplate();
this.linkLabel();
this.marriage();
this.parentChild();
this.model =
go.GraphObject.make(go.GraphLinksModel,
{ // declare support for link label nodes
linkLabelKeysProperty: "labelKeys",
// this property determines which template is used
nodeCategoryProperty: "s",
// create all of the nodes for people
nodeDataArray: array
});
this.setupMarriages(this);
this.setupParents(this);
var node = this.findNodeForKey(focusId);
if (node !== null) {
this.select(node);
// remove any spouse for the person under focus:
//node.linksConnected.each(function(l) {
// if (!l.isLabeledLink) return;
// l.opacity = 0;
// var spouse = l.getOtherNode(node);
// spouse.opacity = 0;
// spouse.pickable = false;
//});
}
}
// two different node templates, one for each sex,
// named by the category value in the node data object
maleTemplate(){
this.nodeTemplateMap.add("M", // male
this.goMake(go.Node, "Vertical",
{ locationSpot: go.Spot.Center, locationObjectName: "ICON" },
this.goMake(go.Panel,
{ name: "ICON" },
this.goMake(go.Shape, "Square",
{ width: 40, height: 40, strokeWidth: 2, fill: "white", portId: "" })
),
this.goMake(go.TextBlock,
{ textAlign: "center", maxSize: new go.Size(80, NaN) },
new go.Binding("text", "n"))
));
}
femaleTemplate(){
this.nodeTemplateMap.add("F", // female
this.goMake(go.Node, "Vertical",
{ locationSpot: go.Spot.Center, locationObjectName: "ICON" },
this.goMake(go.Panel,
{ name: "ICON" },
this.goMake(go.Shape, "Circle",
{ width: 40, height: 40, strokeWidth: 2, fill: "white", portId: "" })
),
this.goMake(go.TextBlock,
{ textAlign: "center", maxSize: new go.Size(80, NaN) },
new go.Binding("text", "n"))
));
}
linkLabel(){
// the representation of each label node -- nothing shows on a Marriage Link
this.nodeTemplateMap.add("LinkLabel",
this.goMake(go.Node, { selectable: false, width: 1, height: 1, fromEndSegmentLength: 20 }));
}
parentChild(){
this.linkTemplate = // for parent-child relationships
this.goMake(go.Link,
{
routing: go.Link.Orthogonal, curviness: 15,
layerName: "Background", selectable: false,
fromSpot: go.Spot.Bottom, toSpot: go.Spot.Top
},
this.goMake(go.Shape, { strokeWidth: 2 })
);
}
marriage(){
this.linkTemplateMap.add("Marriage", // for marriage relationships
this.goMake(go.Link,
{ selectable: false },
this.goMake(go.Shape, { strokeWidth: 2, stroke: "blue" })
));
}
findMarriage(diagram, a, b) { // A and B are node keys
let nodeA = diagram.findNodeForKey(a);
let nodeB = diagram.findNodeForKey(b);
if (nodeA !== null && nodeB !== null) {
let it = nodeA.findLinksBetween(nodeB); // in either direction
while (it.next()) {
let link = it.value;
// Link.data.category === "Marriage" means it's a marriage relationship
if (link.data !== null && link.data.category === "Marriage") return link;
}
}
return null;
}
// now process the node data to determine marriages
setupMarriages(diagram) {
let model = diagram.model;
let nodeDataArray = model.nodeDataArray;
for (let i = 0; i < nodeDataArray.length; i++) {
let data = nodeDataArray[i];
let key = data.key;
let uxs = data.ux;
if (uxs !== undefined) {
if (typeof uxs === "number") uxs = [ uxs ];
for (let j = 0; j < uxs.length; j++) {
let wife = uxs[j];
if (key === wife) {
// or warn no reflexive marriages
continue;
}
let link = this.findMarriage(diagram, key, wife);
if (link === null) {
// add a label node for the marriage link
let mlab = { s: "LinkLabel" };
model.addNodeData(mlab);
// add the marriage link itself, also referring to the label node
let mdata = { from: key, to: wife, labelKeys: [mlab['key']], category: "Marriage" };
model.addLinkData(mdata);
}
}
}
let virs = data.vir;
if (virs !== undefined) {
if (typeof virs === "number") virs = [ virs ];
for (let j = 0; j < virs.length; j++) {
let husband = virs[j];
if (key === husband) {
// or warn no reflexive marriages
continue;
}
let link = this.findMarriage(diagram, key, husband);
if (link === null) {
// add a label node for the marriage link
let mlab = { s: "LinkLabel" };
model.addNodeData(mlab);
// add the marriage link itself, also referring to the label node
let mdata = { from: key, to: husband, labelKeys: [mlab['key']], category: "Marriage" };
model.addLinkData(mdata);
}
}
}
}
}
setupParents(diagram) {
let model = diagram.model;
let nodeDataArray = model.nodeDataArray;
for (let i = 0; i < nodeDataArray.length; i++) {
let data = nodeDataArray[i];
let key = data.key;
let mother = data.m;
let father = data.f;
if (mother !== undefined && father !== undefined) {
let link = this.findMarriage(diagram, mother, father);
if (link === null) {
// or warn no known mother or no known father or no known marriage between them
if (window.console) window.console.log("unknown marriage: " + mother + " & " + father);
continue;
}
let mdata = link.data;
let mlabkey = mdata.labelKeys[0];
let cdata = { from: mlabkey, to: key };
this.model['addLinkData'](cdata);
}
}
}
}
Can someone point me in the right direction?
Thanks.