20

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.

3
  • 2
    the problem at least starts with the positioning in the html Commented Nov 5, 2018 at 9:32
  • 2
    @CarolMcKay I thought as well, but I am currently not sure how I should place the different .person divs within the structure and also the .parent with the children, so the lines depart from that node... So, any markup and CSS changes that could make this work are welcome. Commented Nov 5, 2018 at 9:39
  • 1
    @thomas very nice solution. What if I want to display parent on a single line instead of the new line. If possible can you please guide. (Father-Mother) Commented Nov 5, 2019 at 16:18

6 Answers 6

12

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>

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks, will have a look into this. Though I was hoping to find a solution without JS (at least without a full library), but maybe that is too much to ask. But using some canvas for the lines is maybe something I could do with minimal effort
This does not show up correctly. All people are displayed horizontally.
All people are displayed horizontally
I found a working version of the same. balkangraph.com/OrgChartJS/Demos/RoyalFamilyTree
Is it free to use or comes with a payment?
|
2

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.

1 Comment

Thanks for the link, I will have a look into it. The version, I currently use (updated in my OP), works, but has some limitations and you can only do so much with CSS and before/after pseudo classes.
2

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!

Comments

1

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 -->

2 Comments

Thanks for the effort, though I think without some actual hierarchy in the DOM it will be hard to align the children. This above was just a small example to show what I need. The full tree I am trying to display goes one level up to my great grand father as the root, who had 12 children and some of them also had quite a few children. It currently consists of 300+ people!
"300+ people" WHOA @Thomas.
1

Looking for the same: I found SlickMap. It's a site map builder that uses nested ul > li tags to create it. Here is demo

Maybe not exactly builded for what you want, but could do the trick

Here is the result: sitemap

Here is how you build it:

enter image description here

1 Comment

I used it and it wasn't good. It is impossible for you to add your siblings and your nephews, etc.
0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.