web-dev-qa-db-fra.com

Arbre généalogique avec HTML et CSS purs (ou avec JS minimal)

J'essaie de construire un arbre généalogique avec HTML et CSS. J'ai trouvé un bon exemple sur codepen .

Comme une famille n'est pas une simple hiérarchie de nœuds, mais des relations parfois assez complexes, j'ai besoin d'avoir plusieurs nœuds qui agissent presque comme un seul. Mais commençons par l'exemple pas le plus complexe, en prenant ma famille comme exemple, en commençant par ma grand-mère du côté de la mère comme racine:

  • Il y a mon père qui est l'ex-mari de ma mère
  • Ma mère s'est à nouveau mariée, j'ai donc un beau-père

Donc, le nœud de base d'en haut est ma mère, mais ma sœur et moi devons être placés sous mon père car nous ne sommes pas liés au beau-père. J'ai essayé d'imaginer ceci ici: family tree

Voici mon balisage et css (basé sur l'exemple de codepen ci-dessus):

/* 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: J'ai trouvé une solution, alors que je calcule les nœuds affichés les uns à côté des autres dans le backend et j'écris certains attributs de style avec des largeurs dans un div nouvellement introduit. Ce n'est toujours pas parfait, donc si quelqu'un a des améliorations, postez un commentaire ou une réponse.

5
Thomas

Je vous suggère d'utiliser une bibliothèque d'arbre généalogique js tierce

Par exemple OrgChart JS

L'algorithme de l'arbre généalogique pourrait être très complexe, c'est pourquoi il est plus facile si plusieurs nœuds agissent comme un, comme vous l'avez dit.

La seule chose est que vous devez apprendre à implémenter votre propre modèle dans OrgChart JS.

Voici un exemple avec 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>
2
user2231308

Avez-vous pensé à utiliser SVG, ou SVG + HTML, en conjonction avec votre CSS? L'outil décrit dans SVG Family-Tree Generator est simplement un concepteur qui crache une combinaison configurable de HTML, SVG, JavaScript et CSS. Son arbre visuel a des installations pour montrer la filiation provisoire, les enfants non montrés (pour indiquer qu'il y en a d'autres, mais sans rapport avec la publication dans laquelle l'arbre est intégré), les mariages séquentiels et la personnalisation CSS des lignes de jonction. Il existe des configurations sans script, et toutes les sorties ne sont pas obscurcies (permettant l'édition ou l'apprentissage).

Si vous voulez éviter le script, vous pouvez également trouver SVG à éviter. Bien qu'il soit extrêmement puissant et permette même l'interaction de l'utilisateur avec les éléments de la boîte, certains sites ne l'aiment pas; notamment WordPress où vous pourriez mieux convertir le SVG en PNG ou similaire.

1
ACProctor

Voici ma solution d'arbre généalogique css/html/jQuery qui construit l'arbre à droite de la personne/du couple de base. Les gens sont connectés à leurs parents par des connecteurs qui sont créés en utilisant des fichiers png (non inclus) mesurant 2px de haut et 10 px de large. centerPixels a le centre 4 rempli et pointRight et pointLeft font exactement cela en remplissant leurs moitiés gauche ou droite. Ce n'est pas parfait, mais cela fonctionne avec les exemples de données fournis.

<!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>
0
John Ureke

Voici quelque chose pour vous aider à démarrer, je dois en rester là maintenant.

.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 -->
0
Carol McKay