Public
Edited
Dec 29
Fork of New DrawRing
Importers
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

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
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
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
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
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
drawRing = (svgOrigin, ringParam) => {
const defaultParam = {
// Geometry //
cx: 0, // Angles are mesured from top like a clock in deg
cy: 0,
r: 100,
innerR: 20,
padAngle: 1, // Angle Padding (actually work linear)
startPadAngle: null, // used with customD3 old one not used here...
endPadAngle: null, // https://observablehq.com/@cittadhammo/the-dhamma-citadelneed uncomment at the arc() function
padRadius: null, // arcline = padRadius x padAngle (default sqrt(innerRadius * innerRadius + outerRadius * outerRadius))
padLinear: false, // Linear Padding (not used)
cornerRadius: 0,

// Aspect //
radial: false, // Direction of text
strokeWidth: 0.6,
strokeColor: false,
strokeFade: false,
fontSize: 12,
lineHeight: 0,
fontName: selectedFont,
letterSpacing: 0,
flipLetters: false, // Upside Down
flipLines: false, // Top & Bottom
bold: selectedFont != "Cormorant SC" ? 400 : 700,
bottomAdjust: 0, // Line Height
topAdjust: 0, // Line height

// Coloring //
color: "black",
fade: 0,
fadeLimit: 30, // Turn the background dark and letters white
opacity: 1,
opacityLimit: 0.8, // same (could have a variable switchColor)
darkenFactor: 0,
opaque: true, // Add white background behind ring
gradientID: false,
gradientR: 50,
gradientI: 0,
gradientX: 50,
gradientY: 50,
gradientFX: null,
gradientFY: null,
invertGradient: false,

// Divisions //
div: 1, // |-endsPadAngle-|-1-|-2-|-n-|-4-|-div-|-endsPadAngle-|
n: 1, // Segment num
gPadAngle: 0, // parallel end-padding for the group (this changes the center of the arc... -_-) needs custom d3
gPadRadius: null, // arcline = padRadius x padAngle (default sqrt(innerRadius * innerRadius + outerRadius * outerRadius))
startGPadAngle: null,
endGPadAngle: null,
//gR: 0,
// carefull padAngle at ends of group still needs padAngle
endsPadAngle: 0, // Angle End Padding
endsPadLinear: false, // Linear End Padding
startRatio: 0, // |sR*L|-1-|-2-|-n-|-4-|-div-|L-eR*L|
endRatio: 1, // L : arcLength/square side depends on R and angles
padRatio: 0, // |sR*L|-startPadRatio-|-text-|-endPadRatio-|L-eR*L|
startPadRatio: false, // padRatio by default
endPadRatio: false, // padRatio by default

// Square Style //
square: false,
squareEnds: false,
centerRadius: true, // Radius is mesured from origin to center of cells.
centerInnerR: true, // Inner Radius is mesured from center of cells.
innerSide: false, // length of inner side, overwrite angles and padAngle
padSquare: 0, // Padding between div cells
tilted: false, // Angle of cells wall (not used)
arrowEnd: false, // end in an arrow
arrowStart: false,
arrowFactor: 1,

// Polygone Coloring //
polyId: false, // Identify the poly
polyPad: 0, // inner Padding
polyOpacity: 1,
polyFade: 0,
polyOpaque: true,
centerText: "",
up: 0
};

const d3 = customD3; // customD3old;

// Global Constants & function
const pi = Math.PI;
const dTR = (deg) => (pi / 180) * deg; // deg to rad
const rTD = (rad) => (180 / pi) * rad; // rad to deg
const modPi = (angle) => angle % (2 * pi);
const sin = (x) => Math.sin(x);
const cos = (x) => Math.cos(x);
const atan = (x) => Math.atan(x);
const sqrt = (x) => Math.sqrt(x);

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

// Create Ring
const svg = d3.create("svg");

// For Filling Polygone later
let arrPoints = [];
let d2 = {}; // alternative data point

const background = svg.append("g").attr("id", "background");

ringParam.data.forEach((dOrigin, index) => {
// d will be our main data element
const structuredClone = (val) => JSON.parse(JSON.stringify(val));
const d = structuredClone(dOrigin);

// Index for adding text to arc
const id = DOM.uid("p-" + d.index);

// define local parameter for each data according to priority
// d.origin (data) > ring Param > Default Param
for (const p in defaultParam) {
dOrigin[p] != undefined
? (d[p] = dOrigin[p])
: ringParam[p] != undefined
? (d[p] = ringParam[p])
: (d[p] = defaultParam[p]);
}

if (check.includes("outlineOnly")) (d.opacity = 0), (d.polyOpacity = 0);

// All to Rad !
d.startAngle = dTR(d.startAngle);
d.endAngle = dTR(d.endAngle);
d.padAngle = dTR(d.padAngle);
d.gPadAngle = dTR(d.gPadAngle);
d.startPadAngle = dTR(d.startPadAngle);
d.endPadAngle = dTR(d.endPadAngle);
d.endsPadAngle = dTR(d.endsPadAngle);

// Definition
let a = d.startAngle,
b = d.endAngle,
c = (b - a) / 2;

if (d.endsPadLinear)
d.endsPadAngle = Math.abs(
Math.atan(Math.tan(c) - d.endsPadLinear / d.r) - c
);
a = d.startAngle + d.endsPadAngle;
b = d.endAngle - d.endsPadAngle;
c = (b - a) / 2;

let e = (a + b) / 2,
R = d.r,
Rs = R * cos(c),
r = d.innerR,
rs = r * cos(c);
const div = d.div,
n = d.n,
polyPad = d.polyPad;

// by default d.padRatio
d.startPadRatio = d.startPadRatio !== false ? d.startPadRatio : d.padRatio;
d.endPadRatio = d.endPadRatio !== false ? d.endPadRatio : d.padRatio;

// CenterText
if (d.centerText != "") {
d.text = "";
d.startAngle = 0;
d.endAngle = 0;
d.strokeWidth = 0;
d.opacity = 0;
d.opaque = false;
}

// with group padding NEEDS CUSTOM D3
let sgp = d.startGPadAngle === null ? d.gPadAngle : dTR(d.startGPadAngle),
egp = d.endGPadAngle === null ? d.gPadAngle : dTR(d.endGPadAngle),
rp = d.gPadRadius ? d.gPadRadius : sqrt(r * r + R * R);

sgp = rp * sin(sgp);
egp = rp * sin(egp);

const ca = sgp == 0 ? 0 : atan(sin(b - a) / (egp / sgp + cos(b - a))),
gr = sgp == 0 ? egp / sin(b - a) : sgp / sin(ca);
if (sgp != 0 || egp != 0)
(d.cx += gr * sin(a + ca)),
(d.cy -= gr * cos(a + ca)),
(d.gcx = -d.cx),
(d.gcy = -d.cy),
(R -= gr);

// console.log(d.text, a, ca, R, gr, r, rp, sgp, egp);
// OuterSquare
if (d.square) {
if (d.centerRadius) R = R / cos(c);
if (d.innerSide) {
Rs = R * cos(c);
R = Math.sqrt((d.innerSide * d.innerSide) / 4 + Rs * Rs);
a = e - Math.atan(d.innerSide / 2 / Rs);
b = e + Math.atan(d.innerSide / 2 / Rs);
c = (b - a) / 2;
}
if (d.centerInnerR) r = r / cos(c);
Rs = R * cos(c);
rs = r * cos(c);
} else {
// Calculate angles when division is made
const thetaDiv = (b - a) / div,
thetaStart = a + (n - 1) * thetaDiv,
thetaEnd = a + n * thetaDiv;

a = thetaStart;
b = thetaEnd;
c = (b - a) / 2;
e = (a + b) / 2;
}

// At the bottom ?
e = modPi(e);
const atBottom = e > pi / 2 && e < (3 * pi) / 2,
atBottom2 = e >= pi / 2 - 0.01 && e <= (3 * pi) / 2 + 0.01,
atLeft = e > pi && e < 2 * pi;

// Start drawing here
const g = svg
.append("g")
.attr("class", "arcLabel")
.attr("transform", `translate(${d.cx}, ${d.cy})`);

// Gradient //
//Append a defs (for definition) element to your SVG
const defs = g.append("defs");

//Append a radialGradient element to the defs and give it a unique id
const radialGradient = defs
.append("radialGradient")
.attr("id", "radial-gradient-" + d.gradientID)
.attr("cx", d.gradientX + "%") //The x-center of the gradient
.attr("cy", d.gradientY + "%") //The y-center of the gradient
.attr("r", d.gradientR + "%"); //The radius of the gradient

if (d.gradientFX) radialGradient.attr("fx", d.gradientFX + "%"); //The x-center of the gradient
if (d.gradientFY) radialGradient.attr("fy", d.gradientFY + "%"); //The x-center of the gradient

//Add colors to make the gradient
radialGradient
.append("stop")
.attr("offset", d.gradientI + "%")
.attr(
"stop-color",
d.invertGradient ? fading(d.color, d.fade) : "#FFFFFF"
);
radialGradient
.append("stop")
.attr("offset", "100%")
.attr(
"stop-color",
d.invertGradient ? "#FFFFFF" : fading(d.color, d.fade)
);
//radialGradient.attr("circle at 10%");
// SQUARE SHAPE //
// Square Calculation (y axis is inverted & angle start at the top !)
const OA00x = R * sin(a),
OA00y = R * -cos(a),
OAddx = R * sin(b),
OAddy = R * -cos(b),
OB00x = (R + r) * sin(a),
OB00y = (R + r) * -cos(a),
OBddx = (R + r) * sin(b),
OBddy = (R + r) * -cos(b);

const OA0x =
OA00x + (d.startRatio + d.padAngle + d.startPadRatio) * (OAddx - OA00x),
OA0y =
OA00y + (d.startRatio + d.padAngle + d.startPadRatio) * (OAddy - OA00y),
OAdx =
OA00x + (d.endRatio - d.padAngle - d.endPadRatio) * (OAddx - OA00x),
OAdy =
OA00y + (d.endRatio - d.padAngle - d.endPadRatio) * (OAddy - OA00y);

const OA0xP = (R - polyPad) * sin(a),
OA0yP = (R - polyPad) * cos(a),
OAdxP = (R - polyPad) * sin(b),
OAdyP = (R - polyPad) * cos(b);

const OAix = (i) => (1 - i / div) * OA0x + (i / div) * OAdx,
OAiy = (i) => (1 - i / div) * OA0y + (i / div) * OAdy,
OAixP = (i) => (1 - i / div) * OA0xP + (i / div) * OAdxP,
OAiyP = (i) => (1 - i / div) * OA0yP + (i / div) * OAdyP;

const OB0tx = OA0x + OB00x - OA00x,
OB0ty = OA0y + OB00y - OA00y,
OB0txP = OA0xP + OB00x - OA00x,
OB0tyP = OA0yP + OB00y - OA00y,
OBdtx = OAdx + OBddx - OAddx,
OBdty = OAdy + OBddy - OAddy;

const OBix = (i) => (1 - i / div) * OB0tx + (i / div) * OBdtx,
OBiy = (i) => (1 - i / div) * OB0ty + (i / div) * OBdty,
A0B0x = (1 / 2) * (OB0tx - OA0x + OBdtx - OAdx),
A0B0y = (1 / 2) * (OB0ty - OA0y + OBdty - OAdy),
A0A1x = OAix(1) - OA0x,
A0A1y = OAiy(1) - OA0y,
px = (A0A1x * d.padSquare) / 100,
py = (A0A1y * d.padSquare) / 100;

if (d.square) {
// Square Path
g.append("path")
.attr("display", d.square ? "" : "none")
.attr(
"d",
//d3.line().curve(d3.curveCardinalClosed.tension(0.8))([
d3.line().curve(d3.curveLinearClosed)([
// first point
n <= 1
? [OAix(n - 1), OAiy(n - 1)]
: [OAix(n - 1) + px, OAiy(n - 1) + py],
// second start arrow point
d.squareEnds || d.arrowStart
? [
OAix(n - 1) +
(1 / 2) * A0B0x -
(1 / 2) * A0B0y * (d.arrowStart ? 1 : 0) * d.arrowFactor +
px * (n <= 1 ? 0 : 1),
OAiy(n - 1) +
(1 / 2) * A0B0y +
(1 / 2) * A0B0x * (d.arrowStart ? 1 : 0) * d.arrowFactor +
py * (n <= 1 ? 0 : 1)
]
: n <= 1 // copy of first point
? [OAix(n - 1), OAiy(n - 1)]
: [OAix(n - 1) + px, OAiy(n - 1) + py],
// third point
n <= 1
? d.squareEnds || d.arrowStart
? [OAix(n - 1) + A0B0x, OAiy(n - 1) + A0B0y]
: [OB0tx, OB0ty]
: [OAix(n - 1) + A0B0x + px, OAiy(n - 1) + A0B0y + py],
// fourth point
n == div
? d.squareEnds || d.arrowEnd
? [OAix(n) + A0B0x, OAiy(n) + A0B0y]
: [OBdtx, OBdty]
: [OAix(n) + A0B0x - px, OAiy(n) + A0B0y - py],
// fifth end arrow point
d.squareEnds || d.arrowEnd
? [
OAix(n) +
(1 / 2) * A0B0x -
(1 / 2) * A0B0y * (d.arrowEnd ? 1 : 0) * d.arrowFactor -
px * (n == div ? 0 : 1),
OAiy(n) +
(1 / 2) * A0B0y +
(1 / 2) * A0B0x * (d.arrowEnd ? 1 : 0) * d.arrowFactor -
py * (n == div ? 0 : 1)
]
: n == div // copy of fourth point
? [OAix(n), OAiy(n)]
: [OAix(n) - px, OAiy(n) - py],
// sixth point
n == div ? [OAix(n), OAiy(n)] : [OAix(n) - px, OAiy(n) - py]
])
)
.style("stroke", d.stokeColor ? d.strokeColor : d.color)
.style("stroke-width", d.strokeWidth)
.style("fill", fading(d.color, d.fade))
.style("fill-opacity", d.opacity)
.clone()
.lower()
.style("fill", d.opaque ? "white" : "")
.style("fill-opacity", d.opaque ? 1 : 0);
}

// fill polygone
if (
d.polyId != d2.polyId ||
(index == ringParam.data.length - 1 && d.polyId)
) {
if (index == ringParam.data.length - 1 && d.polyId)
arrPoints.push([OAixP(1), OAiyP(1)]);

// Gradient //

//Append a radialGradient element to the defs and give it a unique id
const radialGradient = defs
.append("radialGradient")
.attr("id", "radial-gradient-" + d2.gradientID)
.attr("cx", d2.gradientX + "%") //The x-center of the gradient
.attr("cy", d2.gradientY + "%") //The y-center of the gradient
.attr("r", d2.gradientR + "%"); //The radius of the gradient

//Add colors to make the gradient
radialGradient
.append("stop")
.attr("offset", d2.gradientI + "%")
.attr("stop-color", d2.invertGradient ? d2.color : "#FFFFFF");
radialGradient
.append("stop")
.attr("offset", "100%")
.attr("stop-color", d2.invertGradient ? "#FFFFFF" : d2.color);

background
.append("g")
.append("path")
.attr("transform", `translate(${d2.cx}, ${d2.cy}) rotate(180)`)
.attr("d", d3.line().curve(d3.curveLinearClosed)(arrPoints))
.style("stroke", d2.color)
.style("stroke-width", d2.strokeWidth)
.style(
"fill",
d2.gradientID
? "url(#radial-gradient-" + d2.gradientID + ")"
: fading(d2.color, d2.polyFade)
)
.style("fill-opacity", d2.polyOpacity)
.clone()
.lower()
.style("fill", d2.polyOpaque ? "white" : "")
.style("fill-opacity", d2.polyOpaque ? 1 : 0);

arrPoints = [];
}
// arrPoints.push([OB0txP, OB0tyP]); // outside of polygone
arrPoints.push([OAixP(1), OAiyP(1)]);

for (const p in d) {
d2[p] = d[p];
}

// Adding Center Text for polygone
const centerText = g
.append("text")
.style("font-weight", d.bold)
.style("fill", darken(d.color, d.darkenFactor))
.style("fill", check.includes("outlineOnly") ? black : "white")
.attr("font-family", d.fontName)
.attr("font-size", d.fontSize);

const titleLines = d.centerText.split("//");

if (titleLines.length == 1)
// one line break
centerText
.text(d.centerText)
.attr("text-anchor", "middle")
.attr("dy", -d.up + "em");

if (titleLines.length == 2)
// one line break
centerText
.text(titleLines[0])
.attr("text-anchor", "middle")
.attr("dy", "-0.8" - d.up + "em")
.clone(true)
.text(titleLines[1])
.attr("dy", "0.6" - d.up + "em");

if (titleLines.length == 3)
// two line breaks
centerText
.text(titleLines[0])
.attr("text-anchor", "middle")
.attr("dy", -1.2 - d.up + "em")
.clone(true)
.text(titleLines[1])
.attr("dy", 0.1 - d.up + "em")
.clone(true)
.text(titleLines[2])
.attr("dy", 1.4 - d.up + "em");

// Function that split lines and add spacing
const lines = d.text?.split("//");
const numberOfLines = lines?.length;
const line = (d, n) =>
numberOfLines > 1
? lines[n - 1].split("").join("\u200A".repeat(d.letterSpacing))
: d.text?.split("").join("\u200A".repeat(d.letterSpacing));

// Arc //

// Arc (!square) cases where lines needs:
// - flipping: radial && !atLeft
// - reverse path: !radial && atBottom
// - lower dy: !radial && atBottom

if (!d.square) {
g.append("path")
.style(
"stroke",
d.strokeColor
? d.strokeFade
? fading(d.strokeColor, d.strokeFade)
: d.strokeColor
: d.strokeFade
? fading(d.color, d.strokeFade)
: d.color
)
.style("stroke-width", d.strokeWidth)
// .style("fill", fading(d.color, d.fade))
.style(
"fill",
d.gradientID
? "url(#radial-gradient-" + d.gradientID + ")"
: fading(d.color, d.fade)
)
.style("fill-opacity", d.opacity)
.attr(
"d",
d3
.arc()
.innerRadius(R)
.outerRadius(R + r)
.altCX(d.gcx)
.altCY(d.gcy)
//.gR(gr)
.padAngle(d.padAngle)
// .startPadAngle(d.startPadAngle) // old custom
// .endPadAngle(d.endPadAngle)
.startAngle(a)
.endAngle(b)
.cornerRadius(d.cornerRadius)
.padRadius(d.padRadius)
)
.clone()
.lower()
.style("fill", d.opaque ? "white" : "")
.style("fill-opacity", d.opaque ? 1 : 0);

if (!d.radial) {
// If Arc not radial
// Adding Arc path for text
g.append("path")
.attr("id", id)
.style("fill", "none")
.attr("d", () => {
const context = d3.path();
context.arc(
0,
0,
R,
(atBottom ? b : a) - pi / 2,
(atBottom ? a : b) - pi / 2,
atBottom
);
return context.toString();
});

g.append("text")
.style(
"fill",
d.opacity < d.opacityLimit || d.fade > d.fadeLimit
? darken(d.color, d.opacity * d.darkenFactor)
: "white"
)
.style("font-size", d.fontSize)
.style("font-weight", d.bold ? d.bold : "normal")
.attr("font-family", d.fontName)
.attr("dy", (d.innerR / 2) * (atBottom ? +1 : -0.9))
.attr("dominant-baseline", "middle")
.attr("text-anchor", "middle")
.append("textPath")
.attr("startOffset", "50%")
.attr("xlink:href", "#" + id)
.text(numberOfLines == 1 ? line(d, 1) : "")
.select(function () {
return this.parentNode;
})
.clone()
.attr(
"dy",
(d.innerR / 2) *
(atBottom ? +0.7 + d.bottomAdjust : -1.3 + d.topAdjust)
)
.append("textPath")
.attr("startOffset", "50%")
.attr("xlink:href", "#" + id)
.text(numberOfLines > 1 ? line(d, 1) : "")
.select(function () {
return this.parentNode;
})
.clone()
.attr(
"dy",
(d.innerR / 2) *
(atBottom ? +1.5 + d.topAdjust : -0.55 + d.bottomAdjust)
)
.append("textPath")
.attr("startOffset", "50%")
.attr("xlink:href", "#" + id)
.text(numberOfLines > 1 ? line(d, 2) : "");
}
}

// Square Style or Radial
if (d.radial || d.square) {
function labelTransform(d, twoLines = 0) {
// 0 middle, 1 up, 2 down

// For arc radial transform
const f = modPi(e - pi / 2); // clockwise from base
const df = c / 3;
const f2 =
twoLines == 0 // Middle Line
? f
: twoLines == 1 // Top Line
? d.flipLines ^
!atLeft ^
atBottom ^
(atBottom && !atLeft) ^
(!atBottom && atLeft) // sorry for that but I have no time to think about it, it works
? f + df
: f - df
: d.flipLines ^
!atLeft ^
atBottom ^
(atBottom && !atLeft) ^
(!atBottom && atLeft) // Bottom Line
? f - df
: f + df;

// For square transformations
const cornerX = OAix(n - 1);
const cornerY = OAiy(n - 1);
const halfWayX = d.radial
? twoLines == 0 // Middle line
? (OAix(1) - OAix(0)) / 2
: twoLines == 1 // Top Line
? ((OAix(1) - OAix(0)) * 4) / 6
: ((OAix(1) - OAix(0)) * 2) / 6
: (OAix(1) - OAix(0)) / 2;
const halfWayY = d.radial
? twoLines == 0
? (OAiy(1) - OAiy(0)) / 2
: twoLines == 1
? ((OAiy(1) - OAiy(0)) * 4) / 6
: ((OAiy(1) - OAiy(0)) * 2) / 6
: (OAiy(1) - OAiy(0)) / 2;
const heightLineX = d.radial
? A0B0x / 2
: twoLines == 0 // Middle line
? A0B0x / (atBottom ? 1.8 : 2.2)
: twoLines == 1 // Top Line
? (A0B0x * 4.15) / 6
: (A0B0x * 1.85) / 6; // Bottom Line
const heightLineY = d.radial
? A0B0y / 2
: twoLines == 0
? A0B0y / (atBottom ? 1.8 : 2.2)
: twoLines == 1
? (A0B0y * (atBottom ? 4.2 : 3.9)) / 6 // first is bottom, second is top
: (A0B0y * (atBottom ? 2.05 : 1.75)) / 6; // first is bottom, second is top

return d.square
? `translate(${cornerX + halfWayX + heightLineX},
${cornerY + halfWayY + heightLineY}
)
rotate(
${
rTD(f) +
(d.flipLetters ? 180 : 0) +
(d.radial ? 0 : 90) +
(atBottom ? 180 : 0) +
(atLeft && d.radial ? 180 : 0)
}
)`
: `rotate(${rTD(f2)}) translate(${R + r / 2},0) rotate(${
(d.flipLetters ? 180 : 0) + atLeft ? 180 : 0 // arc radial
})`;
}

const topLine =
!d.flipLines ^ (!d.radial && atBottom) ^ (d.radial && !atLeft);
// square cases where needs flipping
// square && !radial && atBottom
// square && radial && !atLeft

g.append("text")
.style(
"fill",
d.opacity < d.opacityLimit || d.fade > d.fadeLimit
? darken(d.color, d.opacity * d.darkenFactor)
: "white"
)
.style("font-size", d.fontSize)
.style("font-weight", d.bold ? d.bold : "normal")
.attr("font-family", d.fontName)
.attr("dominant-baseline", "middle")
.attr("text-anchor", "middle")
.text(line(d, topLine ? 1 : 2))
.attr(
"transform",
numberOfLines > 1 ? labelTransform(d, 1) : labelTransform(d, 0)
)
.clone()
.text(numberOfLines > 1 ? line(d, topLine ? 2 : 1) : "")
.attr("transform", labelTransform(d, 2));
}
});

const svgRing = svg.node().innerHTML;
svgOrigin.append("g").html(svgRing);
}
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

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