Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <html>
  <head>
    <title>GoJS Example</title>
    <script src="https://gojs.net/latest/release/go.js"></script>
  </head>
  <body>
    <div id="pedigree-container"></div>
  </body>
</html>
  
              
            
!

CSS

              
                
              
            
!

JS

              
                'use strict';
var myDiagram;
var $ = go.GraphObject.make;
go.Diagram.inherit(GenogramLayout, go.LayeredDigraphLayout);


GenogramLayout.prototype.makeNetwork = function(coll) {
    // generate LayoutEdges for each parent-child Link
    var 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
GenogramLayout.prototype.add = function(net, coll, nonmemberonly) {
    // consider all Nodes in the given collection
    var it = coll.iterator;
    while (it.next()) {
        var 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
            var link = node.labeledLink;
            var spouseA = link.fromNode;
            var spouseB = link.toNode;
            // create vertex representing both husband and wife
            var vertex = net.addNode(node);
            // now define the vertex size to be big enough to hold both spouses
            vertex.width = spouseA.actualBounds.width + 30 + spouseB.actualBounds.width;
            vertex.height = Math.max(spouseA.actualBounds.height, spouseB.actualBounds.height);
            vertex.focus = new go.Point(spouseA.actualBounds.width + 30 / 2, vertex.height / 2);
        } else {
            var anymarriage = false;
            var mit = node.linksConnected;
            while (mit.next()) {
                var link = mit.value;
                if (link.isLabeledLink) { // assume a marriage Link has a label Node
                    anymarriage = true;
                    break;
                }
            }
            // don't add a vertex for any married person!
            // instead, code above adds label node for marriage link
            if (!anymarriage) {
                var vertex = net.addNode(node);
            }
        }
    }
    // now do all Links
    it.reset();
    while (it.next()) {
        var 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) {
            var parent = net.findVertex(link.fromNode); // should be a label node
            var child = net.findVertex(link.toNode);
            if (child !== null) { // an unmarried child
                net.linkVertexes(parent, child, link);
            } else { // a married child
                var mit = link.toNode.linksConnected;
                while (mit.next()) {
                    var l = mit.value;
                    if (l.data.category !== "Marriage") continue;
                    // found the Marriage Link, now get its label Node
                    var 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
                    var mlabvert = net.findVertex(mlab);
                    if (mlabvert !== null) {
                        net.linkVertexes(parent, mlabvert, link);
                    }
                }
            }
        }
    }
};

GenogramLayout.prototype.assignLayers = function() {
    go.LayeredDigraphLayout.prototype.assignLayers.call(this);
    var horiz = this.direction == 0.0 || this.direction == 180.0;
    // for every vertex, record the maximum vertex width or height for the vertex's layer
    var maxsizes = [];
    for (var it = this.network.vertexes.iterator; it.next();) {
        var v = it.value;
        var lay = v.layer;
        var max = maxsizes[lay];
        if (max === undefined) max = 0;
        var 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)
    for (it = this.network.vertexes.iterator; it.next();) {
        var v = it.value;
        var lay = v.layer;
        var 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).
};

GenogramLayout.prototype.commitNodes = function() {
    go.LayeredDigraphLayout.prototype.commitNodes.call(this);
    // position regular nodes
    var it = this.network.vertexes.iterator;
    while (it.next()) {
        var v = it.value;
        if (v.node === null) continue;
        if (v.node.isLinkLabel) continue;
        v.node.position = new go.Point(v.x, v.y);
    }
    // position the spouses of each marriage vertex
    it.reset();
    while (it.next()) {
        var v = it.value;
        if (v.node === null) continue;
        if (!v.node.isLinkLabel) continue;
        var labnode = v.node;
        var lablink = labnode.labeledLink;
        var spouseA = lablink.fromNode;
        var spouseB = lablink.toNode;
        // see if the parents are on the desired sides, to avoid a link crossing
        var aParentsNode = this.findParentsMarriageLabelNode(spouseA);
        var bParentsNode = this.findParentsMarriageLabelNode(spouseB);
        if (aParentsNode !== null && bParentsNode !== null && aParentsNode.position.x > bParentsNode.position.x) {
            // swap the spouses
            spouseA = lablink.toNode;
            spouseB = lablink.fromNode;
        }
        spouseA.position = new go.Point(v.x, v.y);
        spouseB.position = new go.Point(v.x + spouseA.actualBounds.width + 30, v.y);
        if (spouseA.opacity === 0) {
            var pos = new go.Point(v.centerX - spouseA.actualBounds.width / 2, v.y);
            spouseA.position = pos;
            spouseB.position = pos;
        } else if (spouseB.opacity === 0) {
            var pos = new go.Point(v.centerX - spouseB.actualBounds.width / 2, v.y);
            spouseA.position = pos;
            spouseB.position = pos;
        }
    }
};

