Family Tree with pure HTML and CSS (or with minimal JS)

Viewed 42181

I'm trying to build a family tree with HTML and CSS. I found a good example on codepen.

As a family is not a simple node hierarchy, but sometimes rather complex relations, I need to have multiple nodes that almost act as one. But let's start with not the most complex example, taking my family as an example, starting with my grand mother from the mother side as the root:

  • There is my father who is the ex-husband of my mother
  • My mother married again, thus I have a step father

So the base node from above is my mother, but me and my sister should be placed below my father as we are not related to the step father. I tried to picture this here: family tree

Here is my markup and css (based on the codepen example above):

/* Person */
.person {
 border: 1px solid black;
 padding: 10px;
 min-width: 150px;
 background-color: #FFFFFF;
 display: inline-block;
}

.person.female {
 border-color: #F45B69;
}

.person.male {
 border-color: #456990;
}

.person div {
 text-align: center;
}

.person .name {
 font-size: 16px;
}

.person .parentDrop, .person .spouseDrop, .person .childDrop {
 border: 1px dashed #000000;
 width: auto;
 min-width: 80px;
 min-height: 80px;
 display: inline-block;
 vertical-align: top;
 position: relative;
 padding-top: 15px;
}

.person .parentDrop>span,
.person .spouseDrop>span,
.person .childDrop>span {
 position: absolute;
 top: 2px;
 left: 2px;
 font-weight: bold;
}
.parentDrop>.person,
.spouseDrop>.person,
.childDrop>.person {
 margin-top: 20px;
}

/* Tree */
.tree ul {
 padding-top: 20px;
 position: relative;
 transition: all 0.5s;
 -webkit-transition: all 0.5s;
 -moz-transition: all 0.5s;
}

.tree li {
 display: table-cell;
 text-align: center;
 list-style-type: none;
 position: relative;
 padding: 20px 5px 0 5px;
 transition: all 0.5s;
 -webkit-transition: all 0.5s;
 -moz-transition: all 0.5s;
}



/*We will use ::before and ::after to draw the connectors*/
.tree li::before, .tree li::after {
 content: '';
 position: absolute;
 top: 0;
 right: 50%;
 border-top: 1px solid #ccc;
 width: 50%;
 height: 20px;
}

.tree li::after {
 right: auto;
 left: 50%;
 border-left: 1px solid #ccc;
}

/*We need to remove left-right connectors from elements without 
any siblings*/
.tree li:only-child::after, .tree li:only-child::before {
 display: none;
}

/*Remove space from the top of single children*/
.tree li:only-child {
 padding-top: 0;
}

/*Remove left connector from first child and 
right connector from last child*/
.tree li:first-child::before, .tree li:last-child::after {
 border: 0 none;
}
/*Adding back the vertical connector to the last nodes*/
.tree li:last-child::before {
 border-right: 1px solid #ccc;
 border-radius: 0 5px 0 0;
 -webkit-border-radius: 0 5px 0 0;
 -moz-border-radius: 0 5px 0 0;
}

.tree li:first-child::after {
 border-radius: 5px 0 0 0;
 -webkit-border-radius: 5px 0 0 0;
 -moz-border-radius: 5px 0 0 0;
}

/*Time to add downward connectors from parents*/
.tree ul ul::before {
 content: '';
 position: absolute;
 top: 0;
 left: 50%;
 border-left: 1px solid #ccc;
 width: 0;
 height: 20px;
}

.tree li .parent {
 transition: all 0.5s;
 -webkit-transition: all 0.5s;
 -moz-transition: all 0.5s;
 margin-top: 10px;
}
.tree li .parent::before {
    content: '';
    position: absolute;
    top: 40px;
    left: 50%;
    border-left: 1px solid #ccc;
    border-right: 1px solid #ccc;
    width: 3px;
    height: 10px;
}
.tree li .family {
 position: relative;
}
.tree li .family .spouse {
 position: absolute;
 top: 0;
 left: 50%;
    margin-left: 95px;
}
.tree li .family .spouse::before {
    content: '';
    position: absolute;
    top: 50%;
    left: -10px;
    border-top: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
    width: 10px;
    height: 3px;
}

