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.