Public
Edited
Aug 10, 2023
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 = {
// add <= at the bottom to change the top label orentation

// parametres du diagramme
const off = 80; // offset des leaves
const offDNMN = 32; // offset des leaves
let scale = 2.5; // echelle globale
const sizeDepth0 = 28; // font size
const sizeDepth1 = 21;
const sizeDepth2 = 16;
const sizeHeight0 = 8.5;
const sizeHeight1 = 12;
const size = 12;
const bulletDepth0 = 2.5; // bullet size
const bulletDepth1 = 2;
const bullet = 2;
const bulletHeight1 = 1.5;
const bulletHeight0 = 1.5;
const translation = 1270 - 150;
const squircle2 = (t) => 1 + (1 / 8) * Math.sin(2 * t) * Math.sin(2 * t);
let a = 1;
const aKN = 1;
let b = 1;
const bKN = 1.4;
const squircle1 = (t) =>
a / Math.sqrt(Math.sqrt(1 - (1 / 2) * Math.sin(2 * t) * Math.sin(2 * t)));
const n = 100; //4;
const den = (t, n, a, b) =>
Math.pow(Math.cos(t) / a, 2 * n) + Math.pow(Math.sin(t) / b, 2 * n);
const rectellipse = (t, a, b) => 1 / Math.pow(den(t, n, a, b), 1 / (2 * n));
const squircle = (t) => rectellipse(t / 1, a, b);
const divisorKN = 2;
const squircleKN = (t) => rectellipse(t / 2, a, b);

// parametres angulaires
// let divisor = 1.035 + 0.008 - 0.004 + 0.005; // division of the full circle // 1.8
// let angleDeg = -177.3 + 1.4 - 0.6 + 0.8; // angle offset from midday position // -10 is interesting
// let angleRad = (Math.PI / 180) * angleDeg;
// let sepDeg = 2;
// let sepRad = (Math.PI / 180) * sepDeg;
let divisor = 1; //1.035 + 0.008 - 0.004 - 0.007; // division of the full circle // 1.8
let angleDeg = 90; //+ 3.3; // angle offset from midday position // -10 is interesting
let angleRad = (Math.PI / 180) * angleDeg;
let sepDeg = 0.2;
let sepRad = (Math.PI / 180) * sepDeg;
// ajustements fins du diagramme

const root = tree(data);
// const str = "vaggasaṁyutta";
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 == 1 || d.height == 0) d.y = 600;
// if (d.height == 2) d.y = 490;
//if (d.height == 1) d.y = 800;
if (d.height == 2) d.y = heightSN;
// if (d.height == 3) d.y = 280;
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 == 1) d.data.name = d.data.name.replace(str, "");
if (d.depth == 0) d.x = (-Math.PI * 1.1 - angleRad) * divisor + 2 * Math.PI;
//if (d.depth == 0) d.x = 0;
//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; // could be calculated exaclty fo kn branch
d.x = (Math.PI / 2 - angleRad) * divisor;
}
if (d.data.id == "test2") {
d.y = 340 + 60 + 60; // could be calculated exaclty fo kn branch
// d.y = 340 + 60 - 30 + 200; // could be calculated exaclty fo kn branch
d.x = (Math.PI / 2 - angleRad) * divisor;
}
});

// rotation

// const rot = -180;
// const rotRad = (rot * Math.PI) / 180;
// root.each((d) => (d.x += rotRad));
// root.each((d) => (d.x %= 2 * Math.PI));

// testing

// console.log(root);

// fonction d'apartenance à DN ou MN (utile pour les acros)

const isDNMN = (d) =>
d.data.acro?.includes("DN") || d.data.acro?.includes("MN");

// fonction pour la taille de font

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

// fonction pour la taille des bullets

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

// Fonction qui retourne le nom de l'ancestre de depth 1
// utile pour la colorisation

