Public
Edited
Mar 24
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
import { links as edges} from "2d1af81c02434761"
Insert cell
chart = {
const sw = 0.4;
const so = 0.95;
// Beta factor for controlling bundle strength (0.0 to 1.0)
// Lower values = less bundling, higher values = more bundling
const bundleBeta = 0.56; // Adjust this value as needed

const myColor = blackWhite
? (d) => (darkMode ? "white" : "black")
: colorFunc(colorNumber, colorRotation, colorSeed, darkenFactor);
const root = hierarchie;
root.leaves().forEach((leaf) => {
leaf.outgoing = [];
leaf.incoming = [];
const link = edges.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);
}
});
}
edges.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);
// Specify the chart's dimensions.
const width = 2000;
const height = width;
// Compute the graph and start the force simulation.
const links = root.links();
const nodes = root.descendants();

// Modify your force simulation to include a custom force for hierarchical distance constraints
const getDistance = (sourceDepth, targetDepth) => {
const depthDifference = Math.abs(sourceDepth - targetDepth);
return 30 * sourceDepth;
};

const simulation = d3
.forceSimulation(nodes)
.force(
"link",
d3
.forceLink(links)
.id((d) => d.id)
.distance((d) => getDistance(d.source.height, d.target.height))
.strength(1)
)
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX())
.force("y", d3.forceY())
.force("radial", d3.forceRadial(0, 0, 0).strength(-0.08)); // Radial repulsion

nodes.forEach((node) => {
if (node.depth === 0) {
node.fx = 0; // Fix X position
node.fy = 0; // Fix Y position
}
});

// Create the container SVG.
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Append links.

const link = svg
.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line");

// Append nodes.
const node = svg
.append("g")
.attr("fill", "#fff")
.attr("stroke", "#000")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("stroke", (d) => (d.children ? null : "#fff"))
.attr("r", (d) => (d.children ? 0 : 3.5 + 0.1 * 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);
});

// Create a placeholder for the labels group - we'll add labels after simulation
let labelGroup;

// Update function for the simulation
simulation.on("tick", () => {
link
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);

node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
});

// Create a promise that resolves after 3 seconds
const simulationDuration = 3000; // 3 seconds

// Set up a promise to handle the timed simulation
const runTimedSimulation = new Promise((resolve) => {
// Start the simulation
console.log("Starting simulation...");

// Set a timeout to stop the simulation after the duration
setTimeout(() => {
simulation.stop();
console.log("Simulation stopped after 3 seconds");
resolve();
}, simulationDuration);
});