/*Time for some hover effects*/
/*We will apply the hover effect the the lineage of the element also*/
.tree li .child:hover,
.tree li .child:hover+.parent .person,
.tree li .parent .person:hover,
.tree li .child:hover+.parent .person+ul li .child,
.tree li .parent .person:hover+ul li .child,
.tree li .child:hover+.parent .person+ul li .parent .person,
.tree li .parent .person:hover+ul li .parent .person {
 background: #c8e4f8;
 color: #000;
 border: 1px solid #94a0b4;
}
/*Connector styles on hover*/
.tree li .child:hover+.parent::before,
.tree li .child:hover+.parent .person+ul li::after,
.tree li .parent .person:hover+ul li::after,
.tree li .child:hover+.parent .person+ul li::before,
.tree li .parent .person:hover+ul li::before,
.tree li .child:hover+.parent .person+ul::before,
.tree li .parent .person:hover+ul::before,
.tree li .child:hover+.parent .person+ul ul::before,
.tree li .parent .person:hover+ul ul::before {
 border-color: #94a0b4;
}
<div class="tree">
<ul>
<li>
 <div class="family">
  <div class="person child male">
   <div class="name">Grandfather</div>
  </div>
    <div class="parent">
      <div class="person female">
        <div class="name">Grandmother</div>
      </div>
      <ul>
        <li>
          <div class="family" style="width: 172px">
            <div class="person child male">
              <div class="name">Uncle</div>
            </div>
            <div class="parent">
              <div class="person female">
                <div class="name">Wife of Uncle</div>
              </div>
            </div>
          </div>
        </li>
        <li>
          <div class="family" style="width: 172px">
            <div class="person child female">
              <div class="name">Aunt</div>
            </div>
            <div class="parent">
              <div class="person male">
                <div class="name">Husband of Aunt</div>
              </div>
            </div>
          </div>
        </li>
        <li>
          <div class="family" style="width: 344px">
            <div class="person child female">
              <div class="name">Mother</div>
            </div>
            <div class="parent">
              <div class="person male">
                <div class="name">Father</div>
              </div>
              <ul>
                <li>
                  <div class="person child male">
                    <div class="name">Me</div>
                  </div>
                </li>
                <li>
                  <div class="person child female">
                    <div class="name">Sister</div>
                  </div>
                </li>
              </ul>
            </div>
            <div class="person spouse male">
              <div class="name">Spouse</div>
            </div>
          </div>
        </li>
      </ul>
    </div>
 </div>
</li>
</ul>
</div>

EDIT: I found a solution, whereas I calculate the nodes shown next to each other in the backend and write some style attributes with widths to a newly introduced div. It is still not perfect, so if someone has improvements, post a comment or answer.

5 Answers

I would suggest you to use some third party js family tree library

For Example OrgChart JS

Family tree algorithm could be very complex this is why it is easier if multiple nodes act as one, as you said.

The only thing is that you have to learn how to implement your own template in OrgChart JS.

Here is an example with British Royal Family Tree:

enter image description here

