Published
Edited
Apr 30, 2022
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
otherChart = PieChart()
.x("country")
.y("cups");
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
update = function(){
d3.selectAll("#container")
.datum(cs.groups.get(currentVar).all())
.call(myChart);
}();
Insert cell
Insert cell
myChart = PieChart()
.x("key")
.y("value")
.config({
innerRadius: 72,
labelMaxLen: 16
});
Insert cell
Insert cell
function PieChart() {
let
width = 400, // outer width, in pixels
height = 400, // outer height, in pixels
xVal = "key", // given d in data, returns the (ordinal) label
yVal = "value", // given d in data, returns the (quantitative) value
// chartName should be the main var represented in the current chart,
// to be assigned as the id attr in the active items (bar item) parent
chartName = "",

// ---- config object (not direct getters/setters) ----
format = ",", // a format specifier for values (in the label)
colors = palettes.sdmujer.light, // array of string colors, like "#F73" or "red"
stroke = "white", // stroke separating widths
strokeWidth = 0.5, // width of stroke separating wedges
strokeLinejoin = "round", // line join of stroke separating wedges
outerRadius = Math.min(width, height) / 2, // outer radius of pie, in pixels
innerRadius = 0, // inner radius of pie, in pixels (non-zero for donut)
maxRadius = 180,
showLabels = true,
labelColor = "black",
labelMaxLen = undefined, // limit the number of chars in each tick
onMouseOver = function (e, i) {
d3.select(this)
.attr("opacity", 0.8)
.style("cursor", "pointer");
},
onMouseOut = function (e, i) {
d3.select(this)
.attr("opacity", 1)
.style("cursor", "default");
},
onClick = function (e, i) { // toggle class .selected
console.log(i);
let item = d3.select(this);
item.classed("selected", item.classed("selected") ? false : true);
};

// the join -> enter, update and exit
// ----------------------------------
const onEnter = enter => {
const svgEnter = enter.append("svg")
.attr("class", "chart");
const g = svgEnter.append("g");
// create basic DOM elements to update its contents
g.append("g")
.attr("class", "slices items")
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("stroke-linejoin", strokeLinejoin)
.attr("id", chartName);

g.append("g")
.attr("class", "labels")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "middle");
return svgEnter;
};
const onUpdate = update => update;
const onExit = exit => exit.remove();

// returned chart
function chart(sel) {

// keep chart size between limits
outerRadius = outerRadius > Math.min(width, height) / 2 ? Math.min(width, height) : outerRadius;
outerRadius = outerRadius > maxRadius ? maxRadius : outerRadius;
innerRadius = innerRadius > (outerRadius - 16) ? (outerRadius - 16) : innerRadius; // minimum thick

const labelRadius = (0.7 * outerRadius + 0.3 * innerRadius);

sel.each(data => {
// Compute values.
const N = d3.map(data, d => d[xVal]);
const V = d3.map(data, d => d[yVal]);
const I = d3.range(N.length).filter(i => !isNaN(V[i]));
// Construct scales.
const color = d3.scaleOrdinal(N, colors);
// Compute titles.
const formatValue = d3.format(format);
const title = i => `${N[i]}\n${formatValue(V[i])}`;

// Construct arcs.
const arcs = d3.pie().sort(null).value(i => V[i])(I);
const arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius);
const arcLabel = d3.arc().innerRadius(labelRadius).outerRadius(labelRadius);

// work with the selection from which the call function was called
const svg = sel
.selectAll("svg")
.data([data])
.join(onEnter, onUpdate, onExit)
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height]); // center
const g = svg.select("g");

// *** Transition ***
const t = g.transition().duration(500);
// Observable only, stop the animation if it isn't done and the cell is disposed
invalidation.then(() => svg.interrupt(t));
// ----- ITEMS (arcs) ------- ***
// ------------------------------
const moveItems = item => item
.attr("fill", d => color(N[d.data]))
.attr("d", arc);
const items = g.select(".items").selectAll(".item")
.data(arcs)
.join(
enter => {
// https://github.com/d3/d3-selection/issues/201#issuecomment-592952739
// const items must refer to paths, no titles
// so we use an auxliliary selection sel
let sel = enter.append("path")
.attr("class", "item slice")
.call(moveItems);
sel.append("title")
.text(d => title(d.data));
return sel;
},
update => {
update
.call(update => update.transition(t).call(moveItems));
update.selectAll("title")
.text(d => title(d.data));
return update;
}
);

// ---------- Labels --------- ***
// -------------------------------
const configLabels = item => item
.attr("transform", d => `translate(${arcLabel.centroid(d)})`)
.selectAll("tspan")
.data(d => {
// how much info you will show
// deppends on angle of slice (available space)
const lines = `${title(d.data)}`.split(/\n/);
if(d.endAngle - d.startAngle < 0.15){
return []; // nothing
}
if(d.endAngle - d.startAngle < 0.25){
return lines.slice(0, 1); // only label
}
return lines; // label and value
})
.join("tspan")
.attr("x", 0)
.attr("y", (_, i) => `${i * 1.1}em`) // line height
.attr("font-weight", (_, i) => i ? null : "bold")
.text(function(d, i){
if(i == 0 && labelMaxLen && d.length > labelMaxLen){
// trim labels (first line only)
return d.substring(0, labelMaxLen) + "…";
};
return d;
});
if(showLabels){
const values = g.select(".labels").selectAll(".label")
.data(arcs)
.join(
enter =>
enter.append("text")
.attr("class", "label")
.attr("fill", labelColor)
.attr("text-anchor", "middle")
.call(configLabels),
update => {
update.transition(t);
let sel = update
.call(configLabels);
return sel;
}
);
}

// ****** Events -------
items.on("click", onClick);
items.on("mouseover", onMouseOver);
items.on("mouseout", onMouseOut);
});
}

// +++++++ getters and setters ++++++++
// ------------------------------------

chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return xVal;
xVal = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yVal;
yVal = _;
return chart;
};
chart.chartName = function(_) {
if (!arguments.length) return chartName;
chartName = _;
return chart;
};
chart.config = function(_) {
if (!arguments.length) return undefined; // setter only
({
format = ",", // a format specifier for values (in the label)
colors = palettes.sdmujer.light,
stroke = "white",
strokeWidth = 0.5,
strokeLinejoin = "round",
outerRadius = Math.min(width, height) / 2,
innerRadius = 0,
maxRadius = 200,
showLabels = true,
labelColor = "black",
labelMaxLen = undefined,
onMouseOver = function (e, i) {
d3.select(this)
.attr("opacity", 0.8)
.style("cursor", "pointer");
},
onMouseOut = function (e, i) {
d3.select(this)
.attr("opacity", 1)
.style("cursor", "default");
},
onClick = function (e, i) {
console.log(i);
let item = d3.select(this);
item.classed("selected", item.classed("selected") ? false : true);
}
} = _);
return chart;
};

return chart;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
palettes.sdmujer.light
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