GenogramLayout.prototype.findParentsMarriageLabelNode = function(node) {
    var it = node.findNodesInto();
    while (it.next()) {
        var n = it.value;
        if (n.isLinkLabel) return n;
    }
    return null;
};

function goBuildPedigree(pedigreeData, noDisplayNodes, selectedPersonId) {
    if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
    
    myDiagram =
        $(go.Diagram, "myDiagram", {
            initialAutoScale: go.Diagram.Uniform,
            initialContentAlignment: go.Spot.Center,
            // when a node is selected, draw a big yellow circle behind it
            nodeSelectionAdornmentTemplate: $(go.Adornment, "Auto", {
                    layerName: "Grid"
                }, // the predefined layer that is behind everything else
                $(go.Shape, "RoundedRectangle", {
                    fill: "transparent",
                    stroke: '#009ADD',
                    strokeWidth: 2,
                    strokeDashArray: [5, 5]
                }),
                $(go.Placeholder)
            )
        });

    function attrFill(a) {

        switch (a) {
            case "Breast":
                return "#FF99FF";
            case "Cervical":
                return "#226F86";
            case "Triple Negative Breast":
                return "#FF99FF";
            case "Colon":
                return "#333300";
            case "Melanoma":
                return "#000000";
            case "Uterine":
                return "#FFCCCC";
            case "Endometrial":
                return "#FFCCCC";
            case "Biliary Tract":
                return "#0000CC";
            case "Bladder":
                return "#FFFF33";
            case "Brain":
                return "#999999";
            case "Esophogus":
                return "transparent";
            case "Gastric":
                return "#6666FF";
            case "Kidney":
                return "#FF9900";
            case "Leukemia":
                return "#E48241";
            case "Liver and Bile Duct":
                return "#1D8141";
            case "Non-Hodgkin Lymphoma":
                return "transparent";
            case "Oral Cavity and Pharynx":
                return "transparent";
            case "Other":
                return "#FFFF99";
            case "Ovarian":
                return "#33FFFF";
            case "Pancreatic":
                return "#9900CC";
            case "Prostate":
                return "#99CCFF";
            case "Rectal":
                return "#660000";
            case "Lung":
                return "#FFFFFF";
            case "Small Bowel":
                return "#660000";
            case "Stomach":
                return "#6666FF";
            case "Thyroid":
                return "transparent";
            case "Ureter\/Renal Pelvis":
                return "#FFFF33";
            case "Urinary Tract":
                return "#FFFF33";
            case "Skin":
                return "#0033FF";
            case "Skin cancer - non-melanoma":
                return "#FFFFFF";
            case "Hodgkin's Lymphoma":
                return "#FFFFFF";
            default:
                return "transparent";
        }
    }

    var tlpt = new go.Point(1, 1);
    var trpt = new go.Point(20, 1);
    var brpt = new go.Point(20, 20);
    var blpt = new go.Point(1, 20);

    function malePosition(a) {
        switch (a) {
            case "Breast":
                return tlpt;
            case "Triple Negative Breast":
                return tlpt;
            case "Cervical":
                return tlpt;
            case "Colon":
                return trpt;
            case "Melanoma":
                return brpt;
            case "Uterine":
                return blpt;
            case "Endometrial":
                return blpt;
            case "Biliary Tract":
                return trpt;
            case "Non-Hodgkin Lymphoma":
                return trpt;
            case "Oral Cavity and Pharynx":
                return trpt;
            case "Lung":
                return trpt;
            case "Bladder":
                return brpt;
            case "Thyroid":
                return brpt;
            case "Brain":
                return trpt;
            case "Gastric":
                return trpt;
            case "Kidney":
                return trpt;
            case "Leukemia":
                return blpt;
            case "Liver and Bile Duct":
                return trpt;
            case "Other":
                return brpt;
            case "Ovarian":
                return trpt;
            case "Pancreatic":
                return blpt;
            case "Prostate":
                return blpt;
            case "Rectal":
                return trpt;
            case "Small Bowel":
                return trpt;
            case "Stomach":
                return brpt;
            case "Ureter\/Renal Pelvis":
                return trpt;
            case "Urinary Tract":
                return trpt;
            case "Skin":
                return brpt;
            case "Skin cancer - non-melanoma":
                return brpt;
            case "Hodgkin's Lymphoma":
                return brpt;
            default:
                tbrpt;
        }
    }

    var tlarc = go.Geometry.parse("M20 20 B 180 90 20 20 19 19 z", true);
    var trarc = go.Geometry.parse("M20 20 B 270 90 20 20 19 19 z", true);
    var brarc = go.Geometry.parse("M20 20 B 0 90 20 20 19 19 z", true);
    var blarc = go.Geometry.parse("M20 20 B 90 90 20 20 19 19 z", true);

    function femaleGeometry(a) {
        switch (a) {
            case "Breast":
                return tlarc;
            case "Triple Negative Breast":
                return tlarc;
            case "Colon":
                return trarc;
            case "Cervical":
                return tlarc;
            case "Non-Hodgkin Lymphoma":
                return trarc;
            case "Oral Cavity and Pharynx":
                return trarc;
            case "Melanoma":
                return brarc;
            case "Uterine":
                return blarc;
            case "Thyroid":
                return blarc;
            case "Endometrial":
                return blarc;
            case "Biliary Tract":
                return trarc;
            case "Bladder":
                return brarc;
            case "Brain":
                return trarc;
            case "Lung":
                return trarc;
            case "Gastric":
                return trarc;
            case "Kidney":
                return trarc;
            case "Leukemia":
                return blarc;
            case "Liver and Bile Duct":
                return trarc;
            case "Other":
                return brarc;
            case "Ovarian":
                return trarc;
            case "Pancreatic":
                return blarc;
            case "Prostate":
                return blarc;
            case "Rectal":
                return trarc;
            case "Small Bowel":
                return trarc;
            case "Stomach":
                return brarc;
            case "Ureter\/Renal Pelvis":
                return trarc;
            case "Urinary Tract":
                return trarc;
            case "Skin":
                return brarc;
            case "Skin cancer - non-melanoma":
                return brarc;
            case "Hodgkin's Lymphoma":
                return brarc;
            default:
                tbrpt;
        }
    }

    //lets create our decease shapes

    // two different node templates, one for each sex,
    // named by the category value in the node data object
    myDiagram.nodeTemplateMap.add("M", // male
        $(go.Node, "Vertical", {
                locationSpot: go.Spot.Center,
                locationObjectName: "ICON",
                movable:false
            },
            $(go.Panel, {
                    name: "ICON"
                },
                $(go.Shape, "Rectangle", {
                    width: 40,
                    height: 40,
                    strokeWidth: 2,
                    fill: "white",
                    portId: ""
                }),
                $(go.Panel, {
                        itemTemplate: $(go.Panel,
                            $(go.Shape, "Rectangle", {
                                    width: 19,
                                    height: 19,
                                    stroke: null,
                                    strokeWidth: 0
                                },
                                new go.Binding("fill", "", attrFill),
                                new go.Binding("position", "", malePosition))
                        ),
                        margin: 1
                    },
                    new go.Binding("itemArray", "a")
                )
            ),
            $(go.TextBlock, {
                    textAlign: "center",
                    maxSize: new go.Size(80, NaN)
                },
                new go.Binding("text", "n"))
        ));
    // two different node templates, one for each sex,
    // named by the category value in the node data object
    myDiagram.nodeTemplateMap.add("U", // unknown
        $(go.Node, "Vertical", {
                locationSpot: go.Spot.Center,
                locationObjectName: "ICON",
                movable:false
            },
            $(go.Panel, {
                    name: "ICON"
                },
                $(go.Shape, "Triangle", {
                    width: 40,
                    height: 40,
                    strokeWidth: 2,
                    fill: "white",
                    portId: ""
                }),
                $(go.Panel, {
                        itemTemplate: $(go.Panel,
                            $(go.Shape, "Rectangle", {
                                    width: 19,
                                    height: 19,
                                    stroke: null,
                                    strokeWidth: 0
                                },
                                new go.Binding("fill", "", attrFill),
                                new go.Binding("position", "", malePosition))
                        ),
                        margin: 1
                    },
                    new go.Binding("itemArray", "a")
                )
            ),
            $(go.TextBlock, {
                    textAlign: "center",
                    maxSize: new go.Size(80, NaN)
                },
                new go.Binding("text", "n"))
        ));

    myDiagram.nodeTemplateMap.add("MD", // male
        $(go.Node, "Vertical", {
                locationSpot: go.Spot.Center,
                locationObjectName: "ICON",
                movable:false
            },
            $(go.Panel, {
                    name: "ICON"
                },
                $(go.Shape, "Rectangle", {
                    width: 40,
                    height: 40,
                    strokeWidth: 2,
                    fill: "white",
                    portId: ""
                }),
                $(go.Shape, "Line2", {
                    width: 40,
                    height: 40,
                    stroke: "red",
                    strokeWidth: 2
                }),
                $(go.Panel, {
                        itemTemplate: $(go.Panel,
                            $(go.Shape, "Rectangle", {
                                    width: 19,
                                    height: 19,
                                    stroke: null,
                                    strokeWidth: 0
                                },
                                new go.Binding("fill", "", attrFill),
                                new go.Binding("position", "", malePosition)
                            )
                        ),
                        margin: 1
                    },
                    new go.Binding("itemArray", "a")
                )
            ),
            $(go.TextBlock, {
                    textAlign: "center",
                    maxSize: new go.Size(80, NaN)
                },
                new go.Binding("text", "n"))
        ));

    myDiagram.nodeTemplateMap.add("F", // female
        $(go.Node, "Vertical", {
                locationSpot: go.Spot.Center,
                locationObjectName: "ICON",
                movable:false
            },
            $(go.Panel, {
                    name: "ICON"
                },
                $(go.Shape, "Ellipse", {
                    width: 40,
                    height: 40,
                    strokeWidth: 2,
                    fill: "white",
                    portId: ""
                }),
                $(go.Panel, {
                        itemTemplate: $(go.Panel,
                            $(go.Shape, {
                                    stroke: null,
                                    strokeWidth: 0
                                },
                                new go.Binding("fill", "", attrFill),
                                new go.Binding("geometry", "", femaleGeometry))
                        ),
                        margin: 1

                    },
                    new go.Binding("itemArray", "a")
                )
            ),
            $(go.TextBlock, {
                    textAlign: "center",
                    maxSize: new go.Size(80, NaN)
                },
                new go.Binding("text", "n"))
        ));

    myDiagram.nodeTemplateMap.add("FD", // female
        $(go.Node, "Vertical", {
                locationSpot: go.Spot.Center,
                locationObjectName: "ICON",
                movable:false
            },
            $(go.Panel, {
                    name: "ICON"
                },
                $(go.Shape, "Ellipse", {
                    width: 40,
                    height: 40,
                    strokeWidth: 2,
                    fill: "white",
                    portId: ""
                }),
                $(go.Shape, "Line2", {
                    width: 40,
                    height: 40,
                    stroke: "red",
                    strokeWidth: 2
                }),
                $(go.Panel, {
                        itemTemplate: $(go.Panel,
                            $(go.Shape, {
                                    stroke: null,
                                    strokeWidth: 0
                                },
                                new go.Binding("fill", "", attrFill),
                                new go.Binding("geometry", "", femaleGeometry))
                        ),
                        margin: 1
                    },
                    new go.Binding("itemArray", "a")
                )
            ),
            $(go.TextBlock, {
                    textAlign: "center",
                    maxSize: new go.Size(80, NaN)
                },
                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: 20
        }));


    myDiagram.linkTemplateMap.add("Marriage", // for marriage relationships
        $(go.Link, {
                selectable: false
            },
            $(go.Shape, {
                strokeWidth: 2,
                stroke: "black"
            })
        ));

    myDiagram.linkTemplate = // for parent-child relationships
        $(go.Link, {
                routing: go.Link.Orthogonal,
                layerName: "Background",
                curviness : 10,
                selectable: false,
                fromSpot: go.Spot.Bottom,
                toSpot: go.Spot.Top
            },
            $(go.Shape, {strokeWidth : 2},
                new go.Binding("strokeDashArray", "dash")
            )
        );

    myDiagram.layout =
        $(GenogramLayout, {
            direction: 90,
            layerSpacing: 30,
            columnSpacing: 10
        });
    // n: name, s: sex, m: mother, f: father, ux: wife, vir: husband, a: attributes/markers
    setupDiagram(myDiagram, pedigreeData, noDisplayNodes, selectedPersonId /* focus on this person */ );

    return myDiagram;
}