window.onload = function () {
    OrgChart.templates.family_template_11 = Object.assign({}, OrgChart.templates.ana);
    OrgChart.templates.family_template_11.size = [200, 140];
    OrgChart.templates.family_template_11.plus = "";
    OrgChart.templates.family_template_11.minus = "";
    OrgChart.templates.family_template_11.node = '';
    OrgChart.templates.family_template_11.rippleRadius = 45;
    OrgChart.templates.family_template_11.name_1 = '<text class="name_1" style="font-size: 12px;" fill="#000000" x="100" y="105" text-anchor="middle">{val}</text>';
    OrgChart.templates.family_template_11.name_2 = '<text class="name_2" style="font-size: 12px;" fill="#000000" x="235" y="105" text-anchor="middle">{val}</text>';
    OrgChart.templates.family_template_11.name_3 = '<text class="name_3" style="font-size: 12px;" fill="#000000" x="370" y="105" text-anchor="middle">{val}</text>';
    OrgChart.templates.family_template_11.title_1 = '<text class="title_1" style="font-size: 12px;" fill="#aeaeae" x="100" y="120" text-anchor="middle">{val}</text>';
    OrgChart.templates.family_template_11.title_2 = '<text class="title_2" style="font-size: 12px;" fill="#aeaeae" x="235" y="120" text-anchor="middle">{val}</text>';
    OrgChart.templates.family_template_11.title_3 = '<text class="title_3" style="font-size: 12px;" fill="#aeaeae" x="370" y="120" text-anchor="middle">{val}</text>';
    OrgChart.templates.family_template_11.img_0 = '<clipPath id="{randId}"><circle cx="100" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#aeaeae" cx="100" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="60" y="5"  width="80" height="80"></image>';
    OrgChart.templates.family_template_11.linkAdjuster =
        {
            fromX: 0,
            fromY: 0,
            toX: 0,
            toY: 0
        };


    OrgChart.templates.family_template_12 = Object.assign({}, OrgChart.templates.family_template_11);
    OrgChart.templates.family_template_12.img_0 = '<clipPath id="{randId}"><circle cx="100" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#039BE5" cx="100" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="60" y="5"  width="80" height="80"></image>';
    OrgChart.templates.family_template_12.linkAdjuster =
        {
            fromX: 0,
            fromY: 0,
            toX: 0,
            toY: -95
        };



    OrgChart.templates.family_template_21 = Object.assign({}, OrgChart.templates.family_template_11);
    OrgChart.templates.family_template_21.size = [335, 140];
    OrgChart.templates.family_template_21.node = '<line x1="145" x2="190" y1="45" y2="45" stroke-width="1" stroke="#000000"></line>';
    OrgChart.templates.family_template_21.img_1 = '<clipPath id="{randId}"><circle cx="235" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#aeaeae" cx="235" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="195" y="5"  width="80" height="80"></image>';
    OrgChart.templates.family_template_21.linkAdjuster =
        {
            fromX: 65,
            fromY: 0,
            toX: 0,
            toY: -95
        };

    OrgChart.templates.family_template_22 = Object.assign({}, OrgChart.templates.family_template_21);
    OrgChart.templates.family_template_22.linkAdjuster =
        {
            fromX: -70,
            fromY: 0,
            toX: 65,
            toY: -95
        };

    OrgChart.templates.family_template_23 = Object.assign({}, OrgChart.templates.family_template_21);
    OrgChart.templates.family_template_23.img_1 = '<clipPath id="{randId}"><circle cx="235" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#039BE5" cx="235" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="195" y="5"  width="80" height="80"></image>';
    OrgChart.templates.family_template_23.linkAdjuster =
        {
            fromX: 65,
            fromY: 0,
            toX: 65,
            toY: -95
        };

    OrgChart.templates.family_template_24 = Object.assign({}, OrgChart.templates.family_template_21);
    OrgChart.templates.family_template_24.img_0 = '<clipPath id="{randId}"><circle cx="100" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#039BE5" cx="100" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="60" y="5"  width="80" height="80"></image>';


    OrgChart.templates.family_template_25 = Object.assign({}, OrgChart.templates.family_template_21);
    OrgChart.templates.family_template_25.img_1 = '<clipPath id="{randId}"><circle cx="235" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#039BE5" cx="235" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="195" y="5"  width="80" height="80"></image>';




    OrgChart.templates.family_template_31 = Object.assign({}, OrgChart.templates.family_template_21);
    OrgChart.templates.family_template_31.size = [470, 140];
    OrgChart.templates.family_template_31.node = '<line x1="145" x2="190" y1="45" y2="45" stroke-width="1" stroke="#000000"></line><line x1="280" x2="325" y1="45" y2="45" stroke-width="1" stroke="#F57C00"></line>';
    OrgChart.templates.family_template_31.img_1 = '<clipPath id="{randId}"><circle cx="235" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#039BE5" cx="235" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="195" y="5"  width="80" height="80"></image>';

    OrgChart.templates.family_template_31.img_2 = '<clipPath id="{randId}"><circle cx="370" cy="45" r="40"></circle></clipPath><circle stroke-width="3" fill="none" stroke="#aeaeae" cx="370" cy="45" r="45"></circle><image preserveAspectRatio="xMidYMid slice" clip-path="url(#{randId})" xlink:href="{val}" x="330" y="5"  width="80" height="80"></image>';
    OrgChart.templates.family_template_31.linkAdjuster =
        {
            fromX: 0,
            fromY: 0,
            toX: 0,
            toY: -95
        };

    var chart = new OrgChart(document.getElementById("tree"), {
        tags: {
            "family_template_11": {
                template: "family_template_11"
            },
            "family_template_21": {
                template: "family_template_21"
            },
            "family_template_31": {
                template: "family_template_31"
            },
            "family_template_22": {
                template: "family_template_22"
            },
            "family_template_23": {
                template: "family_template_23"
            },
            "family_template_24": {
                template: "family_template_24"
            },
            "family_template_25": {
                template: "family_template_25"
            },
            "family_template_12": {
                template: "family_template_12"
            }
        },
        enableSearch: false,
        nodeMouseClickBehaviour: BALKANGraph.action.none,
        mouseScroolBehaviour: BALKANGraph.action.zoom,
        scaleInitial: BALKANGraph.match.boundary,
        nodeBinding: {
            name_1: "name1",
            name_2: "name2",
            name_3: "name3",
            title_1: "title1",
            title_2: "title2",
            title_3: "title3",
            img_0: "img0",
            img_1: "img1",
            img_2: "img2"
        },
        links: [
            { from: "2", to: "1" },
            { from: "3", to: "1" },
            { from: "4", to: "2" },
            { from: "5", to: "2" },
            { from: "6", to: "2" },
            { from: "7", to: "2" },
            { from: "8", to: "4" },
            { from: "9", to: "4" },
            { from: "10", to: "8" },
            { from: "11", to: "8" },
            { from: "12", to: "8" },
        ],
        nodes: [
            { id: "1", tags: ["family_template_24"], name1: "King George VI", name2: "Queen Elizabeth,", title2: "The Queen Mother", img0: "https://balkangraph.com/js/img/f1.png", img1: "https://balkangraph.com/js/img/f2.png" },
            { id: "2", tags: ["family_template_25"], name1: "Prince Philip", name2: "Queen Elizabeth II", title1: "Duke of Edinburgh", img0: "https://balkangraph.com/js/img/f3.png", img1: "https://balkangraph.com/js/img/f5.png" },
            { id: "3", tags: ["family_template_11"], name1: "Princess Margaret", img0: "https://balkangraph.com/js/img/f6.png" },
            { id: "4", tags: ["family_template_31"], name1: "Camila,", name2: "Charles,", name3: "Diana,", title1: "Duchess of Cornwall", title2: "Prince of Wales", title3: "Princess of Wales", img0: "https://balkangraph.com/js/img/f7.png", img1: "https://balkangraph.com/js/img/f8.png", img2: "https://balkangraph.com/js/img/f9.png" },
            { id: "5", tags: ["family_template_11"], name1: "Anne", title1: "Princess Royal", img0: "https://balkangraph.com/js/img/f10.png" },
            { id: "6", tags: ["family_template_11"], name1: "Prince Andrew", title1: "Duke of York", img0: "https://balkangraph.com/js/img/f11.png" },
            { id: "7", tags: ["family_template_11"], name1: "Prince Edward", title1: "Earl of Wessex", img0: "https://balkangraph.com/js/img/f12.png" },
            { id: "8", tags: ["family_template_23"], name1: "Catherine,", name2: "Prince William", title1: "Duchess of Cambridge", title2: "Duch of Cambridge", img0: "https://balkangraph.com/js/img/f13.png", img1: "https://balkangraph.com/js/img/f14.png" },
            { id: "9", tags: ["family_template_22"], name1: "Prince Harry", name2: "Meghan Markle", img0: "https://balkangraph.com/js/img/f15.png", img1: "https://balkangraph.com/js/img/f16.png" },
            { id: "10", tags: ["family_template_12"], name1: "Prince George of Cambridge", img0: "https://balkangraph.com/js/img/f17.png" },
            { id: "11", tags: ["family_template_12"], name1: "Prince Charlotte of Cambridge", img0: "https://balkangraph.com/js/img/f18.png" },
            { id: "12", tags: ["family_template_12"], name1: "Prince Louis of Cambridge", img0: "https://balkangraph.com/js/img/f19.png" }
        ]
    });
};
html, body {
    margin: 0px;
    padding: 0px;
    width: 100%;
    height: 100%;
    font-family: Helvetica;
    overflow: hidden;
}

