Public
Edited
Oct 13, 2022
Fork of Nibbana II
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
off = 0.6
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Function that create a HTML layer arc of multile labels with options.
// data contains the angles, text, font-size & letter-spacing...

drawRing = (svgOrigin, ring) => {
// Deconstruct object param

const {
cx = 0,
cy = 0,
r = 100,
innerR = 20,
padAngle = 1,
color = "black",
data,
strokeWidth = 2,
fontSize = 12,
fontName = "Cormorant SC",
letterSpace = 0,
fade = 0,
fadeLimit = 60,
opacity = 1,
opacityLimit = 0.5,
bold = true,
darkenFactor = 0,
radial = false
} = ring;

const halfPI = Math.PI / 2;

// Color functions

const fading = (color, f) => {
const { l, c, h } = d3.lch(color);
return d3.lch(l + f, c, h);
};
const darken = (color, f) => {
const { l, c, h } = d3.lch(color);
return d3.lch(l - f, c, h);
};

// Function to konw if we are at the bottom

const atBottom = (d) => {
const middle =
((Math.PI / 180) * d.startAngle -
halfPI +
(Math.PI / 180) * d.endAngle -
halfPI) /
2;
const bottom = middle >= 0 && middle < Math.PI;
return bottom ^ d.flipLetters; // XOR
};

// Define parameters if global or local in data

const dFlipLetters = (d) =>
d.flipLetters != undefined ? d.flipLetters : false;
const dColor = (d) => (d.color != undefined ? d.color : color);
const dLetterSpace = (d) =>
d.letterSpace != undefined ? d.letterSpace : letterSpace;
const dStrokeWidth = (d) =>
d.strokeWidth != undefined ? d.strokeWidth : strokeWidth;
const dfade = (d) => (d.fade != undefined ? d.fade : fade);
const dopacity = (d) => (d.opacity != undefined ? d.opacity : opacity);
const dRadial = (d) => (d.radial != undefined ? d.radial : radial);
const dDarkenFactor = (d) =>
d.darkenFactor != undefined ? d.darkenFactor : darkenFactor;
const dBottomAjust = (d) => (d.bottomAjust != undefined ? d.bottomAjust : 0);
const dTopAjust = (d) => (d.topAjust != undefined ? d.topAjust : 0);

// Create ring

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

const arc = d3
.arc()
.innerRadius(r)
.outerRadius(r + innerR)
.padAngle((Math.PI / 180) * padAngle)
.startAngle((d) => (Math.PI / 180) * d.startAngle)
.endAngle((d) => (Math.PI / 180) * d.endAngle);

const enter = (enter) => {
// Move to center
const g = enter
.append("g")
.attr("class", "arcLabel")
.attr("transform", `translate(${cx}, ${cy})`);

// Create Arc and fill
g.append("path")
.attr("id", (d) => d.uid.id)
.style("fill", "none")
.attr("d", (d) => {
const context = d3.path();
if (atBottom(d)) {
context.arc(
0,
0,
r,
(Math.PI / 180) * d.endAngle - halfPI,
(Math.PI / 180) * d.startAngle - halfPI,
true
);
} else {
context.arc(
0,
0,
r,
(Math.PI / 180) * d.startAngle - halfPI,
(Math.PI / 180) * d.endAngle - halfPI,
false
);
}
return context.toString();
});
g.append("path")
.style("stroke", (d) => dColor(d))
.style("stroke-width", (d) => dStrokeWidth(d))
.style("fill", (d) => fading(dColor(d), dfade(d)))
.style("fill-opacity", (d) =>
!darkMode ? dopacity(d) : dopacity(d) > opacityLimit ? dopacity(d) : 0
)
.attr("d", (d) => arc(d));
// .clone()
// .lower()
// .style("fill", "white")
// .style("fill-opacity", 1);

// Create Text

// if radial

function labelTransform(d) {
const x = (d.endAngle + d.startAngle) / 2 - 90;
return `rotate(${x}) translate(${r + innerR / 2},0) rotate(${
x < 180 - 90 ? 0 : 180
})`;
}

g.append("text")
.style("fill", (d) =>
dopacity(d) < opacityLimit || dfade(d) > fadeLimit
? darken(dColor(d), dopacity(d) * dDarkenFactor(d))
: "white"
)
.style("font-size", (d) => (d.fontSize ? d.fontSize : fontSize))
.style("font-weight", bold ? "bold" : "normal")
.attr("font-family", fontName)
.attr("dominant-baseline", "middle")
.attr("text-anchor", "middle")
.text((d) => d.text)
.attr("transform", (d) => labelTransform(d))
.attr("display", (d) => (dRadial(d) ? "true" : "none"));

// if not radial

g.append("text")
.style("fill", (d) =>
dopacity(d) < opacityLimit || dfade(d) > fadeLimit
? darken(dColor(d), dopacity(d) * dDarkenFactor(d))
: darkMode
? "black"
: "white"
)
.style("font-size", (d) => (d.fontSize ? d.fontSize : fontSize))
.style("font-weight", bold ? "bold" : "normal")
.attr("font-family", fontName)
.attr("dy", (d) => (innerR / 2) * (atBottom(d) ? +1 : -0.9))
.attr("dominant-baseline", "middle")
.attr("text-anchor", "middle")
.append("textPath")
.attr("startOffset", "50%")
.attr("xlink:href", (d) => d.uid.href)
.text((d) => {
let rawText = d.text;
if (!rawText.includes("\n")) {
return dLetterSpace(d)
? rawText.split("").join("\u200A".repeat(dLetterSpace(d)))
: rawText;
}
})
.select(function () {
return this.parentNode;
})
.attr("display", (d) => (dRadial(d) ? "none" : "true"))

// In case of \n in text
.clone()
.attr(
"dy",
(d) =>
(innerR / 2) *
(atBottom(d) ? +0.7 + dBottomAjust(d) : -1.3 + dTopAjust(d))
)
.append("textPath")
.attr("startOffset", "50%")
.attr("xlink:href", (d) => d.uid.href)
.text((d) => {
let rawText = d.text;
if (rawText.includes("\n")) {
let arrText = rawText.split("\n");
return dLetterSpace(d)
? arrText[0].split("").join("\u200A".repeat(dLetterSpace(d)))
: arrText[0];
}
})
.select(function () {
return this.parentNode;
})
.attr("display", (d) => (dRadial(d) ? "none" : "true"))

.clone()
.attr(
"dy",
(d) =>
(innerR / 2) *
(atBottom(d) ? +1.5 + dTopAjust(d) : -0.55 + dBottomAjust(d))
)
.append("textPath")
.attr("startOffset", "50%")
.attr("xlink:href", (d) => d.uid.href)
.text((d) => {
let rawText = d.text;
if (rawText.includes("\n")) {
let arrText = rawText.split("\n");
return dLetterSpace(d)
? arrText[1].split("").join("\u200A".repeat(dLetterSpace(d)))
: arrText[1];
}
})
.attr("display", (d) => (dRadial(d) ? "none" : "true"));
};
data.forEach((d) => (d.uid = DOM.uid("p-" + d.index)));

svg.selectAll("g.arcLabel").data(data).join(enter);
const svgRing = svg.node().innerHTML;
svgOrigin.append("g").html(svgRing);
}
Insert cell
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