Public
Edited
Mar 24
Paused
Importers
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const scale = 1.03;
const scaleWord = 1.8;
const sw = 0.4;
const so = 0.95;
const k = 2; // 2^k colors segments per curve

const myColor = blackWhite
? (d) => (darkMode ? "white" : "black")
: colorFunc(colorNumber, colorRotation, colorSeed, darkenFactor);

// Specify the chart’s dimensions.
const width = 1500;
const margin = 80;
const height = width;
const cx = width * 0.5; // adjust as needed to fit
const cy = height * 0.5; // adjust as needed to fit
const radius = Math.min(width, height) / 2 - 137;

links.forEach((d) => {
if (d.title == "Akusala-sādhārana-cetasika")
d.title = "Akusala-sādhārana-c.";
if (d.title == "Vutthāna-gāminī-vipassanā") d.title = "Vutthāna-gāminī-v.";
});

// Create a radial cluster layout. The layout’s first dimension (x)
// is the angle, while the second (y) is the radius.
const tree = d3
.cluster()
.size([2 * Math.PI, radius])
.separation((a, b) => {
// Find shared ancestor at depth 2 and depth 3
const depth2Ancestor = a
.ancestors()
.find((ancestor) => ancestor.depth === 2);
const depth3Ancestor = a
.ancestors()
.find((ancestor) => ancestor.depth === 3);
const bDepth2Ancestor = b
.ancestors()
.find((ancestor) => ancestor.depth === 2);
const bDepth3Ancestor = b
.ancestors()
.find((ancestor) => ancestor.depth === 3);

// If both have a common ancestor at depth 3
if (depth3Ancestor && depth3Ancestor === bDepth3Ancestor) {
return 0.52; // Moderate separation for shared depth 3 ancestor
}
// If both have a common ancestor at depth 2
if (depth2Ancestor && depth2Ancestor === bDepth2Ancestor) {
return 0.7; // Larger separation for shared depth 2 ancestor
}

// Default separation for other cases
return 1;
});

// Sort the tree and apply the layout.
const root = tree(hierarchie);

root.leaves().forEach((leaf) => {
leaf.outgoing = [];
leaf.incoming = [];
const link = links.find((node) => node.file === leaf.data.name);
if (link) {
link.links.forEach((targetLink) => {
const targetNode = root.find((d) => d.data.name === targetLink.file);
if (targetNode) {
leaf.outgoing.push([leaf, targetNode]);
console.log(targetNode);
}
});
}
links.forEach((node) => {
node.links.forEach((link) => {
if (link.file === leaf.data.name) leaf.incoming.push([node, leaf]);
});
});
leaf.degree = leaf.incoming.length + leaf.outgoing.length;
});

console.log(root);

// Creates the SVG container.
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [
-cx * scale - margin / 2,
-cy * scale - margin / 2,
width * scale + margin,
height * scale + margin
])
.attr("style", "width: 100%; height: auto; font: 10px sans-serif;");

// if (darkMode)
svg
.append("rect")
.attr("x", -width / 2 - margin)
.attr("y", -height / 2 - margin)
.attr("width", width + 2 * margin)
.attr("height", height + 2 * margin)
.attr("fill", darkMode ? "black" : "white");

// Append links.
if (structure)
svg
.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll()
.data(root.links())
.join("path")
.attr(
"d",
d3
.linkRadial()
.angle((d) => d.x)
.radius((d) => d.y)
)
.attr("stroke", (d) =>
myColor(
d.target.ancestors().find((ancestor) => ancestor.depth == colorDepth)
)
);

