chart = {
const off = 80;
const offDNMN = 32;
let scale = 2.5;
const sizeDepth0 = 28;
const sizeDepth1 = 21;
const sizeDepth2 = 16;
const sizeHeight0 = 8.5;
const sizeHeight1 = 12;
const size = 12;
const bulletDepth0 = 2.5;
const bulletDepth1 = 2;
const bullet = 2;
const bulletHeight1 = 1.5;
const bulletHeight0 = 1.5;
let divisor = 1.035 + 0.008 - 0.004 - 0.007;
let angleDeg = 90 + 3.3;
let angleRad = (Math.PI / 180) * angleDeg;
let sepDeg = 1.2;
let sepRad = (Math.PI / 180) * sepDeg;
const root = tree(data);
let heightSN = 800;
root.each(function (d) {
d.y = d.y * scale;
if (d.data.name == "Devatāsaṁyutta") heightSN = d.y;
if (
d
.ancestors()
.find(
(x) =>
x.data.name == "Nidānasaṁyutta" ||
x.data.name == "Khandhasaṁyutta" ||
x.data.name == "Saḷāyatanasaṁyutta"
) &&
d.height == 1
) {
d.y = 890;
d.data.spec = "fontSN";
}
if (d.height == 2) d.y = heightSN;
if (d.depth == 1) d.y = 160 + 100 + 25 + 5;
if (d.depth == 2 && !d.data.acro?.includes("DN")) d.y = 400 + 100 + 30;
if (d.depth == 0) d.x = (-Math.PI * 1.1 - angleRad) * divisor + 2 * Math.PI;
if (d.depth == 0) d.y = -70;
if (d.ancestors().find((x) => x.data.name == "Majjhimanikāya"))
d.x = d.x + sepRad;
if (d.ancestors().find((x) => x.data.name == "Saṁyuttanikāya"))
d.x = d.x + 2 * sepRad;
if (d.ancestors().find((x) => x.data.name == "Aṅguttaranikāya"))
d.x = d.x + 3 * sepRad;
if (d.data.id == "test") {
d.y = 340 + 60 - 30 - 31;
d.x = (Math.PI / 2 - angleRad) * divisor;
}
if (d.data.id == "test2") {
d.y = 340 + 60 + 60;
d.x = (Math.PI / 2 - angleRad) * divisor;
}
});
const isDNMN = (d) =>
d.data.acro?.includes("DN") || d.data.acro?.includes("MN");
let fontSize = (d) => {
if (d.height == 0) {
return sizeHeight0;
} else if (d.data.spec == "fontSN") {
return sizeHeight0;
} else if (d.data.id.slice(0, 2) == "sn" && d.height == 1) {
return sizeHeight1 - 1;
} else if (d.depth == 1) {
return sizeDepth1;
} else if (d.height == 1 && !d.data.acro.includes("AN")) {
return sizeHeight1;
} else if (d.depth == 2) {
return sizeDepth2;
} else if (d.depth == 0) {
return sizeDepth0;
} else return size;
};
let bulletSize = (d) => {
if (d.depth == 0) {
return bulletDepth0;
} else if (
d.data.acro == "SN 12" ||
d.data.acro == "SN 22" ||
d.data.acro == "SN 35"
) {
return bulletHeight1;
} else if (d.data.id == "test" || d.data.id == "test2") {
return 0;
} else if (d.depth == 1) {
return bulletDepth1;
} else if (d.height == 0) {
return bulletHeight0;
} else if (d.height == 1 && !d.data.acro.includes("AN")) {
return bulletHeight1;
} else return bullet;
};
let oneDepth = (d, source = false) => {
let ancestors = {};
source == true
? (ancestors = d.target.ancestors())
: (ancestors = d.ancestors());
const oneDepth = ancestors.find((d) => d.depth == 1);
const result = oneDepth == null ? d.data.name : oneDepth.data.name;
return result;
};
let color = d3.scaleOrdinal(domain);
let isLeaf = (d) => {
let bool = false;
if (d.height == 0) bool = true;
return bool;
};
const fade = (color) => {
const c = d3.color(color);
c.l += 0.25;
return c + "";
};
const svg = d3.create("svg");
const g = svg.append("g").attr("cursor", "grab");
g.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(root.links())
.join("path")
.attr(
"d",
d3
.linkRadial()
.angle((d) => d.x / divisor + angleRad)
.radius((d) => d.y)
)
.attr("stroke", (d) => color(oneDepth(d, true)));
g.append("g")
.selectAll("circle")
.data(root.descendants())
.join("circle")
.attr(
"transform",
(d) => `
rotate(${(d.x * 180) / divisor / Math.PI - 90 + angleDeg})
translate(${d.y},0)
`
)
.attr("r", (d) => bulletSize(d))
.attr("fill", (d) =>
isLeaf(d) ? fade(color(oneDepth(d))) : color(oneDepth(d))
);
g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll("text")
.data(root.descendants())
.join("text")
.attr("font-size", (d) => fontSize(d))
.style("fill", (d) => color(oneDepth(d)))
.attr("transform", (d) => {
console.log(d.data.name, d.x, Math.PI - angleRad);
return `
rotate(${(d.x * 180) / divisor / Math.PI - 90 + angleDeg})
translate(${d.y},0)
rotate(${
d.x > (Math.PI - angleRad) * divisor &&
d.x < (2 * Math.PI - angleRad) * divisor
? 180
: 0
})
`;
})
.attr("dy", "0.31em")
.attr("x", (d) =>
(d.x > (Math.PI - angleRad) * divisor &&
d.x < (2 * Math.PI - angleRad) * divisor) === !d.children
? -6
: 6
)
.attr("text-anchor", (d) =>
(d.x > (Math.PI - angleRad) * divisor &&
d.x < (2 * Math.PI - angleRad) * divisor) === !d.children
? "end"
: "start"
)
.text((d) => {
if (d.depth == 0 || d.depth == 1) return d.data.name;
if (d.height != 0 && isDNMN(d)) return d.data.name;
if (d.data.acro == "") return d.data.name;
if (
d.x > (Math.PI - angleRad) * divisor &&
d.x < (2 * Math.PI - angleRad) * divisor
) {
return !d.children
? d.data.name + " : " + d.data.acro
: d.data.acro + " : " + d.data.name;
} else {
return !d.children
? d.data.acro + " : " + d.data.name
: d.data.name + " : " + d.data.acro;
}
})
.style("cursor", "pointer")
.on("click", function (d, i) {
let link = "https://suttacentral.net/" + i.data.id;
if (i.data.id == "suttaPitaka")
link = "https://suttacentral.net/pitaka/sutta";
window.open(link);
})
.clone(true)
.lower()
.attr("stroke", "white");
scale = 3.5;
divisor = 1.5;
angleDeg = -30;
angleRad = (Math.PI / 180) * angleDeg;
const translation = 1270;
data2.each(function (d) {
if (
d.ancestors().find(
(x) =>
x.data.id != "kn" &&
x.data.id != "kp" &&
x.data.id != "dhp" &&
x.data.id != "bv" &&
x.data.id != "mnd" &&
x.data.id != "mnd" &&
!x.data.id.slice(0, 3) == "cnd" &&
!x.data.id.slice(0, 2) == "cp" &&
!x.data.id.slice(0, 2) == "ps" &&
!x.data.id.slice(0, 2) == "ne" &&
x.data.id != "pe"
) &&
d.height == 1
)
d.children = null;
});
const root2 = tree(data2);
root2.each(function (d) {
d.y = d.y * scale;
if (d.height == 1 || d.height == 0) d.y = 600 + 230 + 100 + 40;
if (d.height == 2) d.y = 490 + 230 + 30 + 30;
if (d.depth == 1) d.y = 260 + 100 + 30;
if (d.depth == 0) d.x = Math.PI;
if (d.depth == 0) d.y = -9 * 80 - 90 - 30 - 20;
if (d.data.name == "test") d.y = 800;
if (d.data.id.slice(0, 3) == "cnd" && d.height == 1)
d.y = 490 + 230 + 30 + 30;
if (d.data.id.slice(0, 3) == "cnd" && d.height == 1) d.data.acro = "";
if (d.data.id.slice(0, 2) == "cp" && d.height == 1)
d.y = 490 + 230 + 30 + 30;
if (d.data.id.slice(0, 2) == "cp" && d.height == 2) d.y = 490 + 100;
if (d.data.id.slice(0, 2) == "cp" && d.height == 2) d.data.acro = "";
if (d.data.id.slice(0, 2) == "ps" && d.height == 1)
d.y = 490 + 230 + 30 + 30;
if (d.data.id.slice(0, 2) == "ps" && d.height == 1) d.data.acro = "";
if (d.data.id.slice(0, 2) == "ne" && d.height == 1)
d.y = 490 + 230 + 30 + 30;
if (d.data.id.slice(0, 2) == "ne" && d.height == 2) d.y = 490 + 100;
if (d.data.id.slice(0, 2) == "ne" && d.height == 1) d.data.acro = "";
});
fontSize = (d) => {
if (d.height == 0) {
return sizeHeight0;
} else if (d.data.id.slice(0, 2) == "ne" && d.height == 1) {
return sizeHeight1;
} else if (d.data.id.slice(0, 2) == "ps" && d.height == 1) {
return sizeHeight1;
} else if (d.data.id.slice(0, 3) == "cnd" && d.height == 1) {
return sizeHeight1;
} else if (d.data.id.slice(0, 2) == "cp" && d.height == 1) {
return sizeHeight1;
} else if (d.data.id.slice(0, 4) == "thag" && d.height == 2) {
return sizeHeight1 - 2;
} else if (d.data.id.slice(0, 2) == "ja" && d.height == 2) {
return sizeHeight1 - 1;
} else if (d.depth == 1) {
return sizeDepth2;
} else if (d.height == 1) {
return sizeHeight0;
} else if (d.depth == 2) {
return sizeHeight1;
} else if (d.depth == 0) {
return sizeDepth1;
} else return size;
};
oneDepth = (d, source = false) => {
let ancestors = {};
source == true
? (ancestors = d.target.ancestors())
: (ancestors = d.ancestors());
const oneDepth = ancestors.find((d) => d.depth == 0);
const result = oneDepth == null ? d.data.name : oneDepth.data.name;
return result;
};
isLeaf = (d) => {
let bool = false;
if (d.height == 0) bool = true;
if (
d.height == 1 &&
d.data.id.slice(0, 2) != "cp" &&
d.data.id.slice(0, 2) != "kp" &&
d.data.id.slice(0, 3) != "dhp" &&
d.data.id.slice(0, 3) != "cnd" &&
d.data.id.slice(0, 2) != "ne" &&
d.data.id.slice(0, 3) != "mnd" &&
d.data.id.slice(0, 2) != "ps" &&
d.data.id.slice(0, 2) != "bv" &&
d.data.id.slice(0, 2) != "pe"
)
bool = true;
return bool;
};
bulletSize = (d) => {
if (d.depth == 0) {
return bulletDepth1;
} else if (d.depth == 1) {
return bulletDepth1;
} else {
return bulletHeight0;
}
};
color = d3.scaleOrdinal(domain);
color("1");
color("2");
color("3");
color("4");
color("5");
color("6");
const g2 = g
.append("g")
.attr("transform", "translate(" + translation + ",0)");
g2.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 0.8)
.selectAll("path")
.data(root2.links())
.join("path")
.attr(
"d",
d3
.linkRadial()
.angle((d) => d.x / divisor + angleRad)
.radius((d) => d.y)
)
.attr("stroke", (d) => color(oneDepth(d, true)));
g2.append("g")
.selectAll("circle")
.data(root2.descendants())
.join("circle")
.attr(
"transform",
(d) => `
rotate(${(d.x * 180) / divisor / Math.PI - 90 + angleDeg})
translate(${d.y},0)
`
)
.attr("fill", (d) => (d.children ? "#555" : "#999"))
.attr("r", (d) => bulletSize(d))
.attr("fill", (d) => color(oneDepth(d)))
.attr("fill", (d) =>
isLeaf(d) ? fade(color(oneDepth(d))) : color(oneDepth(d))
);
g2.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll("text")
.data(root2.descendants())
.join("text")
.attr("font-size", (d) => fontSize(d))
.style("fill", (d) => color(oneDepth(d)))
.attr(
"transform",
(d) => `
rotate(${(d.x * 180) / divisor / Math.PI - 90 + angleDeg})
translate(${d.y},0)
rotate(${
d.x > (Math.PI - angleRad) * divisor || d.x < -angleRad * divisor
? 180
: 0
})
`
)
.attr("dy", "0.31em")
.attr("x", (d) =>
(d.x > (Math.PI - angleRad) * divisor || d.x < -angleRad * divisor) ===
!d.children
? -6
: 6
)
.attr("text-anchor", (d) =>
(d.x > (Math.PI - angleRad) * divisor || d.x < -angleRad * divisor) ===
!d.children
? "end"
: "start"
)
.text((d) => {
if (d.depth == 0 || d.depth == 1) return d.data.name;
if (d.height != 0 && isDNMN(d)) return d.data.name;
if (d.data.acro == "") return d.data.name;
if (d.x > (Math.PI - angleRad) * divisor || d.x < -angleRad * divisor) {
return !d.children
? d.data.name + " : " + d.data.acro
: d.data.acro + " : " + d.data.name;
} else {
return !d.children
? d.data.acro + " : " + d.data.name
: d.data.name + " : " + d.data.acro;
}
})
.style("cursor", "pointer")
.on("click", function (d, i) {
let link = "https://suttacentral.net/" + i.data.id;
if (i.data.id == "suttaPitaka")
link = "https://suttacentral.net/pitaka/sutta";
window.open(link);
})
.clone(true)
.lower()
.attr("stroke", "white");
const fontName = "Poppins";
GFontToDataURI(
"https://fonts.googleapis.com/css2?family=Poppins&display=swap"
)
.then((cssRules) => {
let fontRules = cssRules.join("\n");
d3.select("svg")
.append("defs")
.append("style")
.attr("type", "text/css")
.text(fontRules);
console.log("Added Font");
})
.catch((reason) => console.log(reason));
if (license) {
svg
.append("svg:image")
.attr("x", xLicense - 88)
.attr("y", yLicense - 65)
.attr(
"xlink:href",
await FileAttachment("cc-zero.svg").url()
);
const text = svg
.append("text")
.attr("x", xLicense)
.attr("y", yLicense)
.style("font-weight", "bold")
.attr("font-family", fontName)
.attr("font-size", fLicense)
.text("DhammaCharts.org")
.attr("text-anchor", "end")
.attr("dy", "-1.1em")
.clone(true)
.text("SuttaCentral.net")
.attr("dy", "0.25em");
}
if (licenseNoLogo) {
const text = svg
.append("text")
.attr("x", xLicense)
.attr("y", yLicense)
.style("font-weight", "bold")
.style("fill", "#828282")
.attr("font-family", fontName)
.attr("font-size", fLicense)
.text("CC0 - Public Domain")
.attr("text-anchor", "end")
.attr("dy", "-2.45em")
.clone(true)
.text("DhammaCharts.org")
.attr("dy", "-1.1em")
.clone(true)
.text("SuttaCentral.net")
.attr("dy", "0.25em");
}
if (zoompan) {
g.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
svg.call(
d3
.zoom()
.extent([
[0, 0],
[width, height]
])
.scaleExtent([1, 8])
.on("zoom", zoomed)
);
function dragstarted() {
d3.select(this).raise();
g.attr("cursor", "grabbing");
}
function dragged(event, d) {
d3.select(this)
.attr("cx", (d.x = event.x))
.attr("cy", (d.y = event.y));
}
function dragended() {
g.attr("cursor", "grab");
}
function zoomed({ transform }) {
g.attr("transform", transform);
}
}
return svg.attr("viewBox", autoBox).node();
}