let oneDepth = (d, source = false) => {
let ancestors = {};
source == true
? (ancestors = d.target.ancestors()) // source <=> target for a different result
: (ancestors = d.ancestors());

const oneDepth = ancestors.find((d) => d.depth == 1);
const result = oneDepth == null ? d.data.name : oneDepth.data.name;
// for some reason it dosen't start with sutta pitaka anymore
return result;
// return oneDepth?.data.name;
};

let color = d3.scaleOrdinal(domain);
// let colorFaded = d3.scaleOrdinal(domainFaded);

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 + "";
};

// création du diagramme //////////////////////////////////////////////////////////////////////////////////////////////

const svg = d3.create("svg");

const g = svg.append("g").attr("cursor", "grab");

const AH = 210 * frameRatio,
AW = 297 * frameRatio;

g.append("rect")
.attr("x", translation / 2 - AW / 2 - 10)
.attr("y", -AH / 2)
.attr("width", AW)
.attr("height", AH)
.attr("stroke", frame ? "black" : "white")
.attr("fill", "transparent");

// paths

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 * squircle(d.x))
)
.attr("stroke", (d) => color(oneDepth(d, true))); // colorisation

// bullet

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 * squircle(d.x)},0)
`
)
.attr("r", (d) => bulletSize(d))
.attr("fill", (d) =>
isLeaf(d) ? fade(color(oneDepth(d))) : color(oneDepth(d))
); // colorisation

// text

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))) // colorisation
.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 * squircle(d.x)},0)
rotate(${
d.x > (Math.PI - angleRad) * divisor &&
d.x < (2 * Math.PI - angleRad) * divisor
? 180
: 0
// 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; // not usefull for KN
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) {
// const link = "https://suttacentral.net/" + i.data.id + "/pli/ms";
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");

////////////////////////////////////////////////////// KN chart //////////////////////////////////////////////////////

/// add chart2 methode 1 ///

// const svg2 = chart2.node().innerHTML;
// g.append("g").attr("transform", "translate(0,1300)").html(svg2);

/// add chart2 methode 2 /// (to allow use of .on("click"))

(a = aKN), (b = bKN);

scale = 3.5;

divisor = divisorKN; // division of the full circle // 1.8
angleDeg = -0; //-30; // angle offset from midday position // -10 is interesting
angleRad = (Math.PI / 180) * angleDeg;
sepRad = 0;

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 != "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.x = (Math.PI / 2 - angleRad) * divisor;
if (d.depth == 0) d.y = -9 * 10; //- 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.includes("cnd") && d.height == 2) d.y = 490 + 100;
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.includes("cp") && d.height == 3) d.y = 490;
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 = "";
// if (d.data.id.includes("ne") && d.height == 3) d.y = 490;
// if (data.id.slice(0, 3) == "cnd" && d.height == 1) d.y = 490 + 230 + 30;
});

// fonction pour la taille de font

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()) // source <=> target for a different result
: (ancestors = d.ancestors());

const oneDepth = ancestors.find((d) => d.depth == 0);
const result = oneDepth == null ? d.data.name : oneDepth.data.name;
// for some reason it dosen't start with sutta pitaka anymore
return result;
// return oneDepth?.data.name;
};

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;
// if (d.height )
return bool;
};

// fonction pour la taille des bullets

bulletSize = (d) => {
if (d.depth == 0) {
return bulletDepth1;
} else if (d.depth == 1) {
return bulletDepth1;
} else {
return bulletHeight0;
}
};

// const color = () => col1;
color = d3.scaleOrdinal(domain);
color("1");
color("2");
color("3");
color("4");
color("5");
color("6");

//////////////////// diagrame KN ////////////////

const g2 = g
.append("g")
.attr("transform", "translate(" + translation + ",0)");

// paths

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 * squircleKN(d.x))
)
.attr("stroke", (d) => color(oneDepth(d, true))); // colorisation

// bullet

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 * squircleKN(d.x)},0)
`
)
.attr("fill", (d) => (d.children ? "#555" : "#999"))
.attr("r", (d) => bulletSize(d))
.attr("fill", (d) => color(oneDepth(d))) // colorisation
// .attr("opacity", (d) => (isLeaf(d) ? 0.5 : 1)); // colorisation
.attr("fill", (d) =>
isLeaf(d) ? fade(color(oneDepth(d))) : color(oneDepth(d))
); // colorisation