#tree {
    width: 100%;
    height: 100%;
}
<script src="https://balkangraph.com/js/latest/OrgChart.js"></script>

<div id="tree"></div>

Have you thought of using SVG, or SVG+HTML, in conjunction with your CSS? The tool described at SVG Family-Tree Generator is merely a designer that spits out a configurable combination of HTML, SVG, JavaScript, and CSS. Its visual tree has facilities for showing tentative parentage, unshown children (to indicate there are others, but irrelevant to the publication the tree is embedded within), sequential marriages, and CSS customisation of the joining lines. There are zero-script configurations, and all the output is non-obfuscated (allowing editing or learning).

If you want to avoid script then you may find SVG to be something to avoid also. Although it is hugely powerful, and even allows interaction of the user with the box elements, some sites don't like it; notably WordPress where you might be better converting the SVG to a PNG or similar.

TLDR: Don't use OrgChartJS - use their new FamilyTreeJS (Beta) https://balkan.app/FamilyTreeJS

Im trying to do the exact same thing and I stumbled upon the Balkan OrgChartJS, and I spent a few hours trying to get it to work but realized it didn't. They don't advertise it yet, but they forked it and made it specifically for family trees. I've been banging away at it for a few days and have it getting close to what I want. Check it out!

Here is something to get you started, I have to leave it at this now.