// Append nodes.
svg
.append("g")
.selectAll()
.data(root.leaves())
.join("circle")
.attr(
"transform",
(d) => `rotate(${(d.x * 180) / Math.PI - 90}) translate(${d.y},0)`
)
.attr("fill", (d) => (d.children ? "#555" : "#999"))
.attr("r", (d) => 1 + 0.05 * d.degree)
.attr("fill", (d) => {
const col = myColor(
d.ancestors().find((ancestor) => ancestor.depth == colorDepth)
);
return d.children ? col : darkMode ? darken(col, 0.8) : lighten(col, 0);
// return d.children ? col : darkMode ? darken(col, 0.8) : lighten(col, 0.8);
});

// Append labels.
svg
.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll()
.data(root.leaves())
.join("text")
.attr(
"transform",
(d) =>
`rotate(${(d.x * 180) / Math.PI - 90}) translate(${d.y},0) rotate(${
d.x >= Math.PI ? 180 : 0
})`
)
.attr("dy", "0.31em")
.attr("x", (d) =>
d.x < Math.PI === !d.children ? 6 * scaleWord : -6 * scaleWord
)
.attr("text-anchor", (d) =>
d.x < Math.PI === !d.children ? "start" : "end"
)
// .attr("font-size", (d) => 9.5) // 9.5 + 0.05 * d.degree)
.attr("font-size", (d) => 9.5 + 0.05 * d.degree)
.attr("font-size", (d) =>
d.data.name == "bodhipakkhiya_dh.htm" ? 10 : 9.5 + 0.1 * d.degree
)
.attr("paint-order", "stroke")
.attr("stroke", darkMode ? "black" : "white")
.attr("fill", "currentColor")
.attr("fill", (d) => {
const col = myColor(
d.ancestors().find((ancestor) => ancestor.depth == colorDepth)
);
return darkMode ? lighten(col, 0.4) : col;
})
.text((d) =>
d.height == 0
? links.find((node) => node.file == d.data.name).title
: d.depth
);

if (edge)
svg
.append("g")
.attr("fill", "none")
.attr("stroke-width", sw)
.attr("stroke-opacity", so)
.selectAll("path")
.data(root.leaves().flatMap((leaf) => leaf.outgoing))
.join("path")
//.style("mix-blend-mode", "normal")
.style("mix-blend-mode", darkMode ? "normal" : "multiply")
.style("opacity", so)
.attr("d", ([i, o]) => line(i.path(o)))
.each(function (d) {
d.path = this;
})
.attr("stroke", (d) =>
lighten(
myColor(
d[1].ancestors().find((ancestor) => ancestor.depth == colorDepth)
),
LighenFactor
)
);

const g = svg.append("g");

const drawRingAround = (
level = 2,
radiusCat = 1000,
catRadius = 50,
fontSize = 10,
offAngle = 0,
withText = false,
withEdges = true,
cornerRadius = 5,
arrayName,
arrayTopAdjust,
arrayBottomAdjust
) => {
let leaves = root.leaves();

let currentCategory = leaves[0]
.ancestors()
.find((ancestor) => ancestor.depth == level).data.name;
console.log(currentCategory);
let currentColor = myColor(
leaves[0].ancestors().find((ancestor) => ancestor.depth == colorDepth)
);
let currentFirstX = leaves[0].x;
let currentLastX = leaves[0].x;
let catArray = [];

for (let i = 1; i < leaves.length; i++) {
if (
leaves[i].ancestors().find((ancestor) => ancestor.depth == level).data
.name !== currentCategory
) {
catArray.push({
color: currentColor,
name: currentCategory,
start: currentFirstX,
end: currentLastX
});
currentColor = myColor(
leaves[i].ancestors().find((ancestor) => ancestor.depth == colorDepth)
);
currentCategory = leaves[i]
.ancestors()
.find((ancestor) => ancestor.depth == level).data.name;
currentFirstX = leaves[i].x;
}
currentLastX = leaves[i].x;
}

catArray.push({
color: currentColor,
name: currentCategory,
start: currentFirstX,
end: currentLastX
});

const catData = catArray.map((d, i) => {
return {
color: d.color,
text: withText ? arrayName[i] : "", //d.name,
startAngle: (d.start / Math.PI) * 180,
endAngle: (d.end / Math.PI) * 180,
topAdjust: arrayTopAdjust && arrayTopAdjust[i],
bottomAdjust: arrayBottomAdjust && arrayBottomAdjust[i]
};
});

drawRing(g, {
fontSize: fontSize,
r: radiusCat,
innerR: catRadius,
opacity: 0,
padAngle: 0,
endsPadAngle: -offAngle,
opaque: false,
//radial: true,
cornerRadius: cornerRadius,
data: catData,
strokeWidth: withEdges ? undefined : 0
});
};

