d3 family tree node and link dynamic positioning

88 Views Asked by At

I want family tree app like Balkan family tree with d3.js. And I stuck the node and link dynamic positioning. Sorry my english. I need helped with this.

App Code:

let persons;
let drawed = [];
fetch("/persons")
    .then((res) => res.json())
    .then((res) => {
        persons = res;
        handler();
    })
    .catch(console.log)

async function save () {
    let body = JSON.stringify(persons);
   const res = await fetch("/persons", {
        method: "POST",
        body: body,
        headers: {
            "content-type": "application/json",
        }
   })

   if(res.ok) {
        console.log("Sikeres Mentés!")
   }
}

function handler () {
    //console.log(persons);
    buildTree();
}

const detailsDiv = document.getElementById("details");
function showData (e) {
    detailsDiv.setAttribute("style", "display:block");
    let p;
    for(let i = 0; i < persons.length; i++) {
        if(persons[i].id === parseInt(e.dataset.id)) {
            p = i;
        }
        
    }
   detailsDiv.innerHTML = `
        <div class="${persons[p].gender}">
            <div id="cover">
                <h1>${persons[p].name}</h1>
                <svg data-edit-from-close="" cursor="pointer" onclick="dataClose()" class="bft-edit-form-close"><path style="fill:#ffffff;" d="M21.205,5.007c-0.429-0.444-1.143-0.444-1.587,0c-0.429,0.429-0.429,1.143,0,1.571l8.047,8.047H1.111 C0.492,14.626,0,15.118,0,15.737c0,0.619,0.492,1.127,1.111,1.127h26.554l-8.047,8.032c-0.429,0.444-0.429,1.159,0,1.587 c0.444,0.444,1.159,0.444,1.587,0l9.952-9.952c0.444-0.429,0.444-1.143,0-1.571L21.205,5.007z"></path></svg>
                <div id="avatar">
                    <img src="${persons[p].photo}">
                </div>
            </div>
            
            <div id="buttons">
                <div id="edit-btn" class="edit-btn" onclick="edit()" title="Szerkesztés">
                    <svg width="24" height="24" viewBox="0 0 528.899 528.899"><path fill="#fff" d="M328.883,89.125l107.59,107.589l-272.34,272.34L56.604,361.465L328.883,89.125z M518.113,63.177l-47.981-47.981 c-18.543-18.543-48.653-18.543-67.259,0l-45.961,45.961l107.59,107.59l53.611-53.611 C532.495,100.753,532.495,77.559,518.113,63.177z M0.3,512.69c-1.958,8.812,5.998,16.708,14.811,14.565l119.891-29.069 L27.473,390.597L0.3,512.69z"></path></svg>
                </div>
                <div id="delete-btn" class="edit-btn" onclick="delete()" title="Törlés" hidden>
                    <svg width="24" height="24" viewBox="0 0 900.5 900.5"><path fill="#fff" d="M176.415,880.5c0,11.046,8.954,20,20,20h507.67c11.046,0,20-8.954,20-20V232.487h-547.67V880.5L176.415,880.5z M562.75,342.766h75v436.029h-75V342.766z M412.75,342.766h75v436.029h-75V342.766z M262.75,342.766h75v436.029h-75V342.766z"></path><path fill="#fff" d="M618.825,91.911V20c0-11.046-8.954-20-20-20h-297.15c-11.046,0-20,8.954-20,20v71.911v12.5v12.5H141.874 c-11.046,0-20,8.954-20,20v50.576c0,11.045,8.954,20,20,20h34.541h547.67h34.541c11.046,0,20-8.955,20-20v-50.576 c0-11.046-8.954-20-20-20H618.825v-12.5V91.911z M543.825,112.799h-187.15v-8.389v-12.5V75h187.15v16.911v12.5V112.799z"></path></svg>
                </div>
            </div>
            <div id="profil">
                <form onsubmit="mentes(event)">
                    <label for="name">Teljes név</label>
                    <input type="text" id="name" value="${persons[p].name}" readonly>
                    <label for="bdate">Születési Dátum</label>
                    <input type="text" id="bdate" value="${persons[p].bdate}" readonly>
                    <label for="szhely">Születési hely</label>
                    <input type="text" id="szhely" value="${persons[p].szhely}" readonly>
                    ${persons[p].ddate ? `<label for="ddate">Halálozási dátum</label>
                    <input type="text" id="ddate" value="${persons[p].ddate}" readonly>
                    <label for="hhely">Halálozási hely</label>
                    <input type="text" id="hhely" value="${persons[p].hhely}" readonly>` : ''}
                    <button id="cancle" type="cancle" hidden onclick="cancel(event)">Mégsem</button>
                    <button id="sub" type="submit" hidden>Mentés</button>
                </form>
                
            </div>
        </div>

    `;
}