.tree {
  display:flex;
   width:100%;
  justify-content:center;
  flex-direction:column;
}

.tree > div {
  display:flex;
  justify-content:center;
  width:auto;
/*   background:indianred; */
  align-self:center;
}

.tree div > div {
  
  margin:1em;
}

.spouse::before {
  content:" ";
  position:absolute;
  margin-top:1.5em;
  margin-left:-2.1em;
  width:2em;
  height:1px;
  align-self:center;
  border-top:2px solid purple;
}

.paternal::after {
  content:" ";
  position:absolute;
  margin-top:2px;
  margin-left:3em;
  width:1px;
  height:2em;
  align-self:center;
  border-left:2px solid purple;
}

.person {
  border:2px solid pink;
}

.person {
  border:2px solid pink;
}
<div class="tree">
 <div class="generationone">
  <div class="person male grandparent">
   <div class="name">Grandfather</div>
  </div>
     <div class="person spouse female grandparent">
      <div class="name">Grandmother</div>
    </div>
  </div><!-- end one -->
  <div class="generationonetwo">
  <div class="person male child">
    <div class="name">Uncle</div>
  </div>
    <div class="person spouse female">
    <div class="name">Wife of Uncle</div>
   </div>
  <div class="person child female">
   <div class="name">Aunt</div>
  </div>
  <div class="person spouse male">
    <div class="name">Husband of Aunt</div>
  </div>
  <div class="person paternal male">
    <div class="name">Father</div>
  </div>
  <div class="person spouse child female">
    <div class="name">Mother</div>
  </div>
  <div class="person spouse male">
    <div class="name">Step Father</div>
  </div>
  </div><!-- end two -->
  <div class="generationonetthree">
  <div class="person child male">
    <div class="name">Me</div>
  </div>
    <div class="person child female">
         <div class="name">Sister</div>
    </div>
  <div class="person spouse male">
    <div class="name">Spouse</div>
  </div>
 </div><!-- end three -->
</div><!-- end tree -->

Here is my css/html/jQuery family tree solution that builds the tree to the right of the base person/couple. People are connected to their parents by connectors which are created using some png files (not included) that measure 2px high and 10 px wide. centerPixels has the center 4 filled in and pointRight and pointLeft do just that by filling in their left or right halves. It's not perfect, but it works with the sample data provided.

<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <script src="/lib/jquery-3.3.1.min.js"></script>
    <style>
    .table_Node {border-collapse:collapse; border-spacing:0px; border:0px; padding:0px; vertical-align:middle;}
    .th_Node {border:0px; padding:0px; padding-left:4px; vertical-align:middle;}
    .td_Node {border:0px; padding:5px; vertical-align:middle; position:relative;}
    .lbl_NodeName {font-size:14px;}
    .lbl_NodeBirthDeath {font-size:12px;}
    .divPerson {    
        display:block; 
        width:100%; 
        color:#eeeeee; 
        border:1px solid black; 
        padding:3px 4px 5px 4px; 
        left:8px; 
        min-width:75px; 
        border-radius:8px; 
        background-color: #333;
    }
    .div_AnchorLine {
        display:block;
        position:absolute;
        width:10px;
        left:0px;
        background-image: url("/img/centerPixels.png");
    }
    .div_AnchorTop {
        display:block;
        position:absolute;
        width:10px;
        height:2px;
        top:0px;
        background-image: url("/img/pointRight.png");
    }
    .div_AnchorBottom {
        display:block;
        position:absolute;
        width:10px;
        height:2px;
        bottom:0px;
        background-image: url("/img/pointRight.png");
    }
    .table_EmptyParent {
        height:28px;
        vertical-align:middle;
    }

    .table_Dad {
        margin-left:10px; 
        margin-bottom:2px;
    }

    .div_DadLine {
        display:block;
        position:absolute;
        width:10px;
        left:9px;
        background-image: url("/img/centerPixels.png");
    }
    .div_DadTop {
        display:block;
        position:absolute;
        width:10px;
        height:2px;
        top:0px;
        background-image: url("/img/pointRight.png");
    }
    .div_DadBottom {
        display:block;
        position:absolute;
        width:10px;
        height:2px;
        bottom:0px;
        background-image: url("/img/pointLeft.png");
    }

    .table_Mom {
        margin-left:10px; 
        margin-top:2px;
    }
    .div_MomLine {
        display:block;
        position:absolute;
        width:10px;
        /*height:25%;
        top:50%;*/
        left:9px;
        background-image: url("/img/centerPixels.png");
    }
    .div_MomTop {
        display:block;
        position:absolute;
        width:10px;
        height:2px;
        top:0px;
        background-image: url("/img/pointLeft.png");
    }
    .div_MomBottom {
        display:block;
        position:absolute;
        width:10px;
        height:2px;
        bottom:0px;
        background-image: url("/img/pointRight.png");
    }

    </style>
