I wont save moved position of Nodes in Diagram. But a strange Problem comes. I dont know what i can do.
When i save my Diagram to JSON the position not save and nodes N3, N8,N9,N26,N27 jumps 50% higher then before. I have the Ready Source Code added.
Sorry my bad english and thanks for help.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
</head>
<body>
<script src="https://gojs.net/latest/release/go.js"></script>
<script>
function init() {
const $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
initialAutoScale: go.Diagram.Uniform,
"undoManager.isEnabled": true,
layout: $(GenogramLayout, { direction: 270, layerSpacing: 100, linkSpacing: 25, columnSpacing: 30 })
});
myDiagram.nodeTemplateMap.add("M", // male
$(go.Node, "Vertical",
new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify), // Here Bind the Position of Male Nodes
{ locationSpot: go.Spot.Center, locationObjectName: "ICON", selectionObjectName: "ICON" },
new go.Binding("opacity", "hide", h => h ? 0 : 1),
new go.Binding("pickable", "hide", h => !h),
$(go.Panel, "Auto",
{ name: "ICON" },
$(go.Shape, "Rectangle",
{ width: 100, height: 100, strokeWidth: 5, fill: "#0000ff", portId: "" }),
$(go.TextBlock, { textAlign: "center", font: "bold 20pt Arial" }, new go.Binding("text", "n")),
),
));
myDiagram.nodeTemplateMap.add("F", // female
$(go.Node, "Vertical",
new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify), // Here Bind the Position of Female Nodes
{ locationSpot: go.Spot.Center, locationObjectName: "ICON", selectionObjectName: "ICON" },
new go.Binding("opacity", "hide", h => h ? 0 : 1),
new go.Binding("pickable", "hide", h => !h),
$(go.Panel, "Auto",
{ name: "ICON" },
$(go.Shape, "Rectangle",
{ width: 100, height: 100, strokeWidth: 5, fill: "#ff0000", portId: "" }),
$(go.TextBlock, { textAlign: "center", font: "bold 20pt Arial" }, new go.Binding("text", "n")),
),
));
// the representation of each label node -- nothing shows on a Marriage Link
myDiagram.nodeTemplateMap.add("LinkLabel",
$(go.Node, { selectable: false, width: 1, height: 1, fromEndSegmentLength: 120 }));
myDiagram.linkTemplate = // for parent-child relationships
$(go.Link,
{
routing: go.Link.Orthogonal,
layerName: "Background",
},
$(go.Shape, { stroke: "#000000", strokeWidth: 4 })
);
myDiagram.linkTemplateMap.add("Marriage", // for marriage relationships
$(go.Link,
{ selectable: false, layerName: "Background" },
$(go.Shape, { strokeWidth: 4, stroke: "#000000" })
));
load();
}
function save() {
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
myDiagram.isModified = false;
}
function load() {
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
}
function findMarriage(diagram, a, b) { // A and B are node keys
const nodeA = diagram.findNodeForKey(a);
const nodeB = diagram.findNodeForKey(b);
if (nodeA !== null && nodeB !== null) {
const it = nodeA.findLinksBetween(nodeB); // in either direction
while (it.next()) {
const 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
function setupMarriages(diagram) {
const model = diagram.model;
const nodeDataArray = model.nodeDataArray;
for (let i = 0; i < nodeDataArray.length; i++) {
const data = nodeDataArray[i];
const key = data.key;
let uxs = data.ux;
if (uxs !== undefined) {
if (typeof uxs === "number") uxs = [uxs];
for (let j = 0; j < uxs.length; j++) {
const wife = uxs[j];
const wdata = model.findNodeDataForKey(wife);
if (key === wife || !wdata || wdata.s !== "F") {
console.log("cannot create Marriage relationship with self or unknown person " + wife);
continue;
}
const link = findMarriage(diagram, key, wife);
if (link === null) {
// add a label node for the marriage link
const mlab = { s: "LinkLabel" };
model.addNodeData(mlab);
// add the marriage link itself, also referring to the label node
const 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++) {
const husband = virs[j];
const hdata = model.findNodeDataForKey(husband);
if (key === husband || !hdata || hdata.s !== "M") {
console.log("cannot create Marriage relationship with self or unknown person " + husband);
continue;
}
const link = findMarriage(diagram, key, husband);
if (link === null) {
// add a label node for the marriage link
const mlab = { s: "LinkLabel" };
model.addNodeData(mlab);
// add the marriage link itself, also referring to the label node
const mdata = { from: key, to: husband, labelKeys: [mlab.key], category: "Marriage" };
model.addLinkData(mdata);
}
}
}
}
}
// process parent-child relationships once all marriages are known
function setupParents(diagram) {
const model = diagram.model;
const nodeDataArray = model.nodeDataArray;
for (let i = 0; i < nodeDataArray.length; i++) {
const data = nodeDataArray[i];
const key = data.key;
const mother = data.m;
const father = data.f;
if (mother !== undefined && father !== undefined) {
const link = findMarriage(diagram, mother, father);
if (link === null) {
// or warn no known mother or no known father or no known marriage between them
console.log("unknown marriage: " + mother + " & " + father);
continue;
}
const mdata = link.data;
if (mdata.labelKeys === undefined || mdata.labelKeys[0] === undefined) continue;
const mlabkey = mdata.labelKeys[0];
const cdata = { from: mlabkey, to: key };
myDiagram.model.addLinkData(cdata);
}
}
}
// A custom layout that shows the two families related to a person's parents
class GenogramLayout extends go.LayeredDigraphLayout {
constructor() {
super();
this.alignOption = go.LayeredDigraphLayout.AlignAll;
this.initializeOption = go.LayeredDigraphLayout.InitDepthFirstIn;
this.spouseSpacing = 30; // minimum space between spouses
}
makeNetwork(coll) {
// generate LayoutEdges for each parent-child Link
const 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;
}
// internal method for creating LayeredDigraphNetwork where husband/wife pairs are represented
// by a single LayeredDigraphVertex corresponding to the label Node on the marriage Link
add(net, coll, nonmemberonly) {
const horiz = this.direction == 0.0 || this.direction == 180.0;
const multiSpousePeople = new go.Set();
// consider all Nodes in the given collection
const it = coll.iterator;
while (it.next()) {
const 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
const link = node.labeledLink;
const spouseA = link.fromNode;
const spouseB = link.toNode;
// create vertex representing both husband and wife
const vertex = net.addNode(node);
// now define the vertex size to be big enough to hold both spouses
if (horiz) {
vertex.height = spouseA.actualBounds.height + this.spouseSpacing + spouseB.actualBounds.height;
vertex.width = Math.max(spouseA.actualBounds.width, spouseB.actualBounds.width);
vertex.focus = new go.Point(vertex.width / 2, spouseA.actualBounds.height + this.spouseSpacing / 2);
} else {
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) {
net.addNode(node);
} else if (marriages > 1) {
multiSpousePeople.add(node);
}
}
}
// now do all Links
it.reset();
while (it.next()) {
const 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) {
const parent = net.findVertex(link.fromNode); // should be a label node
const 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
const 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
const 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
const node = multiSpousePeople.first();
const 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
const dummyvert = net.createVertex();
net.addVertex(dummyvert);
const 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)
const mlab = link.labelNodes.first()
const 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);
}
}
// collect all of the people indirectly married with a person
extendCohort(coll, node) {
if (coll.has(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);
}
});
}
assignLayers() {
super.assignLayers();
const horiz = this.direction == 0.0 || this.direction == 180.0;
// for every vertex, record the maximum vertex width or height for the vertex's layer
const maxsizes = [];
this.network.vertexes.each(v => {
const lay = v.layer;
let max = maxsizes[lay];
if (max === undefined) max = 0;
const 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 => {
const lay = v.layer;
const 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).
}
initializeIndices() {
super.initializeIndices();
const vertical = this.direction === 90 || this.direction === 270;
this.network.edges.each(e => {
if (e.fromVertex.node && e.fromVertex.node.isLinkLabel) {
e.portFromPos = vertical ? e.fromVertex.focusX : e.fromVertex.focusY;
}
if (e.toVertex.node && e.toVertex.node.isLinkLabel) {
e.portToPos = vertical ? e.toVertex.focusX : e.toVertex.focusY;
}
})
}
commitNodes() {
super.commitNodes();
const horiz = this.direction == 0.0 || this.direction == 180.0;
// position the spouses of each marriage vertex
this.network.vertexes.each(v => {
if (v.node === null) return;
if (!v.node.isLinkLabel) return;
const labnode = v.node;
const 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;
if (spouseA.opacity > 0 && spouseB.opacity > 0) {
// prefer fathers on the left, mothers on the right
if (spouseA.data.s === "F") { // sex is female
const temp = spouseA;
spouseA = spouseB;
spouseB = temp;
}
// see if the parents are on the desired sides, to avoid a link crossing
const aParentsNode = this.findParentsMarriageLabelNode(spouseA);
const bParentsNode = this.findParentsMarriageLabelNode(spouseB);
if (aParentsNode !== null && bParentsNode !== null &&
(horiz
? aParentsNode.position.x > bParentsNode.position.x
: aParentsNode.position.y > bParentsNode.position.y)) {
// swap the spouses
const temp = spouseA;
spouseA = spouseB;
spouseB = temp;
}
spouseA.moveTo(v.x, v.y);
if (horiz) {
spouseB.moveTo(v.x, v.y + spouseA.actualBounds.height + this.spouseSpacing);
} else {
spouseB.moveTo(v.x + spouseA.actualBounds.width + this.spouseSpacing, v.y);
}
} else if (spouseA.opacity === 0) {
const pos = horiz
? new go.Point(v.x, v.centerY - spouseB.actualBounds.height / 2)
: new go.Point(v.centerX - spouseB.actualBounds.width / 2, v.y);
spouseB.move(pos);
if (horiz) pos.y++; else pos.x++;
spouseA.move(pos);
} else if (spouseB.opacity === 0) {
const pos = horiz
? new go.Point(v.x, v.centerY - spouseA.actualBounds.height / 2)
: new go.Point(v.centerX - spouseA.actualBounds.width / 2, v.y);
spouseA.move(pos);
if (horiz) pos.y++; else pos.x++;
spouseB.move(pos);
}
lablink.ensureBounds();
});
}
findParentsMarriageLabelNode(node) {
const it = node.findNodesInto();
while (it.next()) {
const n = it.value;
if (n.isLinkLabel) return n;
}
return null;
}
}
// end GenogramLayout class
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="myDiagramDiv" style="background-color: #F8F8F8; border: solid 1px black; width:100%; height:700px;"></div>
<button onclick="save()">Save</button>
<button onclick="load()">Load</button>
<textarea id="mySavedModel" style="width:100%;height:230px">
{ "class": "GraphLinksModel",
"copiesArrays": true,
"nodeCategoryProperty": "s",
"linkLabelKeysProperty": "labelKeys",
"nodeDataArray": [
{"key":1,"n":"N1","s":"M","m":0,"f":0,"ux":2,"vir":0},
{"key":2,"n":"N2","s":"F","m":0,"f":0,"ux":0,"vir":1},
{"key":3,"n":"N3","s":"F","m":2,"f":1,"ux":0,"vir":0},
{"key":4,"n":"N4","s":"M","m":2,"f":1,"ux":5,"vir":0},
{"key":5,"n":"N5","s":"F","m":0,"f":0,"ux":0,"vir":4},
{"key":6,"n":"N6","s":"M","m":2,"f":1,"ux":7,"vir":0},
{"key":7,"n":"N7","s":"F","m":0,"f":0,"ux":0,"vir":6},
{"key":8,"n":"N8","s":"F","m":7,"f":6,"ux":0,"vir":0},
{"key":9,"n":"N9","s":"F","m":7,"f":6,"ux":0,"vir":0},
{"key":10,"n":"N10","s":"M","m":7,"f":6,"ux":11,"vir":0},
{"key":11,"n":"N11","s":"F","m":0,"f":0,"ux":0,"vir":10},
{"key":12,"n":"N12","s":"M","m":7,"f":6,"ux":14,"vir":0},
{"key":13,"n":"N13","s":"M","m":7,"f":6,"ux":17,"vir":0},
{"key":14,"n":"N14","s":"F","m":0,"f":0,"ux":0,"vir":12},
{"key":15,"n":"N15","s":"M","m":7,"f":6,"ux":16,"vir":0},
{"key":16,"n":"N16","s":"F","m":0,"f":0,"ux":0,"vir":15},
{"key":17,"n":"N17","s":"F","m":0,"f":0,"ux":0,"vir":13},
{"key":18,"n":"N18","s":"M","m":7,"f":6,"ux":19,"vir":0},
{"key":19,"n":"N19","s":"F","m":0,"f":0,"ux":0,"vir":18},
{"key":20,"n":"N20","s":"F","m":0,"f":0,"ux":0,"vir":21},
{"key":21,"n":"N21","s":"M","m":14,"f":12,"ux":20,"vir":0},
{"key":22,"n":"N22","s":"F","m":0,"f":0,"ux":0,"vir":23},
{"key":23,"n":"N23","s":"M","m":20,"f":21,"ux":22,"vir":0},
{"key":24,"n":"N24","s":"M","m":22,"f":23,"ux":25,"vir":0},
{"key":25,"n":"N25","s":"F","m":0,"f":0,"ux":0,"vir":24},
{"key":26,"n":"N26","s":"M","m":22,"f":23,"ux":0,"vir":0},
{"key":27,"n":"N27","s":"F","m":22,"f":23,"ux":0,"vir":0},
{"key":28,"n":"N28","s":"F","m":0,"f":0,"ux":0,"vir":29},
{"key":29,"n":"N29","s":"M","m":23,"f":24,"ux":28,"vir":0},
{"key":30,"n":"N30","s":"M","m":0,"f":0,"ux":31,"vir":0},
{"key":31,"n":"N31","s":"F","m":25,"f":24,"ux":0,"vir":30},
{"key":32,"n":"N32","s":"M","m":0,"f":0,"ux":34,"vir":0},
{"key":33,"n":"N33","s":"M","m":25,"f":24,"ux":36,"vir":0},
{"key":34,"n":"N34","s":"F","m":28,"f":29,"ux":0,"vir":32},
{"key":35,"n":"N35","s":"M","m":0,"f":0,"ux":39,"vir":0},
{"key":36,"n":"N36","s":"F","m":0,"f":0,"ux":0,"vir":33},
{"key":37,"n":"N37","s":"M","m":28,"f":29,"ux":38,"vir":0},
{"key":38,"n":"N38","s":"F","m":0,"f":0,"ux":0,"vir":37},
{"key":39,"n":"N39","s":"F","m":28,"f":29,"ux":0,"vir":35},
{"s":"LinkLabel","key":-40},
{"s":"LinkLabel","key":-41},
{"s":"LinkLabel","key":-42},
{"s":"LinkLabel","key":-43},
{"s":"LinkLabel","key":-44},
{"s":"LinkLabel","key":-45},
{"s":"LinkLabel","key":-46},
{"s":"LinkLabel","key":-47},
{"s":"LinkLabel","key":-48},
{"s":"LinkLabel","key":-49},
{"s":"LinkLabel","key":-50},
{"s":"LinkLabel","key":-51},
{"s":"LinkLabel","key":-52},
{"s":"LinkLabel","key":-53},
{"s":"LinkLabel","key":-54},
{"s":"LinkLabel","key":-55},
{"s":"LinkLabel","key":-56}
],
"linkDataArray": [
{"from":1,"to":2,"labelKeys":[-40],"category":"Marriage"},
{"from":4,"to":5,"labelKeys":[-41],"category":"Marriage"},
{"from":6,"to":7,"labelKeys":[-42],"category":"Marriage"},
{"from":10,"to":11,"labelKeys":[-43],"category":"Marriage"},
{"from":12,"to":14,"labelKeys":[-44],"category":"Marriage"},
{"from":13,"to":17,"labelKeys":[-45],"category":"Marriage"},
{"from":15,"to":16,"labelKeys":[-46],"category":"Marriage"},
{"from":18,"to":19,"labelKeys":[-47],"category":"Marriage"},
{"from":20,"to":21,"labelKeys":[-48],"category":"Marriage"},
{"from":22,"to":23,"labelKeys":[-49],"category":"Marriage"},
{"from":24,"to":25,"labelKeys":[-50],"category":"Marriage"},
{"from":28,"to":29,"labelKeys":[-51],"category":"Marriage"},
{"from":30,"to":31,"labelKeys":[-52],"category":"Marriage"},
{"from":32,"to":34,"labelKeys":[-53],"category":"Marriage"},
{"from":33,"to":36,"labelKeys":[-54],"category":"Marriage"},
{"from":35,"to":39,"labelKeys":[-55],"category":"Marriage"},
{"from":37,"to":38,"labelKeys":[-56],"category":"Marriage"},
{"from":-40,"to":3},
{"from":-40,"to":4},
{"from":-40,"to":6},
{"from":-42,"to":8},
{"from":-42,"to":9},
{"from":-42,"to":10},
{"from":-42,"to":12},
{"from":-42,"to":13},
{"from":-42,"to":15},
{"from":-42,"to":18},
{"from":-44,"to":21},
{"from":-48,"to":23},
{"from":-49,"to":24},
{"from":-49,"to":26},
{"from":-49,"to":27},
{"from":-50,"to":31},
{"from":-50,"to":33},
{"from":-51,"to":34},
{"from":-51,"to":37},
{"from":-51,"to":39}
]}
</textarea>
</body>
</html>