function edit () {
    const inputs = document.querySelectorAll("input");
    for (let i = 0; i < inputs.length; i++) {
        inputs[i].removeAttribute("readonly");
    }
    const editBtn = document.getElementById("edit-btn");
    editBtn.hidden = true;
    document.getElementById("sub").removeAttribute("hidden");
    document.getElementById("cancle").removeAttribute("hidden");
    document.getElementById("delete-btn").hidden = false;

}

function dataClose () {
    detailsDiv.innerHTML = '';
    detailsDiv.setAttribute("style", "display:none");
}

function mentes (e) {
    e.preventDefault();

}

function cancel(e) {
    e.preventDefault();
    detailsDiv.setAttribute("style", "display:none");
    detailsDiv.innerHTML = '';
}


function buildTree () {
    var width = 1000;
    var height = 600;
    var tree = d3.select("#tree").append("svg")
                    .attr("width", width)
                    .attr("id", "svgImage")
                    .attr("height", height);
                    
    addNode(0,tree,500,300);
                    
                    
                    
    const svgImage = document.getElementById("svgImage");
    const svgContainer = document.getElementById("tree");
    
    var viewBox = {x:0,y:0,w:svgImage.clientWidth,h:svgImage.clientHeight};
    svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`);
    const svgSize = {w:svgImage.clientWidth,h:svgImage.clientHeight};
    var isPanning = false;
    var startPoint = {x:0,y:0};
    var endPoint = {x:0,y:0};;
    var scale = 1;
    
    svgContainer.onmousewheel = function(e) {
        e.preventDefault();
        var w = viewBox.w;
        var h = viewBox.h;
        var mx = e.offsetX;//mouse x  
        var my = e.offsetY;    
        var dw = w*Math.sign(e.deltaY)*0.05;
        var dh = h*Math.sign(e.deltaY)*0.05;
        var dx = dw*mx/svgSize.w;
        var dy = dh*my/svgSize.h;
        viewBox = {x:viewBox.x+dx,y:viewBox.y+dy,w:viewBox.w-dw,h:viewBox.h-dh};
        scale = svgSize.w/viewBox.w;
        zoomValue.innerText = `${Math.round(scale*100)/100}`;
        svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`);
    }
    
    
    svgContainer.onmousedown = function(e){
        isPanning = true;
        startPoint = {x:e.x,y:e.y};   
    }
    
    svgContainer.onmousemove = function(e){
        if (isPanning){
        endPoint = {x:e.x,y:e.y};
        var dx = (startPoint.x - endPoint.x)/scale;
        var dy = (startPoint.y - endPoint.y)/scale;
        var movedViewBox = {x:viewBox.x+dx,y:viewBox.y+dy,w:viewBox.w,h:viewBox.h};
        svgImage.setAttribute('viewBox', `${movedViewBox.x} ${movedViewBox.y} ${movedViewBox.w} ${movedViewBox.h}`);
        }
    }
    
    svgContainer.onmouseup = function(e){
        if (isPanning){ 
        endPoint = {x:e.x,y:e.y};
        var dx = (startPoint.x - endPoint.x)/scale;
        var dy = (startPoint.y - endPoint.y)/scale;
        viewBox = {x:viewBox.x+dx,y:viewBox.y+dy,w:viewBox.w,h:viewBox.h};
        svgImage.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`);
        isPanning = false;
        }
    }
    
    svgContainer.onmouseleave = function(e){
        isPanning = false;
    }
}

function addNode(index,tree,x,y) {
    drawed.push(index);
    const g = tree.append("g")
                    .attr("class", `node ${persons[index].gender}`)
                    .attr("data-id", `${persons[index].id}`)
                    .attr("transform", `matrix(1,0,0,1,${x},${y})`)
                    .attr("onclick", "showData(this)");
    
    g.append("rect")
        .attr("height",120)
        .attr("width",250)
        .attr("rx", 15)
        .attr("x",0)
        .attr("y",0)
    if(persons[index].ddate) {
       g.append("line")
        .attr("x1",200) 
        .attr("y1",0) 
        .attr("x2",250) 
        .attr("y2",50)
        .attr("stroke-width",10)
        .attr("class", "rip")
    }
    g.append("text")
        .text(`${persons[index].name}`)
        .attr("x", 125)
        .attr("y", 80)
        .attr("style", "font-size: 18px; font-weight:bold")
    g.append("text")
        .attr("x", 125)
        .attr("y", 100)
        .text(`${persons[index].ddate ? persons[index].bdate + " - " + persons[index].ddate : persons[index].bdate}`)
    let mIndex,fIndex;
    for (let i = 0; i < persons.length; i++) {
        if(persons[i].id === persons[index].mId) {
            mIndex = i;

        }
        
    }
    if(mIndex && !drawed.includes(mIndex)) {addNode(mIndex,tree,x+145,y-150);}

    for (let i = 0; i < persons.length; i++) {
        if(persons[i].id === persons[index].fId) {
            fIndex = i;

        }
        
    }
    if(fIndex && !drawed.includes(fIndex)) {addNode(fIndex,tree,x-145,y-150);}

    if(persons[index].gender === "female") {
        for(let i = 0; i < persons.length; i++) {
            if (persons[i].mId === persons[index].id) {
                if(!drawed.includes(i)) {addNode(i,tree,x-145,y+150);}
            }
        }
    }else {
        for(let i = 0; i < persons.length; i++) {
            if (persons[i].fId === persons[index].id) {
                if(!drawed.includes(i)) {addNode(i,tree,x-145,y+150);}
            }
        }
    }
    
    
}

I store the data in a json file. And node.js server read it and send it to the client.
testperson.json :

[
    {
        "id": 1,
        "name": "Kiss Péter",
        "bdate": "2013.05.28",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": 3,
        "fId": 2,
        "pIds": [],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "male"
    },
    {
        "id": 2,
        "name": "Kiss János",
        "bdate": "1986.03.22",
        "szhely": "Kecskemét",
        "ddate": "2015.08.27",
        "hhely": "Kecskemét",
        "mId": 4,
        "fId": 6,
        "pIds": [3],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "male"
    },
    {
        "id": 3,
        "name": "Nagy Evelin",
        "bdate": "1989.08.12",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": null,
        "fId": null,
        "pIds": [2],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "female"
    },
    {
        "id": 4,
        "name": "Takács Mária",
        "bdate": "1959.06.19",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": 8,
        "fId": 7,
        "pIds": [6],
        "divorced": [6],
        "married": [],
        "photo" : "",
        "gender": "female"
    },
    {
        "id": 5,
        "name": "Kiss József",
        "bdate": "1984.02.28",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": 4,
        "fId": 6,
        "pIds": [],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "male"
    },
    {
        "id": 6,
        "name": "Kiss István",
        "bdate": "1956.01.18",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": null,
        "fId": null,
        "pIds": [4],
        "divorced": [4],
        "married": [],
        "photo" : "",
        "gender": "male"
    },
    {
        "id": 7,
        "name": "Takács András",
        "bdate": "1928.04.15",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": null,
        "fId": null,
        "pIds": [8],
        "divorced": [],
        "married": [8],
        "photo" : "",
        "gender": "male"
    },
    {
        "id": 8,
        "name": "Tímár Zsuzsanna",
        "bdate": "1930.04.04",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": null,
        "fId": null,
        "pIds": [7],
        "divorced": [],
        "married": [7],
        "photo" : "",
        "gender": "female"
    },
    {
        "id": 9,
        "name": "Takács Zsuszanna",
        "bdate": "1951.04.18",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": 8,
        "fId": 7,
        "pIds": [],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "female"
    },
    {
        "id": 10,
        "name": "Takács István",
        "bdate": "1952.07.29",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": 8,
        "fId": 7,
        "pIds": [],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "male"
    },
    {
        "id": 11,
        "name": "Takács György",
        "bdate": "1954.01.13",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": 8,
        "fId": 7,
        "pIds": [],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "male"
    },
    {
        "id": 12,
        "name": "Takács János",
        "bdate": "1956.04.04",
        "szhely": "Kecskemét",
        "ddate": null,
        "hhely": null,
        "mId": 8,
        "fId": 7,
        "pIds": [],
        "divorced": [],
        "married": [],
        "photo" : "",
        "gender": "male"
    }
]

I've been trying for half a year, but I only got so far. the nodes are placed on top of each other.

0

There are 0 best solutions below