</head>
<body>
<div id='treeDiv' style='position:relative;'></div>
</body>
</html>

<script>

// this is just skeleton objects
var people = [];
var blankPerson = {
    ID : 0,
    Name:"&nbsp;",
    BirthDate:"",
    BirthPlace:"",
    DeathDate:"",
    DeathPlace:"",
    DadID:0,
    MomID:0
};
var marriages = [];
var blankMarriage = {
    HusbandID: 0, 
    WifeID: 0,
    MarriedDate: "",
    MarriedPlace: ""
};
// these anchors are what are used to start the tree
// the anchorPerson is the person with no children
// if the tree data is broken (i.e. a great-grandparent without a chlid), 
// then results are unpredictable.
var anchorPerson = blankPerson;
var anchorHusband = blankPerson;
var anchorWife = blankPerson;
var anchorMarriage = blankMarriage;

$(document).ready(function(){
    loadPeople();
    loadMarriages();
    buildTree();
});

// this should be an ajax get
function loadPeople()
{
    addPerson(1100, "Person1100","1890-01-01","1921-07-14",0,0);
    addPerson(10, "Person10","","",100,101);
    addPerson(100, "Person100","","",1000,2);
    addPerson(1001, "Person1001","","",0,1100);
    addPerson(1, "Person1","1992-01-15","",10,11);
    addPerson(101, "Person101","","",1010,1011);
    addPerson(1010, "Person1010","","",0,0);
    addPerson(1011, "Person1011","","",0,0);

    addPerson(11, "Person11","","",110,111);
    addPerson(110, "Person110","","",1100,0);
    addPerson(1000, "Person1000","","",0,0);
    addPerson(8, "Person8","","",0,0);
    addPerson(2, "Person2","","",0,21);
    addPerson(1101, "Person1101","","",0,0);
    addPerson(21, "Person21","","",1101,0);
    addPerson(111, "Person111","","",1110,1111);
    addPerson(1110, "Person1110","","",0,0);
    addPerson(1111, "Person1111","","",0,0);
}

// the ajax routine will return an array of person objects,
// so this won't even be here
function addPerson(id,name,born,died,dadid, momid)
{
    people.push({
        ID : id,
        Name:name,
        BirthDate:born,
        BirthPlace:"",
        DeathDate:died,
        DeathPlace:"",
        DadID:dadid,
        MomID:momid
    });
}

// this should be an ajax get
function loadMarriages()
{
    addMarriage(110,111);
    addMarriage(1110,1111);
    addMarriage(1,8);
    addMarriage(10,11);
    addMarriage(100,101);
    addMarriage(0,21);
    addMarriage(1000,2);
    addMarriage(1100,1101);
}

// the ajax routine will return an array of person objects,
// so this won't even be here
function addMarriage(hid,wid)
{
    marriages.push({
        HusbandID: hid, 
        WifeID: wid,
        MarriedDate: "",
        MarriedPlace: ""
    });    
}