drawRing(g, {
fontSize: 20,
r: 778,
innerR: 20,
opacity: 0,
opaque: false,
strokeWidth: 0,
color: darkMode
? d3.color("steelblue").brighter(0.5)
: d3.color("steelblue").darker(1),
data: [
{
text: " BUDDHIST DICTIONARY: A MANUAL OF BUDDHIST TERMS AND DOCTRINES",
// text: "THE ILLUSTRATED MANUAL OF BUDDHIST TERMS AND DOCTRINES",
//"The Illustrated Manual of Buddhist Terms and Doctrines",
// Buddhist Dictionary: A Manual of Buddhist Terms and Doctrines By Nyanatiloka Thera
// BUDDHIST DICTIONARY: A MANUAL OF BUDDHIST TERMS AND DOCTRINES
startAngle: -90,
endAngle: 90,
letterSpacing: 5
}
]
});

drawRing(g, {
fontSize: 20,
r: 778,
innerR: 20,
opacity: 0,
opaque: false,
strokeWidth: 0,
color: darkMode
? d3.color("steelblue").brighter(0.5)
: d3.color("steelblue").darker(1),
data: [
{
text: "By Nyanatiloka Thera",
// text: "THE ILLUSTRATED MANUAL OF BUDDHIST TERMS AND DOCTRINES",
//"The Illustrated Manual of Buddhist Terms and Doctrines",
// Buddhist Dictionary: A Manual of Buddhist Terms and Doctrines By Nyanatiloka Thera
// BUDDHIST DICTIONARY: A MANUAL OF BUDDHIST TERMS AND DOCTRINES
startAngle: 90,
endAngle: 270,
letterSpacing: 2
}
]
});

drawRingAround(2, 618, 150, 10, 0.9, false, true, undefined);
drawRingAround(
2,
743,
30,
12,
0.9,
true,
false,
undefined,
paliCategories,
paliCategoriesAdjustTop,
paliCategoriesAdjustBottom
);
drawRingAround(3, 621, 125, 10, 0.6, false, true, 3);
drawRingAround(
3,
724,
30,
8,
0.7,
true,
false,
3,
categories,
categoriesAdjustTop,
categoriesAdjustBottom
);

////////////////////////////////////////////////////// Start of License ///////////////////////////////////////////////

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

// svg
// .append("svg:image")
// .attr("x", xLicense - 55)
// .attr("y", yLicense - 43)
// .attr("width", 55)
// // .attr("height", 31)
// .attr(
// "xlink:href",
// // "https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg"
// "https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/by-nc.svg"
// // await FileAttachment("88x31-1.png").url()
// )
// .attr("opacity", 0.75);

svg
.append("g")
.attr("transform", ` translate(${790 - 70}, ${790 - 46}) scale(0.8)`)
//.attr("fill", "#828282")
.attr("opacity", 0.75)
.html(logo);

const text = svg
.append("text")
.attr("x", 790)
.attr("y", 790)
.style("font-weight", "bold")
.style("fill", "#828282")
.attr("font-family", fontName)
.attr("font-size", 9)
.text("DhammaCharts.org")
.attr("text-anchor", "end")
.attr("dy", "-1.1em")
.clone(true)
.text("PaliKanon.com")
.attr("dy", "0.25em");