// text
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))) // colorisation
.attr(
"transform",
(d) => `
rotate(${(d.x * 180) / divisor / Math.PI - 90 + angleDeg})
translate(${d.y * squircleKN(d.x)},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; // not usefull for KN
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");

////////////////////////////////////////////////////// End of KN chart ///////////////////////////////////////////////
////////////////////////////////////////////////////// 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));

if (license) {
// svg
// .append("svg:image")
// .attr("x", xLicense - 88)
// .attr("y", yLicense - 65)
// // .attr("width", 88)
// // .attr("height", 31)
// .attr(
// "xlink:href",
// // "https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg"
// await FileAttachment("cc-zero.svg").url()
// );
svg
.append("g")
.attr("transform", `translate(${xLicense - 88}, ${yLicense - 65})`)
.html(logo);

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("DhammaCharts.org")
.attr("text-anchor", "end")
.attr("dy", "-1.1em")
.clone(true)
.text("SuttaCentral.net")
.attr("dy", "0.25em");
// .clone(true)
// .text("No Right reserved")
// .attr("dy", "1.6em");
}

if (licenseNoLogo) {
// svg
// .append("svg:image")
// .attr("x", xLicense - 88)
// .attr("y", yLicense - 65)
// // .attr("width", 88)
// // .attr("height", 31)
// .attr(
// "xlink:href",
// // "https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg"
// await FileAttachment("cc0gris@3.svg").url()
// );

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

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

// zoom

if (zoompan) {
g.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);

// zooming + pan;

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();
}
Insert cell
Insert cell
FileAttachment("cc0gris@3.svg")
Insert cell
FileAttachment("cc0gris@3.svg").html().innerHTML
Insert cell
// {
// var wrapper = document.createElement("div");
// wrapper.innerHTML = logoHTML;
// return wrapper.firstChild;
// }
Insert cell
// logoHTML = fetch(
// "https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg"
// )
// .then((response) => response.text())
// // .then((svg) => document.body.insertAdjacentHTML("afterbegin", svg))
Insert cell
Insert cell
Insert cell
Insert cell
// chart2.attr("viewBox", autoBox).node()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
domain2 = [col5, col2, col3, col4, col1, col6]
Insert cell
d3.scaleOrdinal(domain)("test")
Insert cell
domain = colors2
Insert cell
domainFaded = colors2Faded
Insert cell
Insert cell
// the old color funciton


Insert cell
function autoBox() {
document.body.appendChild(this);
const {x, y, width, height} = this.getBBox();
document.body.removeChild(this);
return [x, y, width, height];
}
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
data = {
return d3.stratify()(SuttaPitakaTree);
}
Insert cell
data2 = {
return d3.stratify()(kn3);
}
Insert cell
// data.each((d) => console.log(d.data.name))
Insert cell
import { SuttaPitakaTree } from "eb4984f02c934460"
//import { SuttaPitakaTree3 } from "eb4984f02c934460"
Insert cell
import { kn3 } from "eb4984f02c934460"
Insert cell
width = 975
Insert cell
height = 600
Insert cell
radius = width / 2
Insert cell
tree = d3.cluster().size([2 * Math.PI, radius - 100])
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
import { colorPicker } from "@shaunlebron/color-picker"
Insert cell
colors2 = subdivs(72, 378, divs).map((x) => color2(x + colorShift, s, l))
Insert cell
colors2Faded = subdivs(72, 378, divs).map((x) => color2(x + colorShift, s, l))
Insert cell
color2 = (h, s = 50, l = 50) => "hsl(" + h + "," + s + "%," + l + "%)"
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

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