// pretty much the only thing I couldn't figure out 
// is how to always get someone centered on parents
// people can ride a little high or a little low on their connectors 
// line depending on how the tree is structured to their right
function buildTree()
{
    // clear everything
    $('#treeDiv').html("");

    // find someone with no children
    // again, if the tree is broken, then this will not work
    anchorPerson = findAnchorPerson();

    // find out if the anchorPerson is married
    anchorMarriage = findMarriage(anchorPerson.ID);   
    if (anchorMarriage == blankMarriage)
    {
        // if not, start the tree with the anchorPerson
        $('#treeDiv').append("<table id='table_"+anchorPerson.ID+"' class='table_Node' cellpadding='0'></table>");
    }
    else
    {
        // otherwise, figure out who is the husband and who is the wife and then start with both
        anchorHusband = findPersonByID(anchorMarriage.HusbandID);
        anchorWife = findPersonByID(anchorMarriage.WifeID);
        $('#treeDiv').append("<table id='table_"+anchorMarriage.HusbandID+"' cellpadding='0' class='table_Node' style='margin-left:5px;'></table>");
        $('#treeDiv').append("<div id='div_AnchorLine' class='div_AnchorLine' style='top:0px;'>"
                                +"<div class='div_AnchorTop'></div>"        
                                +"<div class='div_AnchorBottom'></div>"
                            +"</div>");
        $('#treeDiv').append("<table id='table_"+anchorMarriage.WifeID+"' cellpadding='0' class='table_Node' style='margin-left:5px;'></table>");

    }

    var finished = false;
    while(finished == false)
    {
        var somethingWasDone = false;
        $.each(people,function(){
            var thisPerson = this;
            var foundIt = false;
            $('.table_Node').each(function(){
                if ($(this).attr('id') == "table_"+thisPerson.ID)
                {
                    if ($(this).html() == "") //  should only happen twice. once with anchorHusband and once with anchorWife
                    {
                        somethingWasDone = true;
                        $(this).html(getPersonHTML(thisPerson));                        
                    }
                    else if ($('#div_'+thisPerson.ID).html() == "&nbsp;")
                    {
                        somethingWasDone = true;
                        $(this).html(getPersonHTML(thisPerson));                        
                    }
                }
            });
        });
        if (somethingWasDone == false)
            finished = true;
    }
    // if the anchor person is married, add the line linking the couple
    if (anchorMarriage != blankMarriage)
    {
        var top = $('#div_'+anchorMarriage.HusbandID).offset().top;
        var height = Number($('#div_'+anchorMarriage.WifeID).offset().top) - top;
        $('#div_AnchorLine').attr('style','top:'+Number(top+8)+'px; height:'+height+'px;');
    }

    // now, add connectots to everybody's parents
    $('.td_Parents').each(function(){
        addDadConnector($(this).attr('id').substr(11));
        addMomConnector($(this).attr('id').substr(11));
    });
}

function getPersonHTML(person)
{
    var dad = findPersonByID(person.DadID);
    var mom = findPersonByID(person.MomID);

    var dadClass = "table_Dad";
    var momClass = "table_Mom";

    if (dad == blankPerson && mom != blankPerson)
        dadClass += " table_EmptyParent";

    if (mom == blankPerson && dad != blankPerson)
        momClass += " table_EmptyParent";

    var HTML = ""
            +"<tr id='tr_"+person.ID+"' class='tr_Node'>"
                +"<th class='th_Node'>"
                    +"<div id='div_"+person.ID+"' class='divPerson'>"
                        +"<label class='lbl_NodeName'>"+person.Name+"</label>"
                        +"<label class='lbl_NodeBirthDeath'>"+getBirthDeathInfo(person)+"</label>"
                        +"</div>"
                +"</th>"
                +"<td id='td_parents_"+person.ID+"' class='td_Node td_Parents'>"
                    +"<table id='table_"+person.DadID+"' class='table_Node "+dadClass+"'>"
                        +"<tr id='tr_"+person.DadID+"' class='tr_Node'>"
                            +"<th class='th_Node'>"
                                +"<div id='div_"+person.DadID+"' class='divPerson'>&nbsp;</div>"
                            +"</th>"
                        +"</tr>"
                    +"</table>"
                    +"<table id='table_"+person.MomID+"' class='table_Node "+momClass+"' style='height:50%'>"
                        +"<tr id='tr_"+person.MomID+"' class='tr_Node'>"
                            +"<th class='th_Node'>"
                                +"<div id='div_"+person.MomID+"' class='divPerson'>&nbsp;</div>"
                            +"</th>"
                        +"</tr>"
                    +"</table>"
                +"</td>"
            +"</tr>"
    return HTML;
}
// the goal here is to return "(1900-1975)"
// but, we may get "(-1975)" or "(1900-)" or nothing at all
// the div/label arrangement allows for either with no impact on spacing
function getBirthDeathInfo(person)
{
    var birthyear = getBirthYear(person);
    var deathyear = geDeathYear(person);
    var retval = birthyear+" - "+deathyear;
    if (retval == " - ")
        retval = "";
    else
        retval = "<br/>("+retval+")";
    return retval;
}
function getBirthYear(person)
{
    var retval = "";
    if (person.BirthDate.length >= 4)
        retval = person.BirthDate.substr(0,4);
    return retval;
}