////////////////////////////////////////////////////// End of License ///////////////////////////////////////////////

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data = hierarchie.leaves().map((d) => d.data.name)
Insert cell
hierarchie = {
let communities = communitiesH;
const links = removed_edges;
let r = 0;

links.reverse().forEach((link) => {
// Finding Communities
let a = link[0],
b = link[1];
let comA = communities.find((com) =>
com.find((node) => node.data.name == a)
);
let comB = communities.find((com) =>
com.find((node) => node.data.name == b)
);
// Merginig if coms are different
if (comA != comB) {
let heightA = comA.find((node) => node.depth == 0).height;
let heightB = comB.find((node) => node.depth == 0).height;
if (heightB != heightA) {
if (heightB > heightA) {
const comTemp = comB,
temp = b,
heightTemp = heightB;
comB = comA;
comA = comTemp;
b = a;
a = temp;
heightB = heightA;
heightA = heightTemp;
}
// comA is heigher than comB
// we have to merge at heightB + 1
// we need to find the sub tree of comA that contains the node a

const nodeA = comA.find((node) => node.data.name == a);
const ancestorA = nodeA
.ancestors()
.find((ancestor) => ancestor.height == heightB + 1);
comB.parent = ancestorA;
ancestorA.children.push(comB);
comA = d3.hierarchy(comA); // recompute the hierarchy
comA.each((d) => (d.data = { ...d.data.data, children: d.children })); // fixes the glitch
communities = communities.filter((com) => com != comB);
} else {
// we need to add a common new ancestor to both communities
let comC = d3.hierarchy({ name: "C-" + heightA + "-" + r });
comC.children = [comA, comB];
comC = d3.hierarchy(comC);
comC.each((d) => (d.data = { ...d.data.data, children: d.children })); // fixes the glitch
communities.push(comC);
communities = communities.filter((com) => com != comA && com != comB);
r++;
}
}
console.table(communities.length);
});
let comF = d3.hierarchy(communities[0]);
comF.each((d) => (d.data = { ...d.data.data, children: d.children })); // fixes the glitch
return comF;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
paliCategoriesClaude = [
"Fundamental Teachings & Noble Truths",
"Meditation & Spiritual Practices",
"Liberation & Spiritual Powers",
"Mental Factors & Contemplation",
"Consciousness & Ultimate Reality",
"Spiritual Development & Hindrances",
"Existence & Universal Laws",
"Wisdom Knowledge & Mental States",
"Celestial Realms & Planes of Existence"
]
Insert cell
paliCategories = [
"Qualities for Awakening",
"Meditation & Virtue",
"Liberation & Insight",
"Mind & Mental Factors",
"Reality & Experience",
"Defilements & Overcoming Them",
"Views, Rebirth & Samsara",
"Wisdom & Path to Enlightenment",
"Realms & Beings"
]
Insert cell
paliCategories1 = [
"Virtues & Spiritual Development",
"Meditation & Ethical Conduct",
"Liberation & Higher Realizations",
"Mental & Meditative Processes",
"Aggregates & Dependent Origination",
"Defilements & Stages of Enlightenment",
"Doctrinal Classifications & Cosmic Concepts",
"Insight Knowledge & Meditative Progress",
"Realms of Existence & Beings"
]
Insert cell
paliCategoriesAdjustTop = [0, 0, 0, 0, 0, 0, 0, 0, 0]
Insert cell
paliCategoriesAdjustBottom = [0, 0, 0, 0, 0, 0, 0, 0, 0]
Insert cell
Insert cell
Insert cell
categories1 = [
"Powers",
"Enlightenment Factors",
"Mindfulness & //Ethics",
"Path & Wisdom",
"Concentration & // Meditation",
"Reflections & Virtues",
"Meditative // Objects",
"Liberation // States",
"Wisdom & // Liberation",
"Mental Factors & // Stream-entry",
"Bases of Power",
"Mental Processes",
"Happiness & // Concentration",
"Dependent Origination & Kamma",
"Equanimity & Aggregates",
"Elements & Sense Bases",
"Defilements & Path Qualities",
"Cravings & Hindrances",
"Skillful Actions",
"Views & Rebirth",
"Defilements & // Corruptions",
"Beings & Samsaric Cycle",
"Knowledge & // Insight",
"Liberation & Purification",
"Jhana & Mental Processes",
"Functions of Consciousness",
"Teachings & Practice",
"Non-returners & // Realms",
"States of Existence",
"Lower Realms // & Destinations"
]
Insert cell
categoriesAdjustTop = [
0, 0, 0.4, 0, 0.4, 0, 0.4, 0.4, 0, 0, 0, 0, -0.6, 0, 0, 0, 0, 0, 0, 0, -0.6,
0, -0.6, 0, 0, 0, 0.4, 0.4, 0.4, 0.4
]
Insert cell
categoriesAdjustBottom = [
0, 0, 0.3, 0, 0.3, 0, 0.3, 0.3, 0, -0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0,
-0.5, 0, -0.5, 0, 0, 0, 0.3, 0.3, 0.3, 0.3
]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// from https://stackoverflow.com/questions/63288390/font-style-of-text-in-svg-not-propagating-to-png-on-download

/*
Only tested on a really limited set of fonts, can very well not work
This should be taken as an proof of concept rather than a solid script.
@Params : an url pointing to an embed Google Font stylesheet
@Returns : a Promise, fulfiled with all the cssRules converted to dataURI as an Array
*/
function GFontToDataURI(url) {
return fetch(url) // first fecth the embed stylesheet page
.then((resp) => resp.text()) // we only need the text of it
.then((text) => {
// now we need to parse the CSSruleSets contained
// but chrome doesn't support styleSheets in DOMParsed docs...
let s = document.createElement("style");
s.innerHTML = text;
document.head.appendChild(s);
let styleSheet = s.sheet;

// this will help us to keep track of the rules and the original urls
let FontRule = (rule) => {
let src =
rule.style.getPropertyValue("src") ||
rule.style.cssText.match(/url\(.*?\)/g)[0];
if (!src) return null;
let url = src.split("url(")[1].split(")")[0];
return {
rule: rule,
src: src,
url: url.replace(/\"/g, "")
};
};
let fontRules = [],
fontProms = [];

// iterate through all the cssRules of the embedded doc
// Edge doesn't make CSSRuleList enumerable...
for (let i = 0; i < styleSheet.cssRules.length; i++) {
let r = styleSheet.cssRules[i];
let fR = FontRule(r);
if (!fR) {
continue;
}
fontRules.push(fR);
fontProms.push(
fetch(fR.url) // fetch the actual font-file (.woff)
.then((resp) => resp.blob())
.then((blob) => {
return new Promise((resolve) => {
// we have to return it as a dataURI
// because for whatever reason,
// browser are afraid of blobURI in <img> too...
let f = new FileReader();
f.onload = (e) => resolve(f.result);
f.readAsDataURL(blob);
});
})
.then((dataURL) => {
// now that we have our dataURI version,
// we can replace the original URI with it
// and we return the full rule's cssText
return fR.rule.cssText.replace(fR.url, dataURL);
})
);
}
document.head.removeChild(s); // clean up
return Promise.all(fontProms); // wait for all this has been done
});
}
Insert cell
Insert cell
<style>
body{
max-width: 1100px;
padding-left: 1em;
padding-right: 1em;
margin-left: auto;
margin-right: auto;}
img{
/*max-width: 1100px;
margin-left: auto;
margin-right: auto;}*/
max-width: 100%;

h1::after {
content: "";
display: block;
width: 100%;
height: 1px;
background-color: #ccc; /* Grey color for the horizontal rule */
margin: 0; /* Space above and below the rule */
}

h1, h2, h3, h4, h5, h6 {
max-width: none;
}

</style>
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more