// create and initialize the Diagram.model given an array of node data representing people
function setupDiagram(diagram, array, focusIds, selectedNodeId) {

    diagram.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
        });

    setupMarriages(diagram);
    setupParents(diagram);
    for (var i in focusIds) {
        var node = diagram.findNodeForKey(focusIds[i]);
        if (node !== null) {
            diagram.select(node);
            var it = node.linksConnected;
            while (it.next()) {
                var l = it.value;
                if (!l.isLabeledLink) continue;
                l.opacity = 0;
                var spouse = l.getOtherNode(node);
                spouse.opacity = 0;
                spouse.pickable = false;
            }
        }
    }


    var node = diagram.findNodeForKey(selectedNodeId);
    diagram.select(node);
}

function findMarriage(diagram, a, b) {
    var nodeA = diagram.findNodeForKey(a);
    var nodeB = diagram.findNodeForKey(b);
    if (nodeA !== null && nodeB !== null) {
        var it = nodeA.findLinksBetween(nodeB); // in either direction
        while (it.next()) {
            var 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) {
    var model = diagram.model;
    var nodeDataArray = model.nodeDataArray;
    for (var i = 0; i < nodeDataArray.length; i++) {
        var data = nodeDataArray[i];
        var key = data.key;
        var uxs = data.ux;
        if (uxs !== undefined) {
            if (typeof uxs === "number") uxs = [uxs];
            for (var j = 0; j < uxs.length; j++) {
                var wife = uxs[j];
                if (key === wife) {
                    // or warn no reflexive marriages
                    continue;
                }
                var link = findMarriage(diagram, key, wife);
                if (link === null) {
                    // add a label node for the marriage link
                    var mlab = {
                        s: "LinkLabel"
                    };
                    model.addNodeData(mlab);
                    // add the marriage link itself, also referring to the label node
                    var mdata = {
                        from: key,
                        to: wife,
                        labelKeys: [mlab.key],
                        category: "Marriage"
                    };
                    model.addLinkData(mdata);
                }
            }
        }
        var virs = data.vir;
        if (virs !== undefined) {
            if (typeof virs === "number") virs = [virs];
            for (var j = 0; j < virs.length; j++) {
                var husband = virs[j];
                if (key === husband) {
                    // or warn no reflexive marriages
                    continue;
                }
                var link = findMarriage(diagram, key, husband);
                if (link === null) {
                    // add a label node for the marriage link
                    var mlab = {
                        s: "LinkLabel"
                    };
                    model.addNodeData(mlab);
                    // add the marriage link itself, also referring to the label node
                    var 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) {
    var model = diagram.model;
    var nodeDataArray = model.nodeDataArray;
    for (var i = 0; i < nodeDataArray.length; i++) {
        var data = nodeDataArray[i];
        var key = data.key;
        var mother = data.m;
        var father = data.f;
        if (mother !== undefined && father !== undefined) {
            var link = 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;
            }
            var mdata = link.data;
            var mlabkey = mdata.labelKeys[0];
            var cdata = {
                from: mlabkey,
                to: key
            };
            myDiagram.model.addLinkData(cdata);
        }
    }
}


function GenogramLayout() {
    go.LayeredDigraphLayout.call(this);
}
// end GenogramLayout class
function buildPedigree(data, selectedPersonId) {
    
    var formattedPedigree = formatPedigree(data);

    var diagram = goBuildPedigree(formattedPedigree.pedigreeData, formattedPedigree.noDisplay, selectedPersonId);

    diagram.addDiagramListener("InitialLayoutCompleted", function() {
      var node = diagram.findNodeForKey(1);
      diagram.add($(go.Part,
            {
              position: node.getDocumentPoint(new go.Spot(.05, .25)).offset(-30, 0),
              pickable: false, selectable: false
            },
            $(go.Shape, "Arrow", { width: 30, height: 15, angle: 330 })
          ));
      });

    //make an image of the 
    var img = diagram.makeImage();
    addImage(img);
    return diagram;

}

function formatPedigree(pedigree) {
    var d = [];
    //first we must loop through pedigree and determine husbands and wives (ux, vir)
    for (var i in pedigree) {

        var father_key = parseInt(getPersonByID(pedigree, pedigree[i].father_id));
        var mother_key = parseInt(getPersonByID(pedigree, pedigree[i].mother_id));

        if (father_key !== -1 && mother_key !== -1) {
            pedigree[father_key].ux = parseInt(pedigree[i].mother_id);
            pedigree[mother_key].vir = parseInt(pedigree[i].father_id);

            // var father = getPersonByProperty(pedigree[i].father_id, "father_id", pedigree);
            // father.ux = pedigree[i].mother_id;
        }
        // if(typeof pedigree[pedigree[i].mother_id + 1] !== "undefined" && pedigree[pedigree[i].father_id + 1].father_id !== 0)
        //   pedigree[pedigree[i].mother_id + 1].vir = pedigree[i].father_id;
    }
    var fakePersonCounter = 0;
    var probandSpouse = 100000;
    var noDisplayNodes = [1];
    for (var i in pedigree) {
       
        var pName = pedigree[i].first_name;

        if (typeof(pedigree[i].last_name) !== "undefined" && pedigree[i].last_name !== null)
            pName += " " + pedigree[i].last_name;

        var person = {
            key: parseInt(pedigree[i].person_id),
            n: pName,
            s: pedigree[i].gender,
            dash : null,
            width : 2
        };
        if (pedigree[i].is_living === 0 && pedigree[i].person_id !== 1){
            person.s += "D";
        }

        if(pedigree[i].adopted === 'true'){
            person.dash = [5,5];
        }
        //console.log(pedigree[i].relationship_type);
        if (pedigree[i].mother_id != 0) {
            person.m = parseInt(pedigree[i].mother_id);
        } else if (pedigree[i].relationship_type == "SON" || pedigree[i].relationship_type == "DAUGHTER") {
            person.m = probandSpouse;
        }


        if (pedigree[i].father_id != 0)
            person.f = parseInt(pedigree[i].father_id);
        else if (pedigree[i].relationship_type == "SON" || pedigree[i].relationship_type == "DAUGHTER") {
            person.f = probandSpouse;
        }

        if (typeof pedigree[i].vir !== "undefined")
            person.vir = parseInt(pedigree[i].vir);

        if (typeof pedigree[i].ux !== "undefined")
            person.ux = parseInt(pedigree[i].ux);

        person.a = [];
        for (var j in pedigree[i].cancer_history) {
            person.a.push(pedigree[i].cancer_history[j].name);
            if (pedigree[i].cancer_history[j].name)
                person.n += "\n" + pedigree[i].cancer_history[j].name;
            if (pedigree[i].cancer_history[j].age_of_diagnosis)
                person.n += " (" + pedigree[i].cancer_history[j].age_of_diagnosis + ")";
        }
        if (pedigree[i].person_id >= fakePersonCounter) {
            fakePersonCounter = parseInt(pedigree[i].person_id) + 1;
        }

        //d[parseInt(pedigree[i].person_id) - 1] = person;
        d.push(person);
    }


    //now we need to loop through nieces and nephews to establish correct relationships

    for (var i in pedigree) {
        var p = pedigree[i];
        if (pedigree[i].relationship_type == "NEPHEW" || pedigree[i].relationship_type == "NIECE" || pedigree[i].relationship_type == "MALE_COUSIN" || pedigree[i].relationship_type == "FEMALE_COUSIN") {
            //if the persons mother does not exist create the mother and assign relationships
            if (p.mother_id == 0) {
                fakePersonCounter++;
                var person = {
                    key: parseInt(fakePersonCounter),
                    n: "",
                    s: "F",
                    vir: parseInt(p.father_id)
                };
                d.push(person);
                noDisplayNodes.push(parseInt(p.father_id));

                //pedigree[i].mother_id = fakePersonCounter;
                //now assign the mother_id and any other siblings
                //now we must iterate through the pedigree where the father_id's match
                for (var j in d) {
                    var p2 = d[j];
                    if (p2.f == p.father_id) {
                        d[j].m = fakePersonCounter;
                    }
                    if (p2.key == p.father_id) {
                        d[j].ux = fakePersonCounter;
                    }
                }
                for (var k in pedigree) {
                    if (pedigree[k].father_id == pedigree[i].father_id)
                        pedigree[k].mother_id = fakePersonCounter;
                }
            }
            if (p.father_id == 0) {
                //console.log("Fahter ID = " + p.father_id);
                fakePersonCounter++;
                var person = {
                    key: parseInt(fakePersonCounter),
                    n: "",
                    s: "M",
                    ux: parseInt(p.mother_id)
                }
                d.push(person);
                noDisplayNodes.push(parseInt(p.mother_id));

                //now assign the mother_id and any other siblings
                //now we must iterate through the pedigree where the father_id's match
                for (var j in d) {
                    var p2 = d[j];
                    if (p2.m == p.mother_id) {
                        d[j].f = fakePersonCounter;
                    }
                    if (p2.key == p.mother_id) {
                        d[j].vir = fakePersonCounter;
                    }
                }
                for (var k in pedigree) {
                    if (pedigree[k].mother_id == pedigree[i].mother_id)
                        pedigree[k].father_id = fakePersonCounter;
                }

            }
        }
    }

    d.push({
        key: probandSpouse,
        n: "",
        s: (d[0].s == "M") ? "F" : "M"
    });


    d[0].ux = probandSpouse;
    return {
        pedigreeData: d,
        noDisplay: noDisplayNodes
    };
}

function isInt(n) {
    return typeof n === 'number' && n % 1 == 0;
}

function getPersonByID(pedigree, id) {
    for (var i in pedigree) {
        if (pedigree[i].person_id == id) {
            return i; //return the property
        }
    }
    return -1;
}
window.addImage = function(img) {
    var obj = document.getElementById("imagePedigree");
    img.className = "images";
    obj.appendChild(img);
}

var windowObjectReference = null; // global variable

function openRequestedPopup(strUrl, strWindowName) {
    if (windowObjectReference == null || windowObjectReference.closed) {
        windowObjectReference = window.open(strUrl, strWindowName,
            "resizable,scrollbars,status");
    } else {
        windowObjectReference.focus();
    };
}
function getPedigreeData() {
  return [{
    "person_id": 1,
    "father_id": 2,
    "mother_id": 3,
    "first_name": "Janalee Adams",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "proband",
    "age": 29,
    "dob": "05/30/1984",
    "adopted": "true",
    "ancestry": {
      "id": 3,
      "name": "Ashkenazi",
      "is_ashkenazi": true
    }
  }, {
    "person_id": 2,
    "father_id": 4,
    "mother_id": 5,
    "first_name": "Father",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "FATHER",
    "ux": 3
  }, {
    "person_id": 3,
    "father_id": 6,
    "mother_id": 7,
    "first_name": "Mother",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "MOTHER",
    "vir": 2
  }, {
    "person_id": 4,
    "father_id": 0,
    "mother_id": 0,
    "first_name": "Paternal Grandfather",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "PATERNAL_GRANDFATHER",
    "ux": 5
  }, {
    "person_id": 5,
    "father_id": 0,
    "mother_id": 0,
    "first_name": "Paternal Grandmother",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "PATERNAL_GRANDMOTHER",
    "vir": 4
  }, {
    "person_id": 6,
    "father_id": 0,
    "mother_id": 0,
    "first_name": "Maternal Grandfather",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "MATERNAL_GRANDFATHER",
    "ux": 7
  }, {
    "person_id": 7,
    "father_id": 0,
    "mother_id": 0,
    "first_name": "Maternal Grandmother",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "MATERNAL_GRANDMOTHER",
    "vir": 6
  }, {
    "person_id": 8,
    "father_id": 2,
    "mother_id": 3,
    "first_name": "Brother 1",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "BROTHER"
  }, {
    "person_id": 9,
    "father_id": 2,
    "mother_id": 3,
    "first_name": "Sister 1",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "SISTER"
  }, {
    "person_id": 10,
    "father_id": 0,
    "mother_id": 1,
    "first_name": "Son 1",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "SON"
  }, {
    "person_id": 11,
    "father_id": 0,
    "mother_id": 1,
    "first_name": "Son 2",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "SON"
  }, {
    "person_id": 12,
    "father_id": 0,
    "mother_id": 1,
    "first_name": "Son 3",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "SON"
  }, {
    "person_id": 13,
    "father_id": 0,
    "mother_id": 1,
    "first_name": "Daughter 1",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "ashkenazi": "",
    "side": "na",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "DAUGHTER"
  }, {
    "person_id": 14,
    "father_id": 4,
    "mother_id": 5,
    "first_name": "Uncle 1",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "FATHERS",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "PATERNAL_UNCLE"
  }, {
    "person_id": 15,
    "father_id": 4,
    "mother_id": 5,
    "first_name": "Uncle 2",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "FATHERS",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "PATERNAL_UNCLE"
  }, {
    "person_id": 16,
    "father_id": 4,
    "mother_id": 5,
    "first_name": "Uncle 3",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "FATHERS",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "PATERNAL_UNCLE"
  }, {
    "person_id": 17,
    "father_id": 6,
    "mother_id": 7,
    "first_name": "Uncle 1",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "MOTHERS",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "MATERNAL_UNCLE"
  }, {
    "person_id": 18,
    "father_id": 6,
    "mother_id": 7,
    "first_name": "Uncle 2",
    "last_name": "",
    "gender": "M",
    "is_living": "1",
    "ashkenazi": "",
    "side": "MOTHERS",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "MATERNAL_UNCLE"
  }, {
    "person_id": 19,
    "father_id": 6,
    "mother_id": 7,
    "first_name": "Aunt 1",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "ashkenazi": "",
    "side": "MOTHERS",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "MATERNAL_AUNT"
  }, {
    "person_id": 20,
    "father_id": 6,
    "mother_id": 7,
    "first_name": "Aunt 2",
    "last_name": "",
    "gender": "F",
    "is_living": "1",
    "ashkenazi": "",
    "side": "MOTHERS",
    "has_cancer": 0,
    "has_mutation": 0,
    "cancer_history": [],
    "gene_mutations": [],
    "relationship_type": "MATERNAL_AUNT"
  }];
}
function init() {
  document.getElementById("pedigree-container").innerHTML = '';
  document.getElementById("pedigree-container").innerHTML = '<div id="myDiagram" style="height:400px;"></div>';
  var diagram = buildPedigree(getPedigreeData(), 1);
  console.log(diagram);
  diagram.addDiagramListener("ObjectSingleClicked",
                function(e, t) {
                    var mousePt = $scope.diagram.lastInput.viewPoint;
                    var part = e.subject.part;
                    if (!(part instanceof go.Link)) {
                        $scope.key = e.subject.part.ul.key;
                        $scope.selectedPersonId = $scope.findIndexWithAttr($scope.pedigree, 'person_id',$scope.key);
                        $scope.selectedPerson = $scope.pedigree[$scope.selectedPersonId];
                        $scope.$apply();
                    }
                }
            );
            diagram.addDiagramListener("ObjectDoubleClicked",
                function(e, t) {
                    var mousePt = $scope.diagram.lastInput.viewPoint;
                    var part = e.subject.part;
                    if (!(part instanceof go.Link)) {
                        $scope.key = e.subject.part.ul.key;
                        console.log($scope.key);
                    }
                }
            );
}
init();
              
            
!
999px

Console