function geDeathYear(person)
{
    var retval = "";
    if (person.DeathDate.length >= 4)
        retval = person.DeathDate.substr(0,4);
    return retval;
}

// add the connector between the person and the dad
function addDadConnector(personID)
{
    if ($('#td_parents_'+personID).length != 0)
    {
        var personTop = Number($('#div_'+personID).offset().top);
        var personHeight = Number($('#div_'+personID).outerHeight());
        var personPadding = parseInt($('#div_'+personID).css('padding-top'))+parseInt($('#div_'+personID).css('padding-bottom'));
        var personCenter = personTop + ((personHeight + personPadding) / 2) - 10;
        var dadTable = $('#td_parents_'+personID).children('.table_Node')[0];
        var dadDiv = $(dadTable).find('.divPerson')[0];
        if ($(dadDiv).length > 0)
        {
            var dadTop = Number($(dadDiv).offset().top);
            var dadHeight = Number($(dadDiv).outerHeight());
            var dadPadding = parseInt($(dadDiv).css('padding-top'))+parseInt($(dadDiv).css('padding-bottom'));
            var dadCenter = dadTop + ((dadHeight + dadPadding) / 2) - 10;
            var left = Number($(dadDiv).offset().left) - 18;
            var height = personCenter - dadCenter;
            $('#treeDiv').append("<div id='div_DadLine_"+personID+"' class='div_DadLine' style='top:"+dadCenter+"px; left:"+left+"px; height:"+height+"px;'>"
                                    +"<div class='div_DadTop'></div>"
                                    +"<div class='div_DadBottom'></div>"
                                +"</div>");
        }
    }
}

// add the connector between the person and the mom
function addMomConnector(personID)
{
    if ($('#td_parents_'+personID).length != 0)
    {
        var personTop = Number($('#div_'+personID).offset().top);
        var personHeight = Number($('#div_'+personID).innerHeight());
        var personPadding = parseInt($('#div_'+personID).css('padding-top'))+parseInt($('#div_'+personID).css('padding-bottom'));
        var personCenter = personTop + ((personHeight + personPadding) / 2) - 11;
        var momTable = $('#td_parents_'+personID).children('.table_Node')[1];            
        var momDiv = $(momTable).find('.divPerson')[0];
        if ($(momDiv).length > 0)
        {
            var momTop = Number($(momDiv).offset().top);
            var momHeight = Number($(momDiv).outerHeight());
            var momPadding = parseInt($(momDiv).css('padding-top'))+parseInt($(momDiv).css('padding-bottom'));
            var momCenter = momTop + ((momHeight + momPadding) / 2) - 10;
            var left = Number($(momDiv).offset().left) - 18;
            var height = momCenter - personCenter;
            $('#treeDiv').append("<div id='div_MomLine_"+personID+"' class='div_MomLine' style='top:"+personCenter+"px; left:"+left+"px; height:"+height+"px;'>"
                                    +"<div class='div_MomTop'></div>"
                                    +"<div class='div_MomBottom'></div>"
                                +"</div>");
        }
    }
}

// find someone who has no child
// this actually returns the last on in the list with no child,
// but there should be only a max of 2 and they'd be married,
// so it doesn't really matter
// handling siblings would bring a new level of complexity
function findAnchorPerson()
{
    var thisPerson = blankPerson;
    var idWithNoChild = 0;
    $.each(people,function(){
        var somePerson = this;
        var hasChild = false;
        $.each(people,function(){
            if (this.DadID == somePerson.ID || this.MomID == somePerson.ID)
                hasChild = true;
        });
        if (hasChild == false)
            thisPerson = somePerson;
    });
    return thisPerson;
}

function findMarriage(personID)
{
    var marriage = blankMarriage;
    $.each(marriages,function(){
        if (personID == this.HusbandID || personID == this.WifeID)
             marriage = this;
    });
    return marriage;
}

function findPersonByID(personID)
{
    var person = blankPerson;
    $.each(people,function(){
        if (this.ID == personID)
            person = this;
    });
    return person;
}

// this isn't in use right now, but it might be useful at some point
function findSpouse(personID)
{
    var spouse = blankPerson;
    $.each(marriages,function(){
        if (this.HusbandID == personID)
            spouse = findPersonByID(this.WifeID)
        else if (this.WifeID == personID)
            spouse = findPersonByID(this.HusbandID)
    });
    return spouse;
}

</script>
Related