// Add the additional layer of edges and labels after the simulation has run
runTimedSimulation.then(() => {
link.attr("stroke-opacity", 0);
// This function creates a path that's relaxed between nodes according to the beta factor
function createBundledPath(source, target, beta = bundleBeta) {
// The beta factor controls how much bundling occurs
// We'll use it to adjust how much intermediate points influence the path

// Find the common ancestor path
let sourceAncestors = source.ancestors();
let targetAncestors = target.ancestors();

// Find the lowest common ancestor
let commonAncestor = null;
for (let i = 0; i < sourceAncestors.length; i++) {
for (let j = 0; j < targetAncestors.length; j++) {
if (sourceAncestors[i] === targetAncestors[j]) {
commonAncestor = sourceAncestors[i];
break;
}
}
if (commonAncestor) break;
}

// If no common ancestor is found, create a direct path
if (!commonAncestor) {
return `M${source.x},${source.y} L${target.x},${target.y}`;
}

// Create path points
let points = [];

// Add source point
points.push({ x: source.x, y: source.y });

// Get path to common ancestor
const sourceToCommon = [];
for (let i = 0; i < sourceAncestors.length; i++) {
const ancestor = sourceAncestors[i];
if (ancestor === source) continue; // Skip self

sourceToCommon.push({ x: ancestor.x, y: ancestor.y });

if (ancestor === commonAncestor) break;
}

// Get path from common ancestor to target
const commonToTarget = [];
for (let i = 0; i < targetAncestors.length; i++) {
const ancestor = targetAncestors[i];
if (ancestor === target) continue; // Skip self

if (ancestor === commonAncestor) {
// We've found the starting point
break;
}

commonToTarget.unshift({ x: ancestor.x, y: ancestor.y });
}

// Beta-influenced path generation
// When beta is close to 0, the path is more direct
// When beta is close to 1, the path follows the hierarchy more closely

if (beta <= 0) {
// Direct path - just source to target
return `M${source.x},${source.y} L${target.x},${target.y}`;
} else if (beta >= 1) {
// Full hierarchical path
points = points.concat(sourceToCommon, commonToTarget);
} else {
// Partial influence based on beta
// We'll use fewer intermediate points as beta decreases

// For source to common path
if (sourceToCommon.length > 0) {
// Always include the first point after source for minimal bundling
points.push(sourceToCommon[0]);

// If there are more points, add some based on beta
if (sourceToCommon.length > 1) {
// The number of points to include depends on beta
const pointsToInclude = Math.max(
1,
Math.floor(sourceToCommon.length * beta)
);
const step = sourceToCommon.length / (pointsToInclude + 1);

for (let i = 1; i < pointsToInclude; i++) {
const index = Math.min(
Math.floor(i * step),
sourceToCommon.length - 1
);
points.push(sourceToCommon[index]);
}

// Always include common ancestor
points.push(sourceToCommon[sourceToCommon.length - 1]);
}
}

// For common to target path
if (commonToTarget.length > 0) {
// Always include the point before target for minimal bundling
if (commonToTarget.length > 1) {
// The number of points to include depends on beta
const pointsToInclude = Math.max(
1,
Math.floor(commonToTarget.length * beta)
);
const step = commonToTarget.length / (pointsToInclude + 1);

for (let i = 1; i < pointsToInclude; i++) {
const index = Math.min(
Math.floor(i * step),
commonToTarget.length - 1
);
points.push(commonToTarget[index]);
}
}

// Include the last point before target
points.push(commonToTarget[commonToTarget.length - 1]);
}
}

// Add target point
points.push({ x: target.x, y: target.y });

// Create a line generator with a tension that varies based on beta
// curveBasis is good for high bundling, curveCatmullRom for lower bundling
const lineGenerator = d3
.line()
.x((d) => d.x)
.y((d) => d.y)
.curve(beta > 0.5 ? d3.curveBasis : d3.curveCatmullRom.alpha(beta));

return lineGenerator(points);
}

// Now add the extra layer of edges with adjustable bundling
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", darkMode ? "normal" : "multiply")
.style("opacity", so)
.attr("d", ([i, o]) => {
try {
return createBundledPath(i, o, bundleBeta);
} catch (error) {
console.error("Error generating path:", error);
// Fallback to direct line if there's an error
return `M${i.x},${i.y} L${o.x},${o.y}`;
}
})
.each(function (d) {
d.path = this;
})
.attr("stroke", (d) =>
lighten(
myColor(
d[1].ancestors().find((ancestor) => ancestor.depth == colorDepth)
),
LighenFactor
)
);

console.log(
"Added extra layer of edges with adjustable bundling after simulation"
);

// Now add the labels after simulation is complete
labelGroup = svg
.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3);

// Add leaf nodes labels with proper orientation and anchoring
labelGroup
.selectAll("text")
.data(nodes)
.join("text")
.filter((d) => d.height == 0) // Only add labels for leaf nodes
// .attr("font-size", (d) => 10 + 0.05 * d.degree)
.attr("font-size", (d) => 8)
.attr("paint-order", "stroke")
.attr("stroke", "white")
.attr("fill", (d) => {
const col = myColor(
d.ancestors().find((ancestor) => ancestor.depth == colorDepth)
);
return col;
})
.text((d) => edges.find((n) => n.file == d.data.name)?.title)
.each(function (d) {
// Only position leaf nodes at the edge
const parent = d.parent;
if (!parent) return;

// Calculate angle between leaf and its parent (in radians)
const dx = d.x - parent.x;
const dy = d.y - parent.y;
const angle = Math.atan2(dy, dx);

// Calculate offset distance (adjust as needed)
// const offsetDistance = 15 + 0.1 * d.degree; // Base offset + adjustment for degree
const offsetDistance = 5; // Base offset + adjustment for degree

// Calculate offset position
const offsetX = d.x + Math.cos(angle) * offsetDistance;
const offsetY = d.y + Math.sin(angle) * offsetDistance;

// Calculate angle from vertical (0 to 2π)
// Convert from -π to π range to 0 to 2π range
const angleFromVertical = (angle + Math.PI * 2.5) % (Math.PI * 2);

// Set text-anchor based on angle from vertical
// Between 0 and π: start anchor (text sticks out right)
// Between π and 2π: end anchor (text sticks out left)
const textAnchor =
angleFromVertical >= 0 && angleFromVertical < Math.PI
? "start"
: "end";

// Adjust rotation so text is readable (not upside down)
let rotationAngle = angle * (180 / Math.PI); // Convert to degrees

// Keep text upright
if (rotationAngle > 90) rotationAngle -= 180;
if (rotationAngle < -90) rotationAngle += 180;

// Update text positioning and rotation
d3.select(this)
.attr("x", offsetX)
.attr("y", offsetY)
.attr("text-anchor", textAnchor)
.attr("transform", `rotate(${rotationAngle}, ${offsetX}, ${offsetY})`)
.attr("dy", ".35em"); // Vertical centering adjustment
});

// Add non-leaf node labels (just depth numbers) at their positions
// labelGroup
// .selectAll(".non-leaf-label")
// .data(nodes.filter((d) => d.height > 0)) // Only non-leaf nodes
// .join("text")
// .attr("class", "non-leaf-label")
// // .attr("font-size", (d) => d.children ? 0 : 10 + 0.05 * d.degree)
// .attr("font-size", (d) => (d.children ? 0 : 10))
// .attr("paint-order", "stroke")
// .attr("stroke", "white")
// .attr("text-anchor", "middle")
// .attr("x", (d) => d.x)
// .attr("y", (d) => d.y)
// .attr("dy", ".35em")
// .attr("fill", (d) => {
// const col = myColor(
// d.ancestors().find((ancestor) => ancestor.depth == colorDepth)
// );
// return col;
// })
// .text((d) => d.depth);

console.log("Added labels after simulation");
});

// Handle invalidation
invalidation.then(() => simulation.stop());

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
links
Type Table, then Shift-Enter. Ctrl-space for more options.

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
data
Type Table, then Shift-Enter. Ctrl